diff --git a/.agents/skills/clarion-workflow/SKILL.md b/.agents/skills/loomweave-workflow/SKILL.md similarity index 90% rename from .agents/skills/clarion-workflow/SKILL.md rename to .agents/skills/loomweave-workflow/SKILL.md index db6925b6..1b074574 100644 --- a/.agents/skills/clarion-workflow/SKILL.md +++ b/.agents/skills/loomweave-workflow/SKILL.md @@ -1,21 +1,21 @@ --- -name: clarion-workflow +name: loomweave-workflow description: > Use when orienting in an unfamiliar or large codebase and you want to avoid re-reading or grepping the whole source tree: answering "what calls X", "where is X defined", "what does X depend on", "what subsystem is X in", or - "find the function/class/module that does Y". Applies whenever a Clarion - code-archaeology MCP server (clarion serve / mcp__clarion__* tools) is + "find the function/class/module that does Y". Applies whenever a Loomweave + code-archaeology MCP server (loomweave serve / mcp__loomweave__* tools) is available for the project. --- -# Clarion Workflow +# Loomweave Workflow ## Overview -Clarion pre-extracts a codebase into a queryable map — entities (functions, +Loomweave pre-extracts a codebase into a queryable map — entities (functions, classes, modules, files), the call/reference/import edges between them, and -subsystem clusters — and serves it over MCP. **Ask Clarion instead of +subsystem clusters — and serves it over MCP. **Ask Loomweave instead of re-exploring the tree.** One `find_entity` + one `callers_of` answers "what calls this?" without reading a single file. @@ -26,7 +26,7 @@ calls this?" without reading a single file. - You need a function's neighborhood, execution paths, or which subsystem it belongs to. **Not for:** editing code, reading exact implementation bodies (use `summary` or -read the file once you have its path), or codebases with no `.clarion/` index. +read the file once you have its path), or codebases with no `.loomweave/` index. ## Entity IDs — the model @@ -44,10 +44,10 @@ They are not interchangeable: - **`id`** is the entity's *locator* — a mutable address. It changes when the code is renamed or moved, and it's the right thing to feed into the next - Clarion tool call (above). + Loomweave tool call (above). - **`sei`** is the entity's *durable, stable identity*. It survives renames and moves. **When you record a cross-tool binding** — e.g. attaching a Filigree - issue to a Clarion entity — **bind on the `sei`, not the `id`.** A binding + issue to a Loomweave entity — **bind on the `sei`, not the `id`.** A binding keyed on the mutable `id` silently breaks the first time the entity moves. `sei` is `null` when the index predates SEI support or the entity has no binding @@ -99,7 +99,7 @@ re-reading each path element. `truncated`/`truncation_reason` report `edge-cap` ## Catalogue tools — inspection · faceted search · shortcuts -Beyond navigation, Clarion serves a **stateless catalogue** of read tools. All +Beyond navigation, Loomweave serves a **stateless catalogue** of read tools. All of them: take explicit ids/scopes (no cursor/session — there is no `goto`/`back` state to manage); **paginate** (`limit`/`offset`, with a `page` block reporting `total`/`returned`/`truncated` — no silent caps); carry `sei` on every entity @@ -151,14 +151,14 @@ honest-empty unless a plugin emits those tags. Likewise `high_churn` and `index_diff` for repo-level freshness). `search_semantic` is also in the catalogue. It is opt-in under -`semantic_search:`; when enabled, `clarion analyze` populates the git-ignored -`.clarion/embeddings.db` sidecar and the query path filters stale vectors by +`semantic_search:`; when enabled, `loomweave analyze` populates the git-ignored +`.loomweave/embeddings.db` sidecar and the query path filters stale vectors by content hash. > Not in this catalogue: `emit_observation` as a general-purpose write surface. **Guidance authoring has an operator boundary.** Operators can manage sheets via -`clarion guidance create/edit/show/list/delete/promote` (plus `export`/`import` +`loomweave guidance create/edit/show/list/delete/promote` (plus `export`/`import` for team sharing). Agents may call `propose_guidance` to create a Filigree observation, but that proposal is inert until an operator promotes it through `promote_guidance` or the CLI. Promoted sheets reach you through `guidance_for` @@ -192,10 +192,10 @@ and are composed into `summary` prompts with a real guidance fingerprint. ## Launch -`clarion serve --path ` where `` contains `.clarion/clarion.db` -(built by `clarion analyze `). In an MCP client the tools appear as -`mcp__clarion__find_entity`, etc. +`loomweave serve --path ` where `` contains `.loomweave/loomweave.db` +(built by `loomweave analyze `). In an MCP client the tools appear as +`mcp__loomweave__find_entity`, etc. -Besides the tools, the server exposes a `clarion://context` **resource** — live +Besides the tools, the server exposes a `loomweave://context` **resource** — live entity/subsystem/finding counts and index freshness as JSON, a lightweight read when you only want the numbers (`project_status` is the fuller tool-based view). diff --git a/.codex/config.toml b/.codex/config.toml index 4214a24b..e735a39e 100644 --- a/.codex/config.toml +++ b/.codex/config.toml @@ -5,7 +5,7 @@ command = "/home/john/.local/bin/filigree-mcp" args = ["--project", "/home/john/clarion"] -[mcp_servers.clarion-dogfood] -command = "/home/john/clarion/target/release/clarion" +[mcp_servers.loomweave-dogfood] +command = "/home/john/clarion/target/release/loomweave" args = ["serve", "--path", "/home/john/clarion"] env = { PATH = "/home/john/clarion/plugins/python/.venv/bin:/home/john/.local/bin:/home/john/elspeth/.venv/bin:/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin:/home/john/.cargo/bin:/home/john/.nvm/versions/node/v20.19.3/bin:/home/john/miniconda/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" } diff --git a/.config/nextest.toml b/.config/nextest.toml index e7d60791..585efb1c 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -2,5 +2,5 @@ serve-http = { max-threads = 1 } [[profile.default.overrides]] -filter = 'package(clarion-cli) and binary(=serve)' +filter = 'package(loomweave-cli) and binary(=serve)' test-group = 'serve-http' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 04d831f4..64c1cee1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -73,7 +73,7 @@ jobs: - name: install cargo-nextest uses: taiki-e/install-action@e310bff3ef77234d477d6bb655da153a5c49d1db - # Ensure all workspace binaries (notably clarion-plugin-fixture) are built + # Ensure all workspace binaries (notably loomweave-plugin-fixture) are built # before nextest runs. wp2_e2e tests need the fixture binary on disk and # nextest's CARGO_BIN_EXE_* propagation is not reliably set for cross-package # dev-dep binaries (deferred issue clarion-adeff0916d). @@ -167,9 +167,9 @@ jobs: run: | uv export --project plugins/python --locked --extra dev --no-emit-project \ --format requirements.txt \ - --output-file /tmp/clarion-python-dev-requirements.txt + --output-file /tmp/loomweave-python-dev-requirements.txt uv run --project plugins/python --extra dev pip-audit \ - -r /tmp/clarion-python-dev-requirements.txt + -r /tmp/loomweave-python-dev-requirements.txt - name: check B.4*/B.5 performance gates run: uv run --project plugins/python --extra dev python scripts/check-b4-gate-result.py --run-b5-smoke diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 89383254..f9eabd03 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,7 +1,7 @@ name: Docs -# Build and deploy the Clarion documentation site (web/) to GitHub Pages. -# The site is served at https://clarion.foundryside.dev via the CNAME in +# Build and deploy the Loomweave documentation site (web/) to GitHub Pages. +# The site is served at https://loomweave.foundryside.dev via the CNAME in # web/docs/CNAME. Deploys on push to main; PRs get a strict build check only. on: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6f835378..3d2fcd59 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,7 +12,20 @@ on: # The `release` job is gated on event_name == 'push', so workflow_dispatch # never publishes a public Release — it only validates that the build/verify # plumbing is healthy before a tag push. + # + # `target_index: testpypi` additionally runs `publish-pypi` against TestPyPI + # (the Phase-C8 dry-run). Real PyPI is NEVER reachable via dispatch — it + # publishes solely on a pushed `v*` tag. Leave it `none` for a plain + # build/verify dry-run. workflow_dispatch: + inputs: + target_index: + description: "Publish dry-run target (real PyPI only ever publishes on a pushed v* tag)" + type: choice + options: + - none + - testpypi + default: none env: CARGO_TERM_COLOR: always @@ -123,9 +136,9 @@ jobs: run: | uv export --project plugins/python --locked --extra dev --no-emit-project \ --format requirements.txt \ - --output-file /tmp/clarion-python-dev-requirements.txt + --output-file /tmp/loomweave-python-dev-requirements.txt uv run --project plugins/python --extra dev pip-audit \ - -r /tmp/clarion-python-dev-requirements.txt + -r /tmp/loomweave-python-dev-requirements.txt - name: check B.4*/B.5 performance gates run: uv run --project plugins/python --extra dev python scripts/check-b4-gate-result.py --run-b5-smoke @@ -159,7 +172,7 @@ jobs: build-rust: needs: [verify] - name: Build clarion (${{ matrix.target }}) + name: Build loomweave (${{ matrix.target }}) runs-on: ${{ matrix.runner }} strategy: fail-fast: false @@ -188,15 +201,15 @@ jobs: - name: build release run: | set -euo pipefail - cargo build --release --locked --bin clarion --target ${{ matrix.target }} + cargo build --release --locked --bin loomweave --target ${{ matrix.target }} - name: package id: package run: | set -euo pipefail - STAGE="clarion-${{ matrix.target }}" + STAGE="loomweave-${{ matrix.target }}" mkdir -p "$STAGE" - cp "target/${{ matrix.target }}/release/clarion" "$STAGE/" + cp "target/${{ matrix.target }}/release/loomweave" "$STAGE/" cp README.md "$STAGE/" if [ -f LICENSE ]; then cp LICENSE "$STAGE/"; fi ARCHIVE="$STAGE.tar.gz" @@ -210,19 +223,70 @@ jobs: fi SMOKE_DIR="$(mktemp -d)" tar xzf "$ARCHIVE" -C "$SMOKE_DIR" - "$SMOKE_DIR/$STAGE/clarion" --version - "$SMOKE_DIR/$STAGE/clarion" --help >/dev/null + "$SMOKE_DIR/$STAGE/loomweave" --version + "$SMOKE_DIR/$STAGE/loomweave" --help >/dev/null echo "archive=$ARCHIVE" >> "$GITHUB_OUTPUT" - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with: - name: clarion-${{ matrix.target }} + name: loomweave-${{ matrix.target }} path: | ${{ steps.package.outputs.archive }} ${{ steps.package.outputs.archive }}.sha256 if-no-files-found: error retention-days: 7 + build-wheels: + needs: [verify] + name: Build loomweave wheel (${{ matrix.target }}) + runs-on: ${{ matrix.runner }} + # maturin bin-wheels for PyPI. Matrix mirrors `build-rust` (Linux x86_64 + + # macOS aarch64); the Intel-macOS leg stays dropped until those runners + # recover (same note as build-rust). Off-matrix platforms fall back to the + # cosign-signed GitHub Release tarballs / cargo-binstall. + strategy: + fail-fast: false + matrix: + include: + - target: x86_64-unknown-linux-gnu + runner: ubuntu-latest + manylinux: "2_28" + - target: aarch64-apple-darwin + runner: macos-14 + manylinux: auto + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 + + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 + with: + python-version: "3.11" + + - name: build wheel (maturin) + # Reads crates/loomweave-cli/pyproject.toml ([tool.maturin] bindings="bin"). + # On Linux maturin-action runs inside a manylinux_2_28 container so the + # wheel is broadly installable (a bare host build tags the runner's glibc). + uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b + with: + command: build + target: ${{ matrix.target }} + manylinux: ${{ matrix.manylinux }} + args: --release --out dist --manifest-path crates/loomweave-cli/Cargo.toml + + - name: smoke-check wheel carries the binary + run: | + set -euo pipefail + WHL=$(ls dist/loomweave-*.whl | head -1) + echo "built: $WHL" + python -m zipfile -l "$WHL" | grep -E 'data/scripts/loomweave' \ + || { echo "::error::wheel $WHL missing data/scripts/loomweave"; exit 1; } + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a + with: + name: loomweave-wheel-${{ matrix.target }} + path: dist/*.whl + if-no-files-found: error + retention-days: 7 + build-plugin: needs: [verify] name: Build Python plugin sdist @@ -243,29 +307,34 @@ jobs: - name: build sdist run: uv run --project plugins/python --extra dev python -m build --sdist --outdir dist plugins/python + - name: build wheel + # Pure-Python wheel (py3-none-any). Published to PyPI alongside the + # sdist by `publish-pypi`; the sdist remains the GitHub-Release asset. + run: uv build --wheel --project plugins/python --out-dir dist + - name: export locked Python runtime dependencies run: | uv export --project plugins/python --locked --no-dev --no-emit-project \ --format requirements.txt \ - --output-file dist/clarion-plugin-python-runtime-requirements.txt + --output-file dist/loomweave-plugin-python-runtime-requirements.txt uv export --project plugins/python --locked --no-dev --no-emit-project \ --format cyclonedx1.5 \ - --output-file dist/clarion-plugin-python-sbom.cdx.json + --output-file dist/loomweave-plugin-python-sbom.cdx.json - name: smoke install sdist run: | set -euo pipefail python -m venv .venv-smoke . .venv-smoke/bin/activate - python -m pip install --require-hashes -r dist/clarion-plugin-python-runtime-requirements.txt + python -m pip install --require-hashes -r dist/loomweave-plugin-python-runtime-requirements.txt python -m pip install --no-deps dist/*.tar.gz # Version-agnostic: assert the module constant matches the installed # distribution metadata (the sdist's packaged version) rather than a # hardcoded literal, so a release version bump can't silently break # this smoke gate (clarion-12667da9f5: a hardcoded '1.0.0' here blocked # the v1.0.1 publish after the binaries built fine). - python -c "import clarion_plugin_python, importlib.metadata as m; v = clarion_plugin_python.__version__; d = m.version('clarion-plugin-python'); assert v == d, f'module {v!r} != dist {d!r}'" - command -v clarion-plugin-python + python -c "import loomweave_plugin_python, importlib.metadata as m; v = loomweave_plugin_python.__version__; d = m.version('loomweave-plugin-python'); assert v == d, f'module {v!r} != dist {d!r}'" + command -v loomweave-plugin-python - name: checksum run: | @@ -277,13 +346,83 @@ jobs: - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with: - name: clarion-plugin-python-sdist + name: loomweave-plugin-python-sdist path: | dist/*.tar.gz dist/*.tar.gz.sha256 + dist/*.whl if-no-files-found: error retention-days: 7 + publish-pypi: + name: Publish to PyPI (plugin first, then loomweave) + needs: [build-wheels, build-plugin] + runs-on: ubuntu-latest + # Real PyPI: ONLY on a pushed v* tag — already gated on the full `verify` + # job via build-wheels/build-plugin (`needs: [verify]`). TestPyPI dry-run + # (Phase C8): workflow_dispatch with target_index=testpypi. Dispatch can + # NEVER reach real PyPI. + # + # PRECONDITIONS before the first real run (Phase D2 — owner, on pypi.org): + # * Register a Trusted Publisher for BOTH projects (`loomweave` and + # `loomweave-plugin-python`): owner `foundryside-dev`, repo `loomweave`, + # workflow `release.yml`, environment `pypi` (and a `testpypi` + # environment + TestPyPI publishers for the dry-run). + # * Recommended: add required reviewers to the `pypi` environment so the + # upload pauses for a human before going live. + # Until then this job fails the OIDC exchange (fail-safe: it cannot + # mis-publish, only error). + # + # Publish order is plugin-first so that by the time `loomweave` is on the + # index its `loomweave-plugin-python==` pin already resolves. + if: >- + (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) || + (github.event_name == 'workflow_dispatch' && inputs.target_index == 'testpypi') + environment: ${{ github.event_name == 'workflow_dispatch' && 'testpypi' || 'pypi' }} + permissions: + id-token: write + steps: + - name: download plugin dists + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c + with: + name: loomweave-plugin-python-sdist + path: dl-plugin + + - name: stage plugin package dir (wheel + sdist only) + run: | + set -euo pipefail + mkdir -p pkg-plugin + cp dl-plugin/*.whl dl-plugin/*.tar.gz pkg-plugin/ + ls -la pkg-plugin + + - name: publish loomweave-plugin-python (FIRST) + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b + with: + packages-dir: pkg-plugin + attestations: true + repository-url: ${{ github.event_name == 'workflow_dispatch' && 'https://test.pypi.org/legacy/' || 'https://upload.pypi.org/legacy/' }} + + - name: download loomweave wheels + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c + with: + pattern: loomweave-wheel-* + path: dl-loomweave + merge-multiple: true + + - name: stage loomweave package dir + run: | + set -euo pipefail + mkdir -p pkg-loomweave + cp dl-loomweave/*.whl pkg-loomweave/ + ls -la pkg-loomweave + + - name: publish loomweave (SECOND) + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b + with: + packages-dir: pkg-loomweave + attestations: true + repository-url: ${{ github.event_name == 'workflow_dispatch' && 'https://test.pypi.org/legacy/' || 'https://upload.pypi.org/legacy/' }} + release: name: Create GitHub Release runs-on: ubuntu-latest @@ -362,25 +501,25 @@ jobs: # bump or normalization-rule change would invalidate a literal URL. # `|| true` so `set -e` does not abort the script when the glob # is empty — we want to fall through to the explicit error below. - SDIST_PATH=$(ls release/clarion[-_]plugin[-_]python*.tar.gz 2>/dev/null | head -n1 || true) + SDIST_PATH=$(ls release/loomweave[-_]plugin[-_]python*.tar.gz 2>/dev/null | head -n1 || true) if [ -z "$SDIST_PATH" ]; then echo "::error::no Python plugin sdist found in release/ — refusing to publish notes with a broken install link" >&2 exit 1 fi SDIST_NAME=$(basename "$SDIST_PATH") { - echo "## Clarion ${GITHUB_REF_NAME}" + echo "## Loomweave ${GITHUB_REF_NAME}" echo "" echo "### Install" echo "" echo "Pick the archive for your platform from the Assets list below." - echo "Verify the SHA256, extract, and place \`clarion\` on your \`\$PATH\`." + echo "Verify the SHA256, extract, and place \`loomweave\` on your \`\$PATH\`." echo "" echo "\`\`\`bash" - echo "curl -L -o clarion.tar.gz \\" - echo " https://github.com/${GITHUB_REPOSITORY}/releases/download/${GITHUB_REF_NAME}/clarion-x86_64-unknown-linux-gnu.tar.gz" - echo "tar xzf clarion.tar.gz" - echo "install clarion-x86_64-unknown-linux-gnu/clarion ~/.local/bin/" + echo "curl -L -o loomweave.tar.gz \\" + echo " https://github.com/${GITHUB_REPOSITORY}/releases/download/${GITHUB_REF_NAME}/loomweave-x86_64-unknown-linux-gnu.tar.gz" + echo "tar xzf loomweave.tar.gz" + echo "install loomweave-x86_64-unknown-linux-gnu/loomweave ~/.local/bin/" echo "" echo "pipx install \\" echo " https://github.com/${GITHUB_REPOSITORY}/releases/download/${GITHUB_REF_NAME}/${SDIST_NAME}" @@ -402,7 +541,7 @@ jobs: uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda with: tag_name: ${{ github.ref_name }} - name: Clarion ${{ github.ref_name }} + name: Loomweave ${{ github.ref_name }} body_path: ${{ steps.notes.outputs.notes-file }} draft: false prerelease: ${{ contains(github.ref_name, '-rc') || contains(github.ref_name, '-alpha') || contains(github.ref_name, '-beta') }} diff --git a/.gitignore b/.gitignore index d0293346..9b5e95da 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ **/*.rs.bk Cargo.lock.bak -# SQLite working files (project-level .clarion/ is tracked per ADR-005) +# SQLite working files (project-level .loomweave/ is tracked per ADR-005) *.db-journal *.db-wal @@ -27,15 +27,15 @@ htmlcov/ # Smoke-test result artifacts (per-run; archived separately at tag-cut) tests/e2e/external-operator-smoke-results-*.md -# Generated skill fingerprint (rewritten by `clarion install --skills`). -.agents/skills/clarion-workflow/.fingerprint +# Generated skill fingerprint (rewritten by `loomweave install --skills`). +.agents/skills/loomweave-workflow/.fingerprint -# Clarion runtime artifacts — the index DB, per-project instance fingerprint, +# Loomweave runtime artifacts — the index DB, per-project instance fingerprint, # and analyze lock change on every run, so they are not tracked -# (see .clarion/.gitignore). -.clarion/clarion.db -.clarion/instance_id -.clarion/clarion.lock +# (see .loomweave/.gitignore). +.loomweave/loomweave.db +.loomweave/instance_id +.loomweave/loomweave.lock # Documentation site build output (mkdocs `site_dir`, web/mkdocs.yml). /site-build/ diff --git a/.jules/bolt.md b/.jules/bolt.md index 2c736dc8..06bfcd1e 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -1,3 +1,3 @@ ## 2026-06-04 - Borrowing strings during clustering graph traversal -**Learning:** In `crates/clarion-analysis/src/lib.rs`, the fallback algorithm `local_weighted_components` cloned module string IDs deeply while populating neighbor lists (`neighbors`), seen sets (`seen`), and graph traversal stacks (`stack`). For a typical graph with hundreds or thousands of nodes, this causes extensive unnecessary memory allocations and CPU overhead during clustering. +**Learning:** In `crates/loomweave-analysis/src/lib.rs`, the fallback algorithm `local_weighted_components` cloned module string IDs deeply while populating neighbor lists (`neighbors`), seen sets (`seen`), and graph traversal stacks (`stack`). For a typical graph with hundreds or thousands of nodes, this causes extensive unnecessary memory allocations and CPU overhead during clustering. **Action:** Replace `String` with `&str` references inside internal clustering structures. Rust's borrow checker can perfectly track the lifetimes bound to the original `ModuleGraph`, and `.to_owned()` only needs to be called when pushing a module into the final partitioned `Vec` results. This dramatically reduces heap allocations. diff --git a/.clarion/.gitignore b/.loomweave/.gitignore similarity index 80% rename from .clarion/.gitignore rename to .loomweave/.gitignore index 2944180f..d1d0e32b 100644 --- a/.clarion/.gitignore +++ b/.loomweave/.gitignore @@ -1,13 +1,13 @@ -# Clarion .gitignore — ADR-005 tracked-vs-excluded list. +# Loomweave .gitignore — ADR-005 tracked-vs-excluded list. # Tracked (committed): config.json, .gitignore itself. -# Excluded (ignored): clarion.db + instance_id (runtime artifacts, also pinned +# Excluded (ignored): loomweave.db + instance_id (runtime artifacts, also pinned # in the repo-root .gitignore), WAL sidecars, shadow DB, per-run logs, tmp. # Runtime artifacts: the index DB and the per-project instance fingerprint # change on every analyze run, so they are NOT tracked (untracked 2026-06-02). -clarion.db +loomweave.db instance_id -clarion.lock +loomweave.lock # SQLite write-ahead files never belong in the repo. *-wal diff --git a/.clarion/config.json b/.loomweave/config.json similarity index 100% rename from .clarion/config.json rename to .loomweave/config.json diff --git a/.mcp.json b/.mcp.json index e123da4a..6b84d9ec 100644 --- a/.mcp.json +++ b/.mcp.json @@ -1,10 +1,10 @@ { "mcpServers": { - "clarion": { + "loomweave": { "args": [ "serve" ], - "command": "/home/john/.local/bin/clarion", + "command": "/home/john/.local/bin/loomweave", "env": {}, "type": "stdio" }, @@ -18,10 +18,10 @@ "mcp", "--root", ".", - "--clarion-url", + "--loomweave-url", "http://127.0.0.1:9111", "--filigree-url", - "http://127.0.0.1:8542/api/loom/scan-results" + "http://127.0.0.1:8542/api/weft/scan-results" ], "command": "/home/john/.local/bin/wardline", "type": "stdio" diff --git a/CHANGELOG.md b/CHANGELOG.md index e6de20c7..1e5ec4e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -All notable changes to Clarion are documented here. The format is loosely based -on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and Clarion -follows [Semantic Versioning](https://semver.org/) for the `clarion` binary, +All notable changes to Loomweave are documented here. The format is loosely based +on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and Loomweave +follows [Semantic Versioning](https://semver.org/) for the `loomweave` binary, the workspace crates, and the Python plugin. API versioning for the federation HTTP read API (`/api/v1/...`) is independent @@ -12,6 +12,63 @@ only when an incompatible change is made to that surface. See ## [Unreleased] +## [1.0.0] — Loomweave — 2026-06-05 + +**This release renames the product and re-baselines its version.** What shipped +as **Clarion 1.3.0** is now **Loomweave 1.0.0**. The entries below this header +(`[1.3.0]` and earlier) are the pre-rename Clarion lineage, preserved verbatim +as history; they describe the same software under its former name. + +This is a **clean break with no users yet** — there are no migration shims, +on-disk path auto-detection, dual-magic fallbacks, binary symlinks, plugin-prefix +fallbacks, or MCP server-name aliases. Existing local state (a `.clarion/` index) +is not migrated; re-run `loomweave analyze` to rebuild under the new paths. + +### Changed — naming + +- **Product `Clarion` → `Loomweave`** and **framework/suite `Loom` → `Weft`**. + Hierarchy: the **Weft** framework comprises **Loomweave** (this product, the + flagship code-archaeology tool), Filigree, Wardline, and Legis (+ Shuttle planned). +- **Binary** `clarion` → `loomweave`; **workspace crates** `clarion-{core,cli,mcp, + storage,analysis,federation,scanner,plugin-fixture}` → `loomweave-*`. +- **Python plugin** package `clarion-plugin-python` → `loomweave-plugin-python`, + module `clarion_plugin_python` → `loomweave_plugin_python`, shared-data path + `share/clarion/plugins/` → `share/loomweave/plugins/`. +- **Persisted identity**: `.clarion/` → `.loomweave/`, `clarion.db` → + `loomweave.db`, `clarion.yaml` → `loomweave.yaml`; the SQLite `application_id` + magic `0x434C524E` (`"CLRN"`) → `0x4C4D5756` (`"LMWV"`). +- **MCP server identity** `clarion` → `loomweave`; the `clarion-workflow` prompt/ + skill → `loomweave-workflow`. +- **Environment variables** `CLARION_*` → `LOOMWEAVE_*`; the federation/suite + variables `CLARION_LOOM_*` (e.g. `CLARION_LOOM_TOKEN`) → `WEFT_*` (e.g. + `WEFT_TOKEN`) — they name the framework, not the product. +- **Error / diagnostic codes** prefix `CLA-` → `LMWV-` (e.g. + `CLA-INFRA-STORAGE-FOREIGN-DB` → `LMWV-INFRA-STORAGE-FOREIGN-DB`); the + ADR-022 plugin `rule_id_prefix` grammar core prefix `CLA-` → `LMWV-`. +- **Documentation/site** repository `github.com/tachyon-beep/clarion` → + `github.com/foundryside-dev/loomweave`; docs domain `clarion.foundryside.dev` + → `loomweave.foundryside.dev`. + +### Changed — federation contract (cross-product; Filigree + Wardline move in lockstep) + +These touch the wire contract shared with sibling products and require the peers +to be updated in step (the peer pins/clients are being renamed together): + +- Federation HTTP read API routes `/api/loom/...` → `/api/weft/...`; identity + headers `X-Loom-Component` / `X-Loom-Nonce` / `X-Loom-Timestamp` → + `X-Weft-*`; the `X-Loom-Component` identity value `clarion:` → + `loomweave:`. `api_version` stays `1` (a rename is not a wire-incompatible change). +- Federation JSON field names `loom_*` (e.g. `loom_files`, `loom_findings`, + `loom_component`) → `weft_*`. +- The Filigree entity-association field `clarion_entity_id` → `loomweave_entity_id`. +- The Stable Entity Identifier (SEI) prefix `clarion:eid:` → `loomweave:eid:` + (ADR-038 / the Weft SEI conformance standard). + +### Version + +- Recut `1.3.0` → `1.0.0` across the workspace `Cargo.toml`, every + `pyproject.toml`/`plugin.toml`, and the Python plugin `__version__`. + ## [1.3.0] — 2026-06-05 ### Added @@ -23,7 +80,7 @@ only when an incompatible change is made to that surface. See Wardline trust decorators (`external_boundary` / `trust_boundary` / `trusted`) receive `wardline` entity metadata and `wardline:*` tags when the descriptor is available; a missing, invalid, or version-skewed descriptor degrades honestly to - normal structural extraction. This **fully retires** the Clarion-side + normal structural extraction. This **fully retires** the Loomweave-side `wardline.core.registry` startup coupling (federation asterisk 2, registered at `~/loom/asterisk-register.md`): plugin startup performs zero in-process Wardline import, so the plugin no longer requires a co-installed Wardline and is robust to Wardline's @@ -34,7 +91,7 @@ only when an incompatible change is made to that surface. See - **Filigree issue lookups key by Stable Entity Identity (SEI).** The MCP `entity_issue_list` path and the federation Filigree client resolve issues by - SEI rather than source locator, aligning issue enrichment with Clarion's stable + SEI rather than source locator, aligning issue enrichment with Loomweave's stable identity (one key per entity — SEI xor locator, no per-row fallback). - Refreshed release-facing README / index documentation for the 1.3.0 release line, including the 39-tool MCP surface, current install artifact names, fixed @@ -56,26 +113,26 @@ only when an incompatible change is made to that surface. See ### Fixed -- **`clarion doctor` reports enrich-only integration bindings as a warning, not a +- **`loomweave doctor` reports enrich-only integration bindings as a warning, not a gate failure (federation-axiom compliance).** A missing or stale - Clarion+Filigree+Wardline binding previously mapped to `problem` (exit 1), + Loomweave+Filigree+Wardline binding previously mapped to `problem` (exit 1), which made an enrich-only sibling effectively required — contradicting - `loom.md` §5. Both the JSON and text doctor paths now report `warning` for + `weft.md` §5. Both the JSON and text doctor paths now report `warning` for missing/stale bindings; unparseable bindings and `--fix` repair failures remain - `problem`. A bare `clarion doctor` on a no-bindings (Clarion-solo or - Clarion+Filigree-only) project now exits 0 with the warning surfaced. + `problem`. A bare `loomweave doctor` on a no-bindings (Loomweave-solo or + Loomweave+Filigree-only) project now exits 0 with the warning surfaced. ### Security - **Closed a config-driven command-execution path from untrusted repository - contents (`clarion-4b5a8aff54`).** `clarion analyze` (the SEI git-rename - signal) and `clarion serve` (the `index_diff_get` freshness/drift report) + contents (`clarion-4b5a8aff54`).** `loomweave analyze` (the SEI git-rename + signal) and `loomweave serve` (the `index_diff_get` freshness/drift report) shelled `git` inside the analyzed repository with repo-local configuration and Git attributes enabled, so a malicious repository could execute arbitrary commands as the local user during an ordinary analyze/serve — via `core.fsmonitor`, an external diff/textconv driver, or a `filter..clean` selected by a `filter` attribute. All corpus-facing `git` calls now route - through a single hardened helper (`clarion_core::hardened_git_command`) that + through a single hardened helper (`loomweave_core::hardened_git_command`) that ignores operator/global/system config, strips config/exec-injecting environment variables, overrides the program-naming repo-local keys via highest-precedence `-c` flags (including `core.fsmonitor=false` and `core.attributesFile=`), and @@ -103,18 +160,18 @@ only when an incompatible change is made to that surface. See - **Guidance maturity — WS6 (REQ-GUIDANCE-03/-05/-06; ADR-007, ADR-024).** The guidance system moves from schema baseline to operator-usable. - - **`clarion guidance` CLI** — `create` / `edit` (`$EDITOR`) / `show` / `list` + - **`loomweave guidance` CLI** — `create` / `edit` (`$EDITOR`) / `show` / `list` / `delete` / `export` / `import`. Match-rule syntax (`path:` / `tag:` / `kind:` / `subsystem:` / `entity:`), scope-levels (project→function), and `--expires` normalisation to a full ISO-8601 instant. Sheets are written via a - new non-run-scoped `clarion-storage` guidance API, and the rule-matcher is - lifted into `clarion-storage` as the single source of truth shared by the CLI, + new non-run-scoped `loomweave-storage` guidance API, and the rule-matcher is + lifted into `loomweave-storage` as the single source of truth shared by the CLI, `analyze`, and the MCP read path. - - **Staleness findings (`analyze`).** `CLA-FACT-GUIDANCE-ORPHAN` (WARN) now also + - **Staleness findings (`analyze`).** `LMWV-FACT-GUIDANCE-ORPHAN` (WARN) now also fires for a `match_rules {entity:…}` rule pointing at a deleted entity (was - `guides`-edge only); new `CLA-FACT-GUIDANCE-EXPIRED` (INFO) and - `CLA-FACT-GUIDANCE-CHURN-STALE` (WARN, confidence 0.7 heuristic, asymmetric - threshold 50 / 20-pinned). Surfaced via `clarion guidance list --stale` + `guides`-edge only); new `LMWV-FACT-GUIDANCE-EXPIRED` (INFO) and + `LMWV-FACT-GUIDANCE-CHURN-STALE` (WARN, confidence 0.7 heuristic, asymmetric + threshold 50 / 20-pinned). Surfaced via `loomweave guidance list --stale` (review-cadence age) / `--expired`. CHURN-STALE is honest-empty until `git_churn_count` population lands. - **Team import/export.** `export --to ` / `import ` — deterministic @@ -143,9 +200,9 @@ only when an incompatible change is made to that surface. See to `..HEAD`. `analyze` also applies any pending migrations on startup (under the analyze lock) so a binary upgrade does not hard-fail on a DB that `install` has not re-touched. -- **Stable Entity Identity (SEI) — Wave 1 / WS1 (ADR-038).** Clarion is now the +- **Stable Entity Identity (SEI) — Wave 1 / WS1 (ADR-038).** Loomweave is now the suite's identity authority: it mints a durable, opaque **SEI** - (`clarion:eid:`) for every entity + (`loomweave:eid:`) for every entity and demotes the `{plugin}:{kind}:{qualname}` id to a mutable **locator**, so cross-tool bindings survive rename and move. - Migration `0005` adds `sei_bindings` (durable identity store, keyed by SEI, @@ -164,7 +221,7 @@ only when an incompatible change is made to that surface. See - HTTP read API: `POST /api/v1/identity/resolve` (+ `:batch`), `GET /api/v1/identity/sei/{sei}`, `GET /api/v1/identity/lineage/{sei}`. `resolve` fail-closed-rejects an SEI-shaped input by the reserved - `clarion:eid:` prefix (REQ-F-02). `_capabilities` advertises + `loomweave:eid:` prefix (REQ-F-02). `_capabilities` advertises `sei: { supported: true, version: 1 }`. - The MCP tool surface carries the `sei` alongside every entity id (no MCP locator exception — REQ-C-04), via a read-time `sei_bindings` join. @@ -172,7 +229,7 @@ only when an incompatible change is made to that surface. See passes; the cross-tool hard-cutover backfill is documented in [`docs/federation/sei-migration-playbook.md`](docs/federation/sei-migration-playbook.md) and surfaced for owner-gated scheduling. -- **Incremental analysis — Wave 2 / T3.1.** `clarion analyze` now skips files +- **Incremental analysis — Wave 2 / T3.1.** `loomweave analyze` now skips files whose whole-file hash matches the prior run, reusing their entities (the entity graph is cumulative and edges are insert-or-ignore, so the skip is speed-only). `skipped_files` is reported in `stats.json` and a `skipped_unchanged` progress @@ -182,18 +239,18 @@ only when an incompatible change is made to that surface. See orphaned, and skipped entries are re-fed into the prior-index rebuild so the snapshot does not decay. Files carrying a secret finding are never skipped (their finding anchor must stay stable). -- **Dossier participation surface — Wave 2 / WS4.** The exact Clarion HTTP slices +- **Dossier participation surface — Wave 2 / WS4.** The exact Loomweave HTTP slices the cross-tool dossier *assembler* (Wardline) reads are pinned in [`docs/federation/contracts.md`](docs/federation/contracts.md) and specified in - [`docs/superpowers/specs/2026-06-02-clarion-dossier-participation.md`](docs/superpowers/specs/2026-06-02-clarion-dossier-participation.md): + [`docs/superpowers/specs/2026-06-02-loomweave-dossier-participation.md`](docs/superpowers/specs/2026-06-02-loomweave-dossier-participation.md): identity (`resolve` → SEI + content-axis freshness; `resolve_sei`/`lineage` → identity-axis freshness), structural linkages (callers/callees), and file - context. Clarion contributes slices and the SEI join key; it does **not** proxy + context. Loomweave contributes slices and the SEI join key; it does **not** proxy Filigree associations (read directly from Filigree's own ADR-029 endpoint) or assemble the envelope. Proven end-to-end against a renamed-function fixture (`serve_http_dossier_participation_surface_serves_a_renamed_function`). - **`legis` governance consumption — Wave 3 / WS9 (governed paradise).** `legis` - consumes Clarion's stable identity as an **opt-in** governance layer a solo + consumes Loomweave's stable identity as an **opt-in** governance layer a solo project never sees; core paradise (Wave 2) does not depend on it. - **Git-rename provider seam (REQ-C-05).** A second `GitRenameSource`, `LegisGitRenameSource`, reads `legis`'s `GET /git/renames` over HTTP and feeds @@ -202,22 +259,22 @@ only when an incompatible change is made to that surface. See (`select_git_rename_source`, `--legis-url`) is enrich-only and capability-aware: the shell source remains the default and fallback; an unset/unreachable `legis` issues no HTTP and is byte-identical to before. The - two suppliers observe different rename windows (Clarion's `analyze` depends on + two suppliers observe different rename windows (Loomweave's `analyze` depends on the working-tree window; `legis` serves only committed rev-ranges), so the seam is built/tested/ready but inert in the default pipeline until `legis` - adds a working-tree surface or Clarion drives a committed re-index — a gap + adds a working-tree surface or Loomweave drives a committed re-index — a gap surfaced (not papered) in [`docs/federation/contracts.md`](docs/federation/contracts.md). The matcher is fail-closed regardless, so neither window can cause a false carry. Proven by `selector_keeps_working_tree_rename_even_when_a_reachable_legis_sees_nothing`. - - **Audit-spine consumption.** `legis` reads Clarion's existing + - **Audit-spine consumption.** `legis` reads Loomweave's existing `resolve`/`resolve_sei`/`lineage` routes as its governance audit spine; the consumption contract is pinned in `docs/federation/contracts.md`. Per REQ-L-01 (Option 3) `legis` owns integrity at its own boundary (snapshot-hash over - polled lineage) — Clarion ships **no** lineage hash-chain or signature. - - **No trust adjudication.** Clarion carries the trust vocabulary verbatim and + polled lineage) — Loomweave ships **no** lineage hash-chain or signature. + - **No trust adjudication.** Loomweave carries the trust vocabulary verbatim and adds no policy/attestation engine — Wardline analyses, `legis` governs, - attestations key on Clarion's SEI. + attestations key on Loomweave's SEI. ### Fixed @@ -233,15 +290,15 @@ only when an incompatible change is made to that surface. See ### Added -- **Wardline taint-fact store (ADR-036).** Clarion now serves as the persistent - read+write store for Wardline's taint facts over HTTP, keyed to Clarion entity +- **Wardline taint-fact store (ADR-036).** Loomweave now serves as the persistent + read+write store for Wardline's taint facts over HTTP, keyed to Loomweave entity qualnames. New migration `0003` adds the `wardline_taint_facts` table; facts are written through the storage writer-actor (`WriterCmd::UpsertWardlineTaintFact`) and resolved/fetched via the new `wardline_taint` storage module. Routes: - `POST /api/wardline/resolve` — exact-tier qualname → entity resolution. - `POST /api/wardline/taint-facts` — exact-only batch write; the Wardline - payload is stored byte-verbatim (`serde_json::value::RawValue`) so Clarion + payload is stored byte-verbatim (`serde_json::value::RawValue`) so Loomweave never reshapes Wardline's JSON. - `GET /api/wardline/taint-facts` and `…:batch-get` — reads carry a live freshness hash so a consumer can detect drift against the entity's current @@ -254,35 +311,35 @@ only when an incompatible change is made to that surface. See reconciled against Wardline findings stored in Filigree. A pure qualname-reconciliation module matches `metadata.wardline.qualname` byte-exact against entity-ID segment-3; a two-hop Filigree read - (`GET /api/loom/files` → `GET /api/loom/findings`) resolves a path to its + (`GET /api/weft/files` → `GET /api/weft/findings`) resolves a path to its findings. Per the enrich-only axiom, any unreachable hop degrades the section to `result_kind: "unavailable"` rather than failing the tool. -- **`clarion doctor [--fix]`.** A new subcommand that verifies — and with +- **`loomweave doctor [--fix]`.** A new subcommand that verifies — and with `--fix` repairs in place — the installed agent-orientation surfaces: the - `clarion-workflow` skill pack, the `SessionStart` hook in - `.claude/settings.json`, and the `clarion` entry in `.mcp.json` (which - `clarion install` does not register automatically). It prints a per-surface + `loomweave-workflow` skill pack, the `SessionStart` hook in + `.claude/settings.json`, and the `loomweave` entry in `.mcp.json` (which + `loomweave install` does not register automatically). It prints a per-surface ✓/✗ report plus the index snapshot and exits non-zero when any problem remains, so it is usable as a CI / pre-commit gate. Repairs reuse the same - idempotent installers as `clarion install`, and the `.mcp.json` merge is + idempotent installers as `loomweave install`, and the `.mcp.json` merge is never-clobber: sibling servers and a deliberately customised `command` are preserved (only the `--path` args are corrected). ### Changed - Federation contracts pinned/clarified: the Wardline taint-store routes + - freshness contract, the two consumed loom routes for Flow B, Clarion→Filigree + freshness contract, the two consumed weft routes for Flow B, Loomweave→Filigree ephemeral-port endpoint discovery, and the `scan_run_id` contract (stale Phase-0 handshake references removed). -- `docs/suite/loom.md`: added a written retirement condition to the §5 +- `docs/suite/weft.md`: added a written retirement condition to the §5 asterisk-2 (Wardline→Filigree pipeline coupling), and refreshed the §9 status table to the post-1.0 state. -- ADR-036 accepted (Clarion as Wardline taint-fact store), documenting the +- ADR-036 accepted (Loomweave as Wardline taint-fact store), documenting the in-process two-writer concurrency posture. - The Python round-trip self-test now hard-fails (instead of silently skipping) - when the installed `clarion-plugin-python` entry point is missing, so a broken + when the installed `loomweave-plugin-python` entry point is missing, so a broken editable install cannot pass CI green. -- **Shared error vocabulary (ADR-037).** New `clarion-core::errors` module is now +- **Shared error vocabulary (ADR-037).** New `loomweave-core::errors` module is now the single typed source of truth for both wire error-code vocabularies: `HttpErrorCode` (federation HTTP read API, `SCREAMING_SNAKE_CASE`, moved out of `http_read.rs`) and a new `McpErrorCode` (MCP tool envelope, kebab-case) that @@ -312,9 +369,9 @@ carries the full post-1.0.0 scope below plus the build fix. ### Added -- `clarion db backup ` — a consistent, WAL-safe online backup of - `.clarion/clarion.db` via `rusqlite::backup::Backup`. Safe to run during a - live `clarion analyze` (captures outstanding WAL frames into a standalone +- `loomweave db backup ` — a consistent, WAL-safe online backup of + `.loomweave/loomweave.db` via `rusqlite::backup::Backup`. Safe to run during a + live `loomweave analyze` (captures outstanding WAL frames into a standalone single-file copy, unlike `cp`), writes atomically (temp sibling + rename), refuses to clobber without `--force`, and verifies the copy with `PRAGMA integrity_check` before promoting it. Closes gap-register STO-04 @@ -329,25 +386,25 @@ carries the full post-1.0.0 scope below plus the build fix. - `call_sites(id, role=caller|callee, kind?, confidence?, path?)` MCP tool — shows the actual source sites behind `calls`/`references` edges (file, line, byte column, line text, edge kind, confidence) so an agent can see *why* - Clarion believes an edge exists. Statically-unbindable calls are returned in + Loomweave believes an edge exists. Statically-unbindable calls are returned in a separate `unresolved_sites` list, never mixed with resolved evidence. Filterable by edge kind and a documented best-effort production/test path heuristic. No LLM call. The MCP surface now exposes thirteen tools (clarion-9392f74881). -- **Filigree finding emission (WP9-B core, REQ-FINDING-03).** `clarion analyze` +- **Filigree finding emission (WP9-B core, REQ-FINDING-03).** `loomweave analyze` Phase 8 POSTs the run's persisted findings to Filigree's - `POST /api/v1/scan-results` intake, with Clarion's richer fields nested under - `metadata.clarion.*` (wire contract pinned in + `POST /api/v1/scan-results` intake, with Loomweave's richer fields nested under + `metadata.loomweave.*` (wire contract pinned in [`docs/federation/contracts.md`](docs/federation/contracts.md)). Emission is enrich-only — gated behind `integrations.filigree.{enabled,emit_findings}` (now **both default `false`**, so enabling Filigree for `issues_for` reads never silently starts outbound emission — clarion-a26de2f368), and any Filigree-side failure is recorded in `stats.json` - (`CLA-INFRA-FILIGREE-UNREACHABLE`) instead of failing the run. Findings + (`LMWV-INFRA-FILIGREE-UNREACHABLE`) instead of failing the run. Findings anchored to a `briefing_blocked` entity are excluded, matching the fail-closed read posture (clarion-8b32ba0d02). Resolves the 1.0 "finding emission deferred" limitation below. -- **`clarion analyze --resume RUN_ID` (WP9-B, REQ-FINDING-05).** Reopens a prior +- **`loomweave analyze --resume RUN_ID` (WP9-B, REQ-FINDING-05).** Reopens a prior run's `runs` row (a new `WriterCmd::ResumeRun` `UPDATE`s it back to `running` instead of `INSERT`ing, which conflicted on the existing run PK), re-walks the tree, and emits findings to Filigree with `mark_unseen=false` so the re-emit @@ -356,20 +413,20 @@ carries the full post-1.0.0 scope below plus the build fix. (previously only entities did), so a resumed run reproduces the same durable graph as the original. The emitted `mark_unseen` value is recorded in `stats.json`. Tracked under clarion-dd29e69e0e. -- **`clarion analyze --prune-unseen` (WP9-B, REQ-FINDING-06).** After emission - (Phase 8b), POSTs Filigree's `POST /api/loom/findings/clean-stale` retention - route, scoped to `scan_source=clarion`, asking it to soft-archive its own - `unseen_in_latest` Clarion findings older than +- **`loomweave analyze --prune-unseen` (WP9-B, REQ-FINDING-06).** After emission + (Phase 8b), POSTs Filigree's `POST /api/weft/findings/clean-stale` retention + route, scoped to `scan_source=loomweave`, asking it to soft-archive its own + `unseen_in_latest` Loomweave findings older than `integrations.filigree.prune_unseen_days` (default 30). Soft-archive, not delete: Filigree moves them to `fixed` and auto-reopens on reappearance (Filigree ADR-015). Enrich-only — a Filigree outage or the integration being disabled is recorded in `stats.json` (`filigree_prune`) and never fails the run; the `scan_source` scoping is enforced server-side so the sweep can only - touch Clarion's findings. (Closes the REQ-FINDING-06 piece of + touch Loomweave's findings. (Closes the REQ-FINDING-06 piece of clarion-dd29e69e0e; the route was found to already exist in Filigree, so the earlier "file a request" memo is superseded — see its withdrawal banner.) The remaining Phase-0 scan-run-create handshake is an open contract question with - Filigree (Clarion relies on the peer tolerating an unknown `scan_run_id`), + Filigree (Loomweave relies on the peer tolerating an unknown `scan_run_id`), tracked separately under `release:1.1`. ### Changed @@ -384,7 +441,7 @@ carries the full post-1.0.0 scope below plus the build fix. ADR-024 (no published build). Closes gap-register STO-04 / V11-STO-04 (clarion-bdabfd6bca). - The writer actor now opens its batch transactions with `BEGIN IMMEDIATE` - (via a new `clarion_storage::retry::begin_immediate` helper with bounded + (via a new `loomweave_storage::retry::begin_immediate` helper with bounded `SQLITE_BUSY`/`SQLITE_LOCKED` retry + exponential backoff) instead of a deferred `BEGIN`. Taking the write lock up front resolves cross-process write contention at lock-acquire — where `busy_timeout` is honored — rather @@ -394,8 +451,8 @@ carries the full post-1.0.0 scope below plus the build fix. ### Fixed - **macOS release build under `-D warnings`.** The Linux-only `prlimit` - helpers imported/defined in `clarion-core::plugin::host` (and two Linux-only - test sites in `clarion-mcp` and `clarion-cli`) were unused on + helpers imported/defined in `loomweave-core::plugin::host` (and two Linux-only + test sites in `loomweave-mcp` and `loomweave-cli`) were unused on `*-apple-darwin`, so the release build failed with unused-import / dead-code errors. They are now `cfg`-gated to `target_os = "linux"` (plus `test` where unit tests reference them). CI gained a native macOS (aarch64) build + clippy @@ -404,9 +461,9 @@ carries the full post-1.0.0 scope below plus the build fix. ## [1.0.0] — 2026-05-19 -First publishable release. Clarion ships as a Rust core (`clarion` binary, five +First publishable release. Loomweave ships as a Rust core (`loomweave` binary, five workspace crates) plus an editable-install Python language plugin -(`clarion-plugin-python`). Released under the [MIT License](LICENSE). +(`loomweave-plugin-python`). Released under the [MIT License](LICENSE). Targets the `v1.0.0` tag (cut by the operator once all release blockers are green); supersedes the pre-release `v0.1-sprint-1` and `v0.1-sprint-2` @@ -414,16 +471,16 @@ working tags, which remain in the repo as historical anchors. ### Core -- `clarion install --path` initialises a project's `.clarion/` directory +- `loomweave install --path` initialises a project's `.loomweave/` directory (instance ID, SQLite DB, migrations). -- `clarion analyze` walks a Python corpus and persists the structural graph +- `loomweave analyze` walks a Python corpus and persists the structural graph (entities + `contains`, `calls`, `references`, `imports` edges) to a local SQLite store via the writer-actor pattern (ADR-011). -- `clarion serve` exposes the MCP stdio surface for consult-mode agents: +- `loomweave serve` exposes the MCP stdio surface for consult-mode agents: `entity_at`, `find_entity`, `callers_of`, `execution_paths_from`, `summary`, `issues_for`, `neighborhood`, `subsystem_members`. -### Python plugin (`clarion-plugin-python` 1.0.0) +### Python plugin (`loomweave-plugin-python` 1.0.0) - Pyright-backed entity extraction for functions, classes, and modules; resolved / ambiguous / inferred call edges per ADR-022. @@ -434,8 +491,8 @@ working tags, which remain in the repo as historical anchors. ### Federation HTTP read API (ADR-014) -The publisher-side of Clarion's federation contract with Filigree's -`ClarionRegistry`. Pinned in [`docs/federation/contracts.md`](docs/federation/contracts.md); +The publisher-side of Loomweave's federation contract with Filigree's +`LoomweaveRegistry`. Pinned in [`docs/federation/contracts.md`](docs/federation/contracts.md); fixtures under [`docs/federation/fixtures/`](docs/federation/fixtures/) are normative. @@ -450,10 +507,10 @@ normative. request, single pooled `ReaderPool` checkout per batch. Four-way partitioning: `resolved` / `not_found` / `briefing_blocked` / `errors`. - **Bearer authentication.** `serve.http.token_env` (default - `CLARION_LOOM_TOKEN`) names the env var holding the inbound bearer + `WEFT_TOKEN`) names the env var holding the inbound bearer token. Loopback-without-token stays unauthenticated for the v0.1 trust model; non-loopback-without-token is refused at startup with - `CLA-CONFIG-HTTP-NO-AUTH`. + `LMWV-CONFIG-HTTP-NO-AUTH`. - **Briefing-blocked propagation.** Files flagged by the pre-ingest secret scanner return `403 BRIEFING_BLOCKED` on the single-file endpoint and surface in the `briefing_blocked[]` partition on the batch endpoint. @@ -493,7 +550,7 @@ normative. - **Python only.** Other-language plugins (`NG-15`) are v2.0+ scope. - **Filigree finding emission deferred to a future release (tracked under - `release:v1.1`).** Cross-product POSTing of Clarion-generated findings into + `release:v1.1`).** Cross-product POSTing of Loomweave-generated findings into Filigree's intake (WP9-B) is deferred per the [Sprint 2 scope amendment](docs/implementation/sprint-2/scope-amendment-2026-05.md). `issues_for(id)` (the WP9-A binding for reading from Filigree) ships in 1.0. *(WP9-B core shipped post-1.0 — see the Filigree-finding-emission entry under @@ -502,11 +559,11 @@ normative. available, with a narrow core-extension fallback for files that predate manifest capture. - **Cooperative HMAC inbound auth** ships for the HTTP read API via - `serve.http.identity_token_env` and `X-Loom-Component: clarion:`. + `serve.http.identity_token_env` and `X-Weft-Component: loomweave:`. The older bearer-token path remains available for compatibility. - **Python plugin imports `wardline.core.registry.REGISTRY` at startup** - (loom.md §5 asterisk 2). Initialization coupling scoped to the - Wardline-aware plugin only; Clarion core and non-Wardline-aware plugins are + (weft.md §5 asterisk 2). Initialization coupling scoped to the + Wardline-aware plugin only; Loomweave core and non-Wardline-aware plugins are unaffected. *Retirement condition*: Wardline ships a stable runtime probe API. - **Wardline state-file ingest deferred to a future release (tracked under @@ -518,33 +575,33 @@ normative. with WP9-B / WP10. - **Pre-WP5 catalogue upgrade requirement.** Briefing-blocked annotations are stored as a JSON property on file entities at v1.0 (v1.1 promotes - the field to a typed column). A v1.0 binary opening a `.clarion/clarion.db` + the field to a typed column). A v1.0 binary opening a `.loomweave/loomweave.db` produced by a pre-WP5 binary finds no `briefing_blocked` properties — pre-WP5 analyzers never wrote them — and will serve the entire catalogue without refusal. Operators upgrading from a pre-WP5 install MUST run - `clarion analyze` (scanner active by default) against the project root + `loomweave analyze` (scanner active by default) against the project root before exposing the HTTP read API or calling the `summary` MCP tool. See [`docs/operator/secret-scanning.md`](docs/operator/secret-scanning.md#pre-wp5-catalogue-upgrade-requirement). ### Documentation -- Design ladder under [`docs/clarion/1.0/`](docs/clarion/1.0/) — `requirements.md`, +- Design ladder under [`docs/loomweave/1.0/`](docs/loomweave/1.0/) — `requirements.md`, `system-design.md`, `detailed-design.md`. -- ADRs under [`docs/clarion/adr/`](docs/clarion/adr/) — 28 Accepted at 1.0 +- ADRs under [`docs/loomweave/adr/`](docs/loomweave/adr/) — 28 Accepted at 1.0 (ADR-001…ADR-034 with the documented Backlog/Superseded subset excluded). ADR-012 is superseded by ADR-014, whose Security Posture and Error Envelope are in turn partially extended by ADR-034 for the Sprint 3 federation hardening. Four ADRs (ADR-009, ADR-010, ADR-019, ADR-020) remain Backlog and are tracked inside `system-design.md` §12 / `detailed-design.md` §11 until promoted. -- Loom-suite doctrine at [`docs/suite/loom.md`](docs/suite/loom.md). +- Weft-suite doctrine at [`docs/suite/weft.md`](docs/suite/weft.md). - Federation contract surface at [`docs/federation/contracts.md`](docs/federation/contracts.md). - Operator guides under [`docs/operator/`](docs/operator/) — getting-started, OpenRouter setup, HTTP read API. -[Unreleased]: https://github.com/tachyon-beep/clarion/compare/v1.3.0...HEAD -[1.3.0]: https://github.com/tachyon-beep/clarion/compare/v1.2.0...v1.3.0 -[1.2.0]: https://github.com/tachyon-beep/clarion/compare/v1.1.0...v1.2.0 -[1.1.0]: https://github.com/tachyon-beep/clarion/compare/v1.0.1...v1.1.0 -[1.0.1]: https://github.com/tachyon-beep/clarion/compare/v1.0.0...v1.0.1 -[1.0.0]: https://github.com/tachyon-beep/clarion/releases/tag/v1.0.0 +[Unreleased]: https://github.com/foundryside-dev/loomweave/compare/v1.3.0...HEAD +[1.3.0]: https://github.com/foundryside-dev/loomweave/compare/v1.2.0...v1.3.0 +[1.2.0]: https://github.com/foundryside-dev/loomweave/compare/v1.1.0...v1.2.0 +[1.1.0]: https://github.com/foundryside-dev/loomweave/compare/v1.0.1...v1.1.0 +[1.0.1]: https://github.com/foundryside-dev/loomweave/compare/v1.0.0...v1.0.1 +[1.0.0]: https://github.com/foundryside-dev/loomweave/releases/tag/v1.0.0 diff --git a/Cargo.lock b/Cargo.lock index f7aa7efe..508f3d12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -93,9 +93,9 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "assert_cmd" -version = "2.2.1" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39bae1d3fa576f7c6519514180a72559268dd7d1fe104070956cb687bc6673bd" +checksum = "2aa3a22042e45de04255c7bf3626e239f450200fd0493c1e382263544b20aea6" dependencies = [ "anstyle", "bstr", @@ -125,9 +125,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "axum" @@ -192,9 +192,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" -version = "2.11.1" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +checksum = "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a" [[package]] name = "blake3" @@ -232,9 +232,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bytes" @@ -244,9 +244,9 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cc" -version = "1.2.60" +version = "1.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" dependencies = [ "find-msvc-tools", "shlex", @@ -310,146 +310,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" -[[package]] -name = "clarion-analysis" -version = "1.3.0" -dependencies = [ - "anyhow", - "serde", - "sha2", - "xgraph", -] - -[[package]] -name = "clarion-cli" -version = "1.3.0" -dependencies = [ - "anyhow", - "assert_cmd", - "axum", - "blake3", - "clap", - "clarion-analysis", - "clarion-core", - "clarion-federation", - "clarion-mcp", - "clarion-plugin-fixture", - "clarion-scanner", - "clarion-storage", - "dotenvy", - "fs2", - "hmac", - "ignore", - "reqwest", - "rusqlite", - "serde", - "serde_json", - "serde_norway", - "sha1", - "sha2", - "subtle", - "tempfile", - "time", - "tokio", - "toml", - "tower", - "tower-http", - "tracing", - "tracing-subscriber", - "uuid", -] - -[[package]] -name = "clarion-core" -version = "1.3.0" -dependencies = [ - "async-trait", - "nix", - "reqwest", - "serde", - "serde_json", - "tempfile", - "thiserror 1.0.69", - "tokio", - "toml", - "tracing", - "which", -] - -[[package]] -name = "clarion-federation" -version = "1.3.0" -dependencies = [ - "clarion-core", - "reqwest", - "serde", - "serde_json", - "serde_norway", - "tempfile", - "thiserror 1.0.69", -] - -[[package]] -name = "clarion-mcp" -version = "1.3.0" -dependencies = [ - "async-trait", - "blake3", - "clarion-core", - "clarion-federation", - "clarion-storage", - "nix", - "reqwest", - "rusqlite", - "serde", - "serde_json", - "serde_norway", - "tempfile", - "thiserror 1.0.69", - "time", - "tokio", - "tracing", - "uuid", -] - -[[package]] -name = "clarion-plugin-fixture" -version = "1.3.0" -dependencies = [ - "clarion-core", - "nix", - "serde_json", -] - -[[package]] -name = "clarion-scanner" -version = "1.3.0" -dependencies = [ - "regex", - "serde", - "serde_norway", - "sha1", - "tempfile", - "thiserror 1.0.69", -] - -[[package]] -name = "clarion-storage" -version = "1.3.0" -dependencies = [ - "blake3", - "clarion-core", - "deadpool-sqlite", - "rusqlite", - "serde", - "serde_json", - "tempfile", - "thiserror 1.0.69", - "time", - "tokio", - "tracing", -] - [[package]] name = "colorchoice" version = "1.0.5" @@ -600,9 +460,9 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", @@ -617,9 +477,9 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "equivalent" @@ -827,9 +687,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.17.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "hashlink" @@ -872,9 +732,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" dependencies = [ "bytes", "itoa", @@ -917,9 +777,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" dependencies = [ "atomic-waker", "bytes", @@ -977,12 +837,13 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -990,9 +851,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -1003,9 +864,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -1017,15 +878,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", @@ -1037,15 +898,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", @@ -1075,9 +936,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", @@ -1085,9 +946,9 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.25" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" +checksum = "b915661dd01db3f05050265b2477bcc6527b3792388e2749b41623cc592be67d" dependencies = [ "crossbeam-deque", "globset", @@ -1106,7 +967,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.17.0", + "hashbrown 0.17.1", "serde", "serde_core", ] @@ -1131,9 +992,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" -version = "0.3.95" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" dependencies = [ "cfg-if", "futures-util", @@ -1155,9 +1016,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.185" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libsqlite3-sys" @@ -1190,9 +1051,149 @@ checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "log" -version = "0.4.29" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" + +[[package]] +name = "loomweave-analysis" +version = "1.0.0" +dependencies = [ + "anyhow", + "serde", + "sha2", + "xgraph", +] + +[[package]] +name = "loomweave-cli" +version = "1.0.0" +dependencies = [ + "anyhow", + "assert_cmd", + "axum", + "blake3", + "clap", + "dotenvy", + "fs2", + "hmac", + "ignore", + "loomweave-analysis", + "loomweave-core", + "loomweave-federation", + "loomweave-mcp", + "loomweave-plugin-fixture", + "loomweave-scanner", + "loomweave-storage", + "reqwest", + "rusqlite", + "serde", + "serde_json", + "serde_norway", + "sha1", + "sha2", + "subtle", + "tempfile", + "time", + "tokio", + "toml", + "tower", + "tower-http", + "tracing", + "tracing-subscriber", + "uuid", +] + +[[package]] +name = "loomweave-core" +version = "1.0.0" +dependencies = [ + "async-trait", + "nix", + "reqwest", + "serde", + "serde_json", + "tempfile", + "thiserror 1.0.69", + "tokio", + "toml", + "tracing", + "which", +] + +[[package]] +name = "loomweave-federation" +version = "1.0.0" +dependencies = [ + "loomweave-core", + "reqwest", + "serde", + "serde_json", + "serde_norway", + "tempfile", + "thiserror 1.0.69", +] + +[[package]] +name = "loomweave-mcp" +version = "1.0.0" +dependencies = [ + "async-trait", + "blake3", + "loomweave-core", + "loomweave-federation", + "loomweave-storage", + "nix", + "reqwest", + "rusqlite", + "serde", + "serde_json", + "serde_norway", + "tempfile", + "thiserror 1.0.69", + "time", + "tokio", + "tracing", + "uuid", +] + +[[package]] +name = "loomweave-plugin-fixture" +version = "1.0.0" +dependencies = [ + "loomweave-core", + "nix", + "serde_json", +] + +[[package]] +name = "loomweave-scanner" +version = "1.0.0" +dependencies = [ + "regex", + "serde", + "serde_norway", + "sha1", + "tempfile", + "thiserror 1.0.69", +] + +[[package]] +name = "loomweave-storage" +version = "1.0.0" +dependencies = [ + "blake3", + "deadpool-sqlite", + "loomweave-core", + "rusqlite", + "serde", + "serde_json", + "tempfile", + "thiserror 1.0.69", + "time", + "tokio", + "tracing", +] [[package]] name = "lru-slab" @@ -1217,9 +1218,9 @@ checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" [[package]] name = "mime" @@ -1229,9 +1230,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mio" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" dependencies = [ "libc", "wasi", @@ -1261,9 +1262,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-traits" @@ -1608,7 +1609,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1640,9 +1641,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" dependencies = [ "openssl-probe", "rustls-pki-types", @@ -1762,9 +1763,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", @@ -1851,9 +1852,9 @@ dependencies = [ [[package]] name = "shlex" -version = "1.3.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "slab" @@ -1869,9 +1870,9 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", "windows-sys 0.61.2", @@ -2052,9 +2053,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.52.1" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", @@ -2159,9 +2160,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.10" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68d6fdd9f81c2819c9a8b0e0cd91660e7746a8e6ea2ba7c6b2b057985f6bcb51" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ "bitflags", "bytes", @@ -2259,9 +2260,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" -version = "1.20.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "unicode-ident" @@ -2313,9 +2314,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.1" +version = "1.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -2376,11 +2377,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen 0.46.0", + "wit-bindgen 0.57.1", ] [[package]] @@ -2394,9 +2395,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.118" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" dependencies = [ "cfg-if", "once_cell", @@ -2407,9 +2408,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.68" +version = "0.4.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" +checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" dependencies = [ "js-sys", "wasm-bindgen", @@ -2417,9 +2418,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.118" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2427,9 +2428,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.118" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" dependencies = [ "bumpalo", "proc-macro2", @@ -2440,9 +2441,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.118" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" dependencies = [ "unicode-ident", ] @@ -2483,9 +2484,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.95" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" +checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" dependencies = [ "js-sys", "wasm-bindgen", @@ -2559,6 +2560,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.60.2" @@ -2721,12 +2731,6 @@ version = "0.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" -[[package]] -name = "wit-bindgen" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" - [[package]] name = "wit-bindgen" version = "0.51.0" @@ -2736,6 +2740,12 @@ dependencies = [ "wit-bindgen-rust-macro", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "wit-bindgen-core" version = "0.51.0" @@ -2834,9 +2844,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -2857,18 +2867,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 5198071a..ed0e35e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,21 +1,21 @@ [workspace] resolver = "3" members = [ - "crates/clarion-analysis", - "crates/clarion-core", - "crates/clarion-federation", - "crates/clarion-storage", - "crates/clarion-cli", - "crates/clarion-mcp", - "crates/clarion-plugin-fixture", - "crates/clarion-scanner", + "crates/loomweave-analysis", + "crates/loomweave-core", + "crates/loomweave-federation", + "crates/loomweave-storage", + "crates/loomweave-cli", + "crates/loomweave-mcp", + "crates/loomweave-plugin-fixture", + "crates/loomweave-scanner", ] [workspace.package] -version = "1.3.0" +version = "1.0.0" edition = "2024" license = "MIT" -repository = "https://github.com/tachyon-beep/clarion" +repository = "https://github.com/foundryside-dev/loomweave" rust-version = "1.88" [workspace.lints.rust] diff --git a/README.md b/README.md index 0648e3ab..ea4f44c8 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,27 @@ -# Clarion +# Loomweave -Clarion is a code-archaeology tool. It ingests a codebase, extracts entities +Loomweave is a code-archaeology tool. It ingests a codebase, extracts entities (functions, classes, modules) and their relationships (`contains`, `calls`, `references`), persists the structural graph to a local SQLite store, and serves the result to consult-mode LLM agents over MCP. A coding agent that would -otherwise re-explore the tree on every question reaches Clarion first and asks a +otherwise re-explore the tree on every question reaches Loomweave first and asks a graph-aware tool. The current release line ships a Rust core plus a Python language plugin; other languages remain future scope. -Part of the [Loom suite](docs/suite/loom.md) of code-archaeology, issue-tracking, +Part of the [Weft suite](docs/suite/weft.md) of code-archaeology, issue-tracking, and trust-topology tools. ## Status -**v1.3.0 — current release line.** Scope: +**v1.0.0 — current release line.** Scope: - **Python only.** Other-language plugins (`NG-15`) are v2.0+ scope. -- **Structural extraction + on-demand LLM summarisation.** `clarion analyze` +- **Structural extraction + on-demand LLM summarisation.** `loomweave analyze` walks the corpus and persists entities + edges; `summary(id)` over MCP dispatches the LLM lazily, one entity at a time. - **Local-first.** No mandatory cloud component; the only required network egress is the LLM provider during `summary` calls. -- **Stable identity and suite enrichment.** Clarion mints Stable Entity +- **Stable identity and suite enrichment.** Loomweave mints Stable Entity Identity (SEI) tokens, serves the federation HTTP read API, emits opted-in Filigree scan findings (issue lookups now key by SEI), and enriches MCP reads with Filigree/Wardline context without making sibling products mandatory. @@ -29,17 +29,17 @@ and trust-topology tools. Wardline's NG-25 trust-vocabulary descriptor as a plain file and tags trust-decorated entities (`wardline:*`) — without importing Wardline, so a co-installed Wardline is not required. Degrades cleanly when the descriptor is - absent. (Retires the last Clarion-side federation asterisk; see - [`docs/suite/loom.md`](docs/suite/loom.md) §5.) + absent. (Retires the last Loomweave-side federation asterisk; see + [`docs/suite/weft.md`](docs/suite/weft.md) §5.) - **Guidance authoring.** Operators can author, import, export, and review - guidance sheets through `clarion guidance`; consult agents consume them + guidance sheets through `loomweave guidance`; consult agents consume them through MCP and summary cache invalidation. **Known limitations:** - **HTTP file language inference uses stored plugin identity plus a narrow core-extension fallback.** Plugin manifests declare language and extensions, - but Clarion does not yet persist a manifest language registry for the + but Loomweave does not yet persist a manifest language registry for the `/api/v1/files` read path. - **Some guidance lifecycle surfaces remain deferred.** The in-browser staleness-review UI is still tracked separately; authored guidance is @@ -47,7 +47,7 @@ and trust-topology tools. ## What it does today -`clarion serve` exposes a 39-tool MCP surface that a consult-mode agent calls +`loomweave serve` exposes a 39-tool MCP surface that a consult-mode agent calls instead of grep-and-read. The core tool families are: | Family | Examples | @@ -62,32 +62,32 @@ instead of grep-and-read. The core tool families are: ```bash # 1. Install from the current GitHub Release -TAG=v1.3.0 -curl -L -o clarion-x86_64-unknown-linux-gnu.tar.gz \ - "https://github.com/tachyon-beep/clarion/releases/download/${TAG}/clarion-x86_64-unknown-linux-gnu.tar.gz" -tar xzf clarion-x86_64-unknown-linux-gnu.tar.gz -install clarion-x86_64-unknown-linux-gnu/clarion ~/.local/bin/ +TAG=v1.0.0 +curl -L -o loomweave-x86_64-unknown-linux-gnu.tar.gz \ + "https://github.com/foundryside-dev/loomweave/releases/download/${TAG}/loomweave-x86_64-unknown-linux-gnu.tar.gz" +tar xzf loomweave-x86_64-unknown-linux-gnu.tar.gz +install loomweave-x86_64-unknown-linux-gnu/loomweave ~/.local/bin/ pipx install \ - "https://github.com/tachyon-beep/clarion/releases/download/${TAG}/clarion-plugin-python-1.3.0.tar.gz" + "https://github.com/foundryside-dev/loomweave/releases/download/${TAG}/loomweave-plugin-python-1.0.0.tar.gz" # 2. Initialise a project cd /path/to/your/python/repo -clarion install --path . +loomweave install --path . # 3. Walk the corpus and persist the structural graph -clarion analyze +loomweave analyze # 4. Serve the graph over MCP for consult-mode agents -clarion serve +loomweave serve ``` -`clarion install` is the one-step agent setup path: it initialises `.clarion/`, -installs the `clarion-workflow` skill for Claude Code and Codex, writes Claude +`loomweave install` is the one-step agent setup path: it initialises `.loomweave/`, +installs the `loomweave-workflow` skill for Claude Code and Codex, writes Claude Code MCP config, upserts Codex MCP config, and installs the SessionStart hook. Use component flags such as `--claude-code`, `--codex`, `--skills`, `--codex-skills`, and `--hooks` for partial installs. -`clarion analyze` works without any LLM credentials and is the fastest way to +`loomweave analyze` works without any LLM credentials and is the fastest way to verify the install. `summary(id)` calls require `OPENROUTER_API_KEY` to be set (see [docs/operator/openrouter.md](docs/operator/openrouter.md)). @@ -99,37 +99,37 @@ in [docs/operator/getting-started.md](docs/operator/getting-started.md). ``` crates/ Rust workspace -├── clarion-core/ Entity-ID assembler, plugin host, manifest parser -├── clarion-storage/ Writer-actor + reader-pool over SQLite (ADR-011) -├── clarion-federation/ Shared federation HTTP types -├── clarion-scanner/ Pre-ingest secret scanner (ADR-013, WP5) -├── clarion-cli/ The `clarion` binary (install, analyze, serve) -└── clarion-mcp/ MCP server exposing the consult tools +├── loomweave-core/ Entity-ID assembler, plugin host, manifest parser +├── loomweave-storage/ Writer-actor + reader-pool over SQLite (ADR-011) +├── loomweave-federation/ Shared federation HTTP types +├── loomweave-scanner/ Pre-ingest secret scanner (ADR-013, WP5) +├── loomweave-cli/ The `loomweave` binary (install, analyze, serve) +└── loomweave-mcp/ MCP server exposing the consult tools plugins/python/ Python language plugin (pyright-backed) -docs/clarion/1.0/ Design ladder — requirements → system-design → detailed-design -docs/clarion/adr/ Authored architecture decision records +docs/loomweave/1.0/ Design ladder — requirements → system-design → detailed-design +docs/loomweave/adr/ Authored architecture decision records ``` For the design ladder start at -[docs/clarion/1.0/README.md](docs/clarion/1.0/README.md). The full ADR index -is at [docs/clarion/adr/README.md](docs/clarion/adr/README.md). The Loom +[docs/loomweave/1.0/README.md](docs/loomweave/1.0/README.md). The full ADR index +is at [docs/loomweave/adr/README.md](docs/loomweave/adr/README.md). The Weft federation doctrine that anchors every cross-product decision is in -[docs/suite/loom.md](docs/suite/loom.md). +[docs/suite/weft.md](docs/suite/weft.md). ## Storage and operations -Clarion keeps project state in a local `.clarion/` directory. +Loomweave keeps project state in a local `.loomweave/` directory. The local-first storage model, the no-NFS constraint, the no-double-analyze constraint (fs2 advisory lock), and the backup/restore procedure are documented in -[docs/clarion/1.0/operations.md](docs/clarion/1.0/operations.md). +[docs/loomweave/1.0/operations.md](docs/loomweave/1.0/operations.md). ## Contributing -Read the [v1.0 docset README](docs/clarion/1.0/README.md) for the canonical +Read the [v1.0 docset README](docs/loomweave/1.0/README.md) for the canonical design ladder, its reading order, and where canonical truth lives. The CI floor every PR must clear is fixed by -[ADR-023](docs/clarion/adr/ADR-023-tooling-baseline.md): +[ADR-023](docs/loomweave/adr/ADR-023-tooling-baseline.md): ```bash # Rust gates diff --git a/crates/clarion-mcp/src/config.rs b/crates/clarion-mcp/src/config.rs deleted file mode 100644 index 1d7d4549..00000000 --- a/crates/clarion-mcp/src/config.rs +++ /dev/null @@ -1 +0,0 @@ -pub use clarion_federation::config::*; diff --git a/crates/clarion-mcp/src/filigree.rs b/crates/clarion-mcp/src/filigree.rs deleted file mode 100644 index 234d9309..00000000 --- a/crates/clarion-mcp/src/filigree.rs +++ /dev/null @@ -1 +0,0 @@ -pub use clarion_federation::filigree::*; diff --git a/crates/clarion-mcp/src/filigree_url.rs b/crates/clarion-mcp/src/filigree_url.rs deleted file mode 100644 index 5fd6e18b..00000000 --- a/crates/clarion-mcp/src/filigree_url.rs +++ /dev/null @@ -1 +0,0 @@ -pub use clarion_federation::filigree_url::*; diff --git a/crates/clarion-mcp/src/scan_results.rs b/crates/clarion-mcp/src/scan_results.rs deleted file mode 100644 index d77288b0..00000000 --- a/crates/clarion-mcp/src/scan_results.rs +++ /dev/null @@ -1 +0,0 @@ -pub use clarion_federation::scan_results::*; diff --git a/crates/clarion-plugin-fixture/Cargo.toml b/crates/clarion-plugin-fixture/Cargo.toml deleted file mode 100644 index 6f89a0a9..00000000 --- a/crates/clarion-plugin-fixture/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "clarion-plugin-fixture" -version.workspace = true -edition.workspace = true -license.workspace = true -repository.workspace = true -rust-version.workspace = true - -[lints] -workspace = true - -[[bin]] -name = "clarion-plugin-fixture" -path = "src/main.rs" - -[dependencies] -clarion-core = { path = "../clarion-core", version = "1.3.0" } -serde_json.workspace = true - -[target.'cfg(unix)'.dependencies] -nix = { workspace = true, features = ["mman", "signal"] } diff --git a/crates/clarion-analysis/Cargo.toml b/crates/loomweave-analysis/Cargo.toml similarity index 90% rename from crates/clarion-analysis/Cargo.toml rename to crates/loomweave-analysis/Cargo.toml index 3f3372ed..95e02970 100644 --- a/crates/clarion-analysis/Cargo.toml +++ b/crates/loomweave-analysis/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "clarion-analysis" +name = "loomweave-analysis" version.workspace = true edition.workspace = true license.workspace = true diff --git a/crates/clarion-analysis/src/lib.rs b/crates/loomweave-analysis/src/lib.rs similarity index 100% rename from crates/clarion-analysis/src/lib.rs rename to crates/loomweave-analysis/src/lib.rs diff --git a/crates/clarion-cli/Cargo.toml b/crates/loomweave-cli/Cargo.toml similarity index 62% rename from crates/clarion-cli/Cargo.toml rename to crates/loomweave-cli/Cargo.toml index 1f7c8867..edd5d37b 100644 --- a/crates/clarion-cli/Cargo.toml +++ b/crates/loomweave-cli/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "clarion-cli" +name = "loomweave-cli" version.workspace = true edition.workspace = true license.workspace = true @@ -10,7 +10,7 @@ rust-version.workspace = true workspace = true [[bin]] -name = "clarion" +name = "loomweave" path = "src/main.rs" [dependencies] @@ -18,12 +18,12 @@ anyhow.workspace = true axum.workspace = true blake3.workspace = true clap.workspace = true -clarion-core = { path = "../clarion-core", version = "1.3.0" } -clarion-analysis = { path = "../clarion-analysis", version = "1.3.0" } -clarion-federation = { path = "../clarion-federation", version = "1.3.0" } -clarion-mcp = { path = "../clarion-mcp", version = "1.3.0" } -clarion-scanner = { path = "../clarion-scanner", version = "1.3.0" } -clarion-storage = { path = "../clarion-storage", version = "1.3.0" } +loomweave-core = { path = "../loomweave-core", version = "1.0.0" } +loomweave-analysis = { path = "../loomweave-analysis", version = "1.0.0" } +loomweave-federation = { path = "../loomweave-federation", version = "1.0.0" } +loomweave-mcp = { path = "../loomweave-mcp", version = "1.0.0" } +loomweave-scanner = { path = "../loomweave-scanner", version = "1.0.0" } +loomweave-storage = { path = "../loomweave-storage", version = "1.0.0" } dotenvy.workspace = true fs2.workspace = true hmac.workspace = true @@ -46,7 +46,7 @@ uuid.workspace = true [dev-dependencies] assert_cmd.workspace = true -clarion-plugin-fixture = { path = "../clarion-plugin-fixture", version = "1.3.0" } +loomweave-plugin-fixture = { path = "../loomweave-plugin-fixture", version = "1.0.0" } rusqlite.workspace = true serde_json.workspace = true sha1.workspace = true diff --git a/crates/loomweave-cli/pyproject.toml b/crates/loomweave-cli/pyproject.toml new file mode 100644 index 00000000..9df6faef --- /dev/null +++ b/crates/loomweave-cli/pyproject.toml @@ -0,0 +1,27 @@ +[build-system] +requires = ["maturin>=1.7,<2"] +build-backend = "maturin" + +[project] +name = "loomweave" +version = "1.0.0" +description = "Loomweave — graph-aware code archaeology (Rust core)" +readme = "../../README.md" +requires-python = ">=3.11" +license = { text = "MIT" } +authors = [{ name = "John Morrissey", email = "qacona@gmail.com" }] +classifiers = [ + "Development Status :: 4 - Beta", + "Programming Language :: Rust", + "Programming Language :: Python :: 3", +] +dependencies = ["loomweave-plugin-python==1.0.0"] + +[project.urls] +Repository = "https://github.com/foundryside-dev/loomweave" + +[tool.maturin] +bindings = "bin" +manifest-path = "Cargo.toml" +bins = ["loomweave"] +strip = true diff --git a/crates/clarion-cli/src/analyze.rs b/crates/loomweave-cli/src/analyze.rs similarity index 96% rename from crates/clarion-cli/src/analyze.rs rename to crates/loomweave-cli/src/analyze.rs index 4f3fd9cc..4e738f2b 100644 --- a/crates/clarion-cli/src/analyze.rs +++ b/crates/loomweave-cli/src/analyze.rs @@ -1,4 +1,4 @@ -//! `clarion analyze` — discover plugins, walk the source tree, persist entities. +//! `loomweave analyze` — discover plugins, walk the source tree, persist entities. //! //! WP2 Task 8 replaces the Sprint-1 stub with real plugin orchestration: //! - Discover plugins via L9 `$PATH` convention (Task 5). @@ -21,12 +21,12 @@ use rusqlite::Connection; use time::{OffsetDateTime, macros::format_description}; use uuid::Uuid; -use clarion_core::{ +use loomweave_core::{ AcceptedEdge, AcceptedEntity, AnalyzeFileOutcome, CrashLoopBreaker, CrashLoopState, DiscoveredPlugin, EmbeddingProvider, FINDING_DISABLED_CRASH_LOOP, HostError, HostFinding, UnresolvedCallSite, discover, }; -use clarion_storage::{ +use loomweave_storage::{ DEFAULT_BATCH_SIZE, DEFAULT_CHANNEL_CAPACITY, EmbeddingKey, EmbeddingStore, GitRename, NewEntityDescriptor, PriorIndexEntry, SeiBindingRecord, SeiDecision, SeiLineageEntry, UnresolvedCallSiteRecord, Writer, alive_bindings_snapshot, @@ -35,30 +35,30 @@ use clarion_storage::{ sei::{BindingStatus, LineageEvent}, }; -use clarion_federation::config::{FiligreeConfig, McpConfig, SemanticSearchConfig}; -use clarion_federation::filigree::FiligreeHttpClient; -use clarion_federation::filigree_url::resolve_filigree_url; -use clarion_federation::scan_results::{ - CLARION_SCAN_SOURCE, CleanStaleRequest, CleanStaleResponse, EmitOptions, FindingForEmit, +use loomweave_federation::config::{FiligreeConfig, McpConfig, SemanticSearchConfig}; +use loomweave_federation::filigree::FiligreeHttpClient; +use loomweave_federation::filigree_url::resolve_filigree_url; +use loomweave_federation::scan_results::{ + CleanStaleRequest, CleanStaleResponse, EmitOptions, FindingForEmit, LOOMWEAVE_SCAN_SOURCE, PreparedBatch, ScanResultsResponse, clean_stale_url, prepare_batch, scan_results_url, }; use crate::config::{AnalyzeConfig, ClusteringConfig}; use crate::stats::P95Accumulator; -use clarion_analysis::{ +use loomweave_analysis::{ ClusterAlgorithm, ClusterConfig, ModuleEdge, ModuleGraph, cluster_hash, cluster_modules, }; -const WEAK_MODULARITY_RULE_ID: &str = "CLA-FACT-CLUSTERING-WEAK-MODULARITY"; +const WEAK_MODULARITY_RULE_ID: &str = "LMWV-FACT-CLUSTERING-WEAK-MODULARITY"; /// REQ-ANALYZE-04: one finding per entity that vanished from source since the /// prior run (deletion detection, Phase 7). -const ENTITY_DELETED_RULE_ID: &str = "CLA-FACT-ENTITY-DELETED"; +const ENTITY_DELETED_RULE_ID: &str = "LMWV-FACT-ENTITY-DELETED"; /// REQ-ANALYZE-04: a guidance sheet whose explicit `guides` edge now points at a /// deleted entity — the guidance is stranded and should not enrich briefings for /// an entity that no longer exists. -const GUIDANCE_ORPHAN_RULE_ID: &str = "CLA-FACT-GUIDANCE-ORPHAN"; +const GUIDANCE_ORPHAN_RULE_ID: &str = "LMWV-FACT-GUIDANCE-ORPHAN"; /// Bounded handoff from the blocking plugin worker to the async writer loop. /// Mirrors detailed-design §11's `file_analyzed` backpressure cap. @@ -70,20 +70,20 @@ type DescribedEdgeRecord = (String, EdgeRecord); /// REQ-GUIDANCE-05 (WS6 T4a): a guidance sheet whose `expires` instant is in the /// past. The read path already excludes expired sheets from composition; this /// finding surfaces the state operatively (the sheet is not deleted). -const GUIDANCE_EXPIRED_RULE_ID: &str = "CLA-FACT-GUIDANCE-EXPIRED"; +const GUIDANCE_EXPIRED_RULE_ID: &str = "LMWV-FACT-GUIDANCE-EXPIRED"; /// REQ-GUIDANCE-05 (WS6 T4a): a guidance sheet whose matched entities carry a high /// aggregate `git_churn_count` — the code under the sheet has churned enough that /// the guidance is likely stale. Heuristic (confidence 0.7); inert until the /// churn-history pipeline (clarion-997c93ec4e) populates `git_churn_count`. -const GUIDANCE_CHURN_STALE_RULE_ID: &str = "CLA-FACT-GUIDANCE-CHURN-STALE"; +const GUIDANCE_CHURN_STALE_RULE_ID: &str = "LMWV-FACT-GUIDANCE-CHURN-STALE"; /// REQ-GUIDANCE-05 (WS6 T4): a Wardline-derived guidance sheet was preserved as /// an operator override while `wardline.yaml` changed underneath it. -const GUIDANCE_STALE_RULE_ID: &str = "CLA-FACT-GUIDANCE-STALE"; +const GUIDANCE_STALE_RULE_ID: &str = "LMWV-FACT-GUIDANCE-STALE"; /// Aggregate `git_churn_count` (summed over a sheet's matched entities) at or above -/// which a non-pinned sheet is flagged `CLA-FACT-GUIDANCE-CHURN-STALE`. +/// which a non-pinned sheet is flagged `LMWV-FACT-GUIDANCE-CHURN-STALE`. const CHURN_STALE_THRESHOLD: i64 = 50; /// The lower (stricter) churn threshold for `pinned: true` sheets — pinned guidance @@ -92,17 +92,17 @@ const CHURN_STALE_THRESHOLD_PINNED: i64 = 20; /// REQ-ANALYZE-05: a subsystem whose tier-bearing members declare ≥2 distinct /// Wardline tiers (a trust-boundary smell — the cluster straddles tiers). -const TIER_MIXING_RULE_ID: &str = "CLA-FACT-TIER-SUBSYSTEM-MIXING"; +const TIER_MIXING_RULE_ID: &str = "LMWV-FACT-TIER-SUBSYSTEM-MIXING"; /// REQ-ANALYZE-05: a subsystem whose tier-bearing members (≥2) all agree on one /// Wardline tier — a positive signal for tier-consistency reporting. -const TIER_UNANIMOUS_RULE_ID: &str = "CLA-FACT-SUBSYSTEM-TIER-UNANIMOUS"; +const TIER_UNANIMOUS_RULE_ID: &str = "LMWV-FACT-SUBSYSTEM-TIER-UNANIMOUS"; /// The finding rules persisted via `PersistPostRunFinding` *after* `CommitRun` /// (the SEI mint pass's deletion findings + the tier-subsystem pass), and so /// after Phase-8 emission has already run. A second, additive emission pass /// (Phase 8c, `clarion-ef8f64d5fd`) re-reads exactly these so they reach Filigree -/// in the same run rather than being stranded store-only. `CLA-FACT-ENTITY-DELETED` +/// in the same run rather than being stranded store-only. `LMWV-FACT-ENTITY-DELETED` /// anchors to the deleted entity's own path-bearing row; the subsystem-anchored /// tier rules (and, once authoring lands, the guidance-anchored orphan rule) are /// path-less, so the Phase-8c pass anchors them to the project root (the @@ -125,7 +125,7 @@ const POST_RUN_FINDING_RULES: &[&str] = &[ /// in the plugin's stderr. Pyright degradation findings now ride the plugin /// findings wire, but the syntax-error module property remains the stable /// source for parse failures because the degraded module entity is the anchor. -const SYNTAX_ERROR_RULE_ID: &str = "CLA-PY-SYNTAX-ERROR"; +const SYNTAX_ERROR_RULE_ID: &str = "LMWV-PY-SYNTAX-ERROR"; /// Writes structured run progress to a JSON file for the MCP `analyze_status` /// tool (clarion-7e0c21558a). A no-op unless `analyze_start` passed a @@ -309,7 +309,7 @@ pub(crate) struct AnalyzeOptions { /// the Filigree peer. Takes precedence over `run_id` as the run identifier. pub(crate) resume_run_id: Option, /// `--prune-unseen` (REQ-FINDING-06): after emission, ask Filigree to - /// soft-archive its stale `unseen_in_latest` Clarion findings. Enrich-only: + /// soft-archive its stale `unseen_in_latest` Loomweave findings. Enrich-only: /// a failure or a disabled integration never fails the run. pub(crate) prune_unseen: bool, /// When set, structured progress is written here as the run proceeds. @@ -333,7 +333,7 @@ pub(crate) struct AnalyzeOptions { /// /// # Errors /// -/// Returns an error if the target directory does not exist, has no `.clarion/` +/// Returns an error if the target directory does not exist, has no `.loomweave/` /// directory, if analyze config is invalid, or if the writer actor fails to /// start or process commands. #[allow(clippy::too_many_lines)] @@ -347,19 +347,19 @@ pub(crate) async fn run_with_options(project_path: PathBuf, options: AnalyzeOpti let project_root = project_path .canonicalize() .with_context(|| format!("cannot canonicalise path {}", project_path.display()))?; - let clarion_dir = project_root.join(".clarion"); - if !clarion_dir.exists() { + let loomweave_dir = project_root.join(".loomweave"); + if !loomweave_dir.exists() { bail!( - "{} has no .clarion/ directory. Run `clarion install` first.", + "{} has no .loomweave/ directory. Run `loomweave install` first.", project_root.display() ); } - let db_path = clarion_dir.join("clarion.db"); + let db_path = loomweave_dir.join("loomweave.db"); // Cross-process advisory lock (STO-01). Must outlive the writer-actor's // `handle.await` at the bottom of this function — see the drop-order // note on `AnalyzeLockGuard`. Drop on function exit releases the lock. - let _analyze_lock = crate::analyze_lock::acquire_analyze_lock(&clarion_dir)?; + let _analyze_lock = crate::analyze_lock::acquire_analyze_lock(&loomweave_dir)?; // Apply any pending schema migrations before opening the writer. `install` // is the usual migrator, but a binary upgrade that adds a migration the run @@ -370,11 +370,12 @@ pub(crate) async fn run_with_options(project_path: PathBuf, options: AnalyzeOpti { let mut conn = Connection::open(&db_path).context("open database to apply pending migrations")?; - clarion_storage::pragma::apply_write_pragmas(&conn).map_err(|e| anyhow::anyhow!("{e}"))?; - clarion_storage::schema::apply_migrations(&mut conn) + loomweave_storage::pragma::apply_write_pragmas(&conn) + .map_err(|e| anyhow::anyhow!("{e}"))?; + loomweave_storage::schema::apply_migrations(&mut conn) .map_err(|e| anyhow::anyhow!("{e}")) .context("apply pending migrations")?; - let repaired = clarion_storage::mark_stale_running_runs_failed(&conn) + let repaired = loomweave_storage::mark_stale_running_runs_failed(&conn) .map_err(|e| anyhow::anyhow!("{e}")) .context("mark stale running analyze runs failed")?; if repaired > 0 { @@ -490,7 +491,7 @@ pub(crate) async fn run_with_options(project_path: PathBuf, options: AnalyzeOpti .map_err(|e| anyhow::anyhow!("{e}"))?; // Non-zero exit. Printing to stdout + returning Ok(()) here - // hides the failure from `clarion analyze && do_next` chains + // hides the failure from `loomweave analyze && do_next` chains // and breaks CI gating that reads `$?`. The run row in the DB // is already marked `failed` above. bail!("analyze run {run_id} failed — {reason}"); @@ -614,9 +615,9 @@ pub(crate) async fn run_with_options(project_path: PathBuf, options: AnalyzeOpti let (prior_file_hashes, mut prior_locs_by_file, prior_index_snapshot) = if incremental { match Connection::open(&db_path) { Ok(conn) => { - let files = clarion_storage::previously_analyzed_files(&conn).unwrap_or_default(); - let locs = clarion_storage::prior_locators_by_file(&conn).unwrap_or_default(); - let snapshot = clarion_storage::load_prior_index(&conn).unwrap_or_default(); + let files = loomweave_storage::previously_analyzed_files(&conn).unwrap_or_default(); + let locs = loomweave_storage::prior_locators_by_file(&conn).unwrap_or_default(); + let snapshot = loomweave_storage::load_prior_index(&conn).unwrap_or_default(); (files, locs, snapshot) } Err(err) => { @@ -926,9 +927,9 @@ pub(crate) async fn run_with_options(project_path: PathBuf, options: AnalyzeOpti Err(plugin_error) => { log_plugin_findings(&plugin_id, &plugin_error.findings); // REQ-ANALYZE-06: persist the host findings collected before the - // crash. A per-file timeout already rides in as a CLA-PY-TIMEOUT + // crash. A per-file timeout already rides in as a LMWV-PY-TIMEOUT // finding (and is the root cause), so suppress the generic - // CLA-INFRA-PLUGIN-CRASH in that case to avoid double-reporting. + // LMWV-INFRA-PLUGIN-CRASH in that case to avoid double-reporting. let timed_out = plugin_error .findings .iter() @@ -1400,7 +1401,7 @@ pub(crate) async fn run_with_options(project_path: PathBuf, options: AnalyzeOpti // emission already ran, so without this they reach the store but // never the same-run Filigree emission. A second, additive pass // re-reads only the post-commit rules (the during-run findings were - // already emitted at Phase 8) and posts them: `CLA-FACT-ENTITY-DELETED` + // already emitted at Phase 8) and posts them: `LMWV-FACT-ENTITY-DELETED` // against the deleted entity's own path, and the path-less // subsystem-anchored tier facts against the project root (the // `default_path` fallback supplied inside `emit_findings_to_filigree` @@ -1525,8 +1526,8 @@ struct SeiPassStats { minted: u64, carried: u64, orphaned: u64, - /// Count of REQ-ANALYZE-04 deletion findings (`CLA-FACT-ENTITY-DELETED` + - /// `CLA-FACT-GUIDANCE-ORPHAN`) persisted from this run's orphaned set. + /// Count of REQ-ANALYZE-04 deletion findings (`LMWV-FACT-ENTITY-DELETED` + + /// `LMWV-FACT-GUIDANCE-ORPHAN`) persisted from this run's orphaned set. deletion_findings: u64, } @@ -1786,12 +1787,12 @@ async fn run_sei_mint_pass( /// (already sorted + deduped by the caller for determinism), returning the total /// finding count. /// -/// For each deleted entity: emit one `CLA-FACT-ENTITY-DELETED` (anchored to the +/// For each deleted entity: emit one `LMWV-FACT-ENTITY-DELETED` (anchored to the /// entity's own row — `entities` is never pruned, so the FK resolves) and /// invalidate its cached summaries. Then, for every guidance sheet stranded on a /// deleted entity — via an explicit `guides` edge OR a `match_rules` /// `{"type":"entity","id":X}` entry (detailed-design.md §5) — emit one -/// `CLA-FACT-GUIDANCE-ORPHAN` (anchored to the sheet, deleted target as a related +/// `LMWV-FACT-GUIDANCE-ORPHAN` (anchored to the sheet, deleted target as a related /// id). A sheet that strands the same target via both paths emits one finding. /// /// Returns `Ok(0)` for an empty deleted set without opening a connection. @@ -1856,9 +1857,9 @@ async fn emit_deletion_findings( // Scan every guidance sheet's `match_rules` for `{type:entity, id:X}` // entries whose X is in the deleted set. Reuse the shared rule shape - // (`clarion_storage::rule_match` reads `{"type":"entity","id":…}`), not a + // (`loomweave_storage::rule_match` reads `{"type":"entity","id":…}`), not a // hand-rolled key. - for sheet in clarion_storage::list_guidance_sheets(&conn) + for sheet in loomweave_storage::list_guidance_sheets(&conn) .map_err(|e| anyhow::anyhow!("{e}")) .context("list guidance sheets for match-rule orphan scan")? { @@ -1898,13 +1899,13 @@ async fn emit_deletion_findings( Ok(count) } -/// Build a `CLA-FACT-ENTITY-DELETED` finding anchored to the deleted entity's own +/// Build a `LMWV-FACT-ENTITY-DELETED` finding anchored to the deleted entity's own /// (never-pruned) row. The id is deterministic and run-scoped so a `--resume` /// re-walk regenerates the same id and `InsertFinding`'s upsert is idempotent. fn entity_deleted_finding(entity_id: &str, run_id: &str, now: &str) -> FindingRecord { FindingRecord { id: format!("core:finding:{run_id}:entity-deleted:{entity_id}"), - tool: "clarion".to_owned(), + tool: "loomweave".to_owned(), tool_version: env!("CARGO_PKG_VERSION").to_owned(), run_id: run_id.to_owned(), rule_id: ENTITY_DELETED_RULE_ID.to_owned(), @@ -1924,7 +1925,7 @@ fn entity_deleted_finding(entity_id: &str, run_id: &str, now: &str) -> FindingRe } } -/// Build a `CLA-FACT-GUIDANCE-ORPHAN` finding anchored to the guidance sheet +/// Build a `LMWV-FACT-GUIDANCE-ORPHAN` finding anchored to the guidance sheet /// whose `guides` edge targets `deleted_entity_id`. Run-scoped, deterministic id. fn guidance_orphan_finding( guidance_id: &str, @@ -1934,7 +1935,7 @@ fn guidance_orphan_finding( ) -> FindingRecord { FindingRecord { id: format!("core:finding:{run_id}:guidance-orphan:{guidance_id}:{deleted_entity_id}"), - tool: "clarion".to_owned(), + tool: "loomweave".to_owned(), tool_version: env!("CARGO_PKG_VERSION").to_owned(), run_id: run_id.to_owned(), rule_id: GUIDANCE_ORPHAN_RULE_ID.to_owned(), @@ -1963,19 +1964,19 @@ fn guidance_orphan_finding( /// REQ-GUIDANCE-05 (WS6 T4a): persist guidance-staleness findings over the /// committed graph and return the count. Independent signals per sheet: /// -/// - **`CLA-FACT-GUIDANCE-EXPIRED`** — the sheet's `expires` instant is lexically +/// - **`LMWV-FACT-GUIDANCE-EXPIRED`** — the sheet's `expires` instant is lexically /// `< now` (both are the fixed-width `YYYY-MM-DDTHH:MM:SS.sssZ` form /// [`iso8601_now`] emits, so a byte compare is a valid instant compare). Absent /// or malformed `expires` ⇒ skip. -/// - **`CLA-FACT-GUIDANCE-CHURN-STALE`** — the aggregate `git_churn_count` over the +/// - **`LMWV-FACT-GUIDANCE-CHURN-STALE`** — the aggregate `git_churn_count` over the /// sheet's matched entities meets the staleness threshold (asymmetric: 20 for /// `pinned` sheets, 50 otherwise). -/// - **`CLA-FACT-GUIDANCE-STALE`** — a Wardline-derived override still carries +/// - **`LMWV-FACT-GUIDANCE-STALE`** — a Wardline-derived override still carries /// the old `wardline.yaml` manifest hash after the manifest changed. /// /// Runs post-`CommitRun`, unconditionally (NOT gated on the SEI pass or on /// deletions) — see the call site. Deterministic: sheets in -/// [`clarion_storage::list_guidance_sheets`] order; matched ids sorted. +/// [`loomweave_storage::list_guidance_sheets`] order; matched ids sorted. /// /// Churn proxy note: the design wants "churn since `authored_at`/`reviewed_at`", /// but there is no churn-history to compute a true delta and `git_churn_count` is @@ -2010,7 +2011,7 @@ fn plan_guidance_staleness_findings( .canonicalize() .unwrap_or_else(|_| project_root.to_path_buf()); - let sheets = clarion_storage::list_guidance_sheets(&conn) + let sheets = loomweave_storage::list_guidance_sheets(&conn) .map_err(|e| anyhow::anyhow!("{e}")) .context("list guidance sheets for staleness scan")?; @@ -2072,7 +2073,7 @@ fn plan_guidance_staleness_findings( let mut agg: i64 = 0; let mut matched: Vec = Vec::new(); for (entity_id, churn) in &churned { - if clarion_storage::guidance_sheet_matches_entity( + if loomweave_storage::guidance_sheet_matches_entity( &conn, sheet, entity_id, @@ -2147,12 +2148,12 @@ async fn emit_guidance_staleness_findings( Ok(count) } -/// Build a `CLA-FACT-GUIDANCE-EXPIRED` finding anchored to the expired sheet. +/// Build a `LMWV-FACT-GUIDANCE-EXPIRED` finding anchored to the expired sheet. /// Run-scoped, deterministic id; INFO, confidence 1.0. fn guidance_expired_finding(guidance_id: &str, run_id: &str, now: &str) -> FindingRecord { FindingRecord { id: format!("core:finding:{run_id}:guidance-expired:{guidance_id}"), - tool: "clarion".to_owned(), + tool: "loomweave".to_owned(), tool_version: env!("CARGO_PKG_VERSION").to_owned(), run_id: run_id.to_owned(), rule_id: GUIDANCE_EXPIRED_RULE_ID.to_owned(), @@ -2181,7 +2182,7 @@ fn guidance_stale_finding( ) -> FindingRecord { FindingRecord { id: format!("core:finding:{run_id}:guidance-stale:{guidance_id}"), - tool: "clarion".to_owned(), + tool: "loomweave".to_owned(), tool_version: env!("CARGO_PKG_VERSION").to_owned(), run_id: run_id.to_owned(), rule_id: GUIDANCE_STALE_RULE_ID.to_owned(), @@ -2208,7 +2209,7 @@ fn guidance_stale_finding( } } -/// Build a `CLA-FACT-GUIDANCE-CHURN-STALE` finding anchored to the sheet, carrying +/// Build a `LMWV-FACT-GUIDANCE-CHURN-STALE` finding anchored to the sheet, carrying /// the matched entities (sorted) as related ids and the aggregate churn + /// threshold as evidence. Run-scoped, deterministic id; WARN, confidence 0.7 /// (heuristic). @@ -2221,7 +2222,7 @@ fn guidance_churn_stale_finding( ) -> FindingRecord { FindingRecord { id: format!("core:finding:{run_id}:guidance-churn-stale:{guidance_id}"), - tool: "clarion".to_owned(), + tool: "loomweave".to_owned(), tool_version: env!("CARGO_PKG_VERSION").to_owned(), run_id: run_id.to_owned(), rule_id: GUIDANCE_CHURN_STALE_RULE_ID.to_owned(), @@ -2269,8 +2270,8 @@ fn extract_wardline_tier(wardline_json: &str) -> Option { /// Wardline tiers land on functions (`python:function:`), not modules, /// so each tier-bearing entity is resolved up its `contains` chain to the /// subsystem it belongs to (`subsystem_of_entity`). Per subsystem, over its -/// tier-bearing members: ≥2 distinct tiers ⇒ `CLA-FACT-TIER-SUBSYSTEM-MIXING`; -/// exactly one tier across ≥2 members ⇒ `CLA-FACT-SUBSYSTEM-TIER-UNANIMOUS`. A +/// tier-bearing members: ≥2 distinct tiers ⇒ `LMWV-FACT-TIER-SUBSYSTEM-MIXING`; +/// exactly one tier across ≥2 members ⇒ `LMWV-FACT-SUBSYSTEM-TIER-UNANIMOUS`. A /// single tier-bearing member yields neither (no consensus from one voice). /// /// Conditional on a prior Wardline ingest: `analyze` never writes tier facts (the @@ -2309,7 +2310,7 @@ async fn emit_tier_subsystem_findings( let Some(tier) = extract_wardline_tier(&wardline_json) else { continue; }; - if let Some(subsystem) = clarion_storage::subsystem_of_entity(&conn, &entity_id) + if let Some(subsystem) = loomweave_storage::subsystem_of_entity(&conn, &entity_id) .map_err(|e| anyhow::anyhow!("{e}")) .with_context(|| format!("resolve subsystem for {entity_id}"))? { @@ -2350,7 +2351,7 @@ async fn emit_tier_subsystem_findings( Ok(count) } -/// Build a `CLA-FACT-TIER-SUBSYSTEM-MIXING` finding anchored to the subsystem, +/// Build a `LMWV-FACT-TIER-SUBSYSTEM-MIXING` finding anchored to the subsystem, /// carrying its tier-bearing members as related ids and the tier distribution as /// evidence. Members are pre-sorted by the caller; the id is run-scoped. fn tier_mixing_finding( @@ -2367,7 +2368,7 @@ fn tier_mixing_finding( } FindingRecord { id: format!("core:finding:{run_id}:tier-mixing:{subsystem_id}"), - tool: "clarion".to_owned(), + tool: "loomweave".to_owned(), tool_version: env!("CARGO_PKG_VERSION").to_owned(), run_id: run_id.to_owned(), rule_id: TIER_MIXING_RULE_ID.to_owned(), @@ -2391,7 +2392,7 @@ fn tier_mixing_finding( } } -/// Build a `CLA-FACT-SUBSYSTEM-TIER-UNANIMOUS` finding (positive signal) anchored +/// Build a `LMWV-FACT-SUBSYSTEM-TIER-UNANIMOUS` finding (positive signal) anchored /// to the subsystem whose ≥2 tier-bearing members all share `tier`. fn tier_unanimous_finding( subsystem_id: &str, @@ -2403,7 +2404,7 @@ fn tier_unanimous_finding( let member_ids: Vec<&str> = members.iter().map(|(id, _)| id.as_str()).collect(); FindingRecord { id: format!("core:finding:{run_id}:tier-unanimous:{subsystem_id}"), - tool: "clarion".to_owned(), + tool: "loomweave".to_owned(), tool_version: env!("CARGO_PKG_VERSION").to_owned(), run_id: run_id.to_owned(), rule_id: TIER_UNANIMOUS_RULE_ID.to_owned(), @@ -2636,7 +2637,7 @@ async fn run_phase3_clustering( kind: "in_subsystem".to_owned(), from_id: module_id.clone(), to_id: subsystem_id.clone(), - confidence: clarion_core::EdgeConfidence::Resolved, + confidence: loomweave_core::EdgeConfidence::Resolved, properties_json: None, source_file_id: None, source_byte_start: None, @@ -2696,7 +2697,7 @@ async fn run_phase3_clustering( } fn subsystem_entity_id(cluster_hash: &str) -> Result { - Ok(clarion_core::entity_id::entity_id("core", "subsystem", cluster_hash)?.to_string()) + Ok(loomweave_core::entity_id::entity_id("core", "subsystem", cluster_hash)?.to_string()) } fn subsystem_display_name(member_ids: &[String], cluster_hash: &str) -> (String, String) { @@ -2766,7 +2767,7 @@ async fn insert_weak_modularity_finding( .send_wait(|ack| WriterCmd::InsertFinding { finding: Box::new(FindingRecord { id: finding_id.clone(), - tool: "clarion".to_owned(), + tool: "loomweave".to_owned(), tool_version: env!("CARGO_PKG_VERSION").to_owned(), run_id: run_id.to_owned(), rule_id: WEAK_MODULARITY_RULE_ID.to_owned(), @@ -2803,7 +2804,7 @@ async fn insert_weak_modularity_finding( Ok(true) } -/// Build a `CLA-PY-SYNTAX-ERROR` finding for an accepted entity the plugin +/// Build a `LMWV-PY-SYNTAX-ERROR` finding for an accepted entity the plugin /// flagged `parse_status="syntax_error"`, or `None` for cleanly-parsed entities. /// /// The finding anchors to the degraded entity itself (the plugin still emits one @@ -2830,7 +2831,7 @@ fn syntax_error_finding( } Some(FindingRecord { id: format!("core:finding:{run_id}:syntax-error:{}", record.id), - tool: "clarion".to_owned(), + tool: "loomweave".to_owned(), tool_version: env!("CARGO_PKG_VERSION").to_owned(), run_id: run_id.to_owned(), rule_id: SYNTAX_ERROR_RULE_ID.to_owned(), @@ -2860,8 +2861,8 @@ fn syntax_error_finding( /// Core-emitted crash subcode (REQ-ANALYZE-06). Distinct from the crash-loop /// breaker subcode (`FINDING_DISABLED_CRASH_LOOP`): this fires per plugin crash, /// the breaker subcode fires once when the breaker trips. -const INFRA_CRASH_RULE_ID: &str = "CLA-INFRA-PLUGIN-CRASH"; -const SOURCE_WALK_SKIPPED_RULE_ID: &str = "CLA-INFRA-SOURCE-WALK-SKIPPED"; +const INFRA_CRASH_RULE_ID: &str = "LMWV-INFRA-PLUGIN-CRASH"; +const SOURCE_WALK_SKIPPED_RULE_ID: &str = "LMWV-INFRA-SOURCE-WALK-SKIPPED"; const SOURCE_WALK_ERROR_SAMPLE_LIMIT: usize = 10; /// Anchor entity id for project/plugin-level findings that are not file-scoped @@ -2928,20 +2929,20 @@ async fn ensure_project_anchor( /// Core-emitted per-file analysis-timeout subcode (REQ-ANALYZE-06). Host-side: /// the plugin is killed when a single `analyze_file` exceeds the deadline. -const PLUGIN_TIMEOUT_RULE_ID: &str = "CLA-PY-TIMEOUT"; -const PLUGIN_JAIL_OPEN_RULE_ID: &str = "CLA-INFRA-PLUGIN-JAIL-OPEN-FAILED"; +const PLUGIN_TIMEOUT_RULE_ID: &str = "LMWV-PY-TIMEOUT"; +const PLUGIN_JAIL_OPEN_RULE_ID: &str = "LMWV-INFRA-PLUGIN-JAIL-OPEN-FAILED"; /// Per-file `analyze_file` deadline. ADR-035 tuning: basis — a single file's /// extraction (incl. pyright queries) completes in well under a second on /// healthy plugins, so minutes of no progress means a hung plugin, not slow -/// work; override — env `CLARION_PLUGIN_FILE_TIMEOUT_MS`; retune — raise if a +/// work; override — env `LOOMWEAVE_PLUGIN_FILE_TIMEOUT_MS`; retune — raise if a /// legitimate analyzer (very large generated file) trips it in practice. const DEFAULT_PLUGIN_FILE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(120); const PLUGIN_WATCHDOG_POLL_INTERVAL: std::time::Duration = std::time::Duration::from_millis(50); /// Resolve the per-file analysis timeout, honouring the env override. fn plugin_file_timeout() -> std::time::Duration { - std::env::var("CLARION_PLUGIN_FILE_TIMEOUT_MS") + std::env::var("LOOMWEAVE_PLUGIN_FILE_TIMEOUT_MS") .ok() .and_then(|v| v.parse::().ok()) .map_or( @@ -2958,8 +2959,8 @@ fn infra_severity(subcode: &str) -> &'static str { INFRA_CRASH_RULE_ID | PLUGIN_TIMEOUT_RULE_ID | FINDING_DISABLED_CRASH_LOOP - | "CLA-INFRA-PLUGIN-OOM-KILLED" - | "CLA-INFRA-PLUGIN-DISABLED-PATH-ESCAPE" => "ERROR", + | "LMWV-INFRA-PLUGIN-OOM-KILLED" + | "LMWV-INFRA-PLUGIN-DISABLED-PATH-ESCAPE" => "ERROR", _ => "WARN", } } @@ -2985,7 +2986,7 @@ fn host_finding_to_record( .to_string(); FindingRecord { id: format!("core:finding:{run_id}:infra:{discriminator}"), - tool: "clarion".to_owned(), + tool: "loomweave".to_owned(), tool_version: env!("CARGO_PKG_VERSION").to_owned(), run_id: run_id.to_owned(), rule_id: hf.subcode.clone(), @@ -3013,9 +3014,9 @@ fn host_finding_anchor_id(hf: &HostFinding, project_root: &Path, project_anchor: } fn verified_plugin_dispatch_path(project_root: &Path, file: &Path) -> Result { - let _handle = clarion_core::plugin::jail::safe_open(project_root, file) + let _handle = loomweave_core::plugin::jail::safe_open(project_root, file) .with_context(|| format!("safe-open {}", file.display()))?; - let jailed = clarion_core::plugin::jail::jail_to_string(project_root, file) + let jailed = loomweave_core::plugin::jail::jail_to_string(project_root, file) .map_err(|e| anyhow::anyhow!("{e}")) .with_context(|| format!("jail-check {}", file.display()))?; Ok(PathBuf::from(jailed)) @@ -3039,7 +3040,7 @@ fn jail_open_failed_finding(file: &Path, error: &anyhow::Error) -> HostFinding { } } -/// Build the `CLA-INFRA-PLUGIN-CRASH` finding for a plugin that crashed mid-run +/// Build the `LMWV-INFRA-PLUGIN-CRASH` finding for a plugin that crashed mid-run /// (REQ-ANALYZE-06). Anchored to the project entity; the crash reason is the /// evidence. fn crash_finding_record( @@ -3052,7 +3053,7 @@ fn crash_finding_record( let discriminator = blake3::hash(format!("{plugin_id}\u{0}{reason}").as_bytes()).to_hex(); FindingRecord { id: format!("core:finding:{run_id}:crash:{discriminator}"), - tool: "clarion".to_owned(), + tool: "loomweave".to_owned(), tool_version: env!("CARGO_PKG_VERSION").to_owned(), run_id: run_id.to_owned(), rule_id: INFRA_CRASH_RULE_ID.to_owned(), @@ -3086,7 +3087,7 @@ fn source_walk_finding_record( .to_hex(); FindingRecord { id: format!("core:finding:{run_id}:source-walk:{discriminator}"), - tool: "clarion".to_owned(), + tool: "loomweave".to_owned(), tool_version: env!("CARGO_PKG_VERSION").to_owned(), run_id: run_id.to_owned(), rule_id: SOURCE_WALK_SKIPPED_RULE_ID.to_owned(), @@ -3115,12 +3116,12 @@ fn source_walk_finding_record( } } -/// Load the MCP-side config (Filigree integration) from the same `clarion.yaml` -/// `clarion serve` reads. A missing or unparseable file falls back to the +/// Load the MCP-side config (Filigree integration) from the same `loomweave.yaml` +/// `loomweave serve` reads. A missing or unparseable file falls back to the /// default (Filigree disabled), so a config problem never fails the run — it /// just means no emission. pub(crate) fn load_mcp_config(project_root: &Path, config_path: Option<&Path>) -> McpConfig { - let path = config_path.map_or_else(|| project_root.join("clarion.yaml"), Path::to_path_buf); + let path = config_path.map_or_else(|| project_root.join("loomweave.yaml"), Path::to_path_buf); if !path.exists() { return McpConfig::default(); } @@ -3169,8 +3170,8 @@ async fn populate_semantic_embeddings( } let conn = Connection::open(db_path) - .with_context(|| format!("open Clarion database {}", db_path.display()))?; - let store = EmbeddingStore::open_in_clarion_dir(project_root) + .with_context(|| format!("open Loomweave database {}", db_path.display()))?; + let store = EmbeddingStore::open_in_loomweave_dir(project_root) .map_err(|err| anyhow::anyhow!("{err}")) .context("open semantic embedding sidecar")?; let pending = semantic_embedding_candidates(&conn, &store, &model_id, &mut stats)?; @@ -3319,7 +3320,7 @@ fn semantic_embedding_text(short_name: &str, name: &str, properties_json: &str) /// Best-effort and enrich-only: gated behind /// `integrations.filigree.{enabled,emit_findings}`, and any failure (Filigree /// down, transport error, build error) is recorded in the returned stats blob -/// and logged as `CLA-INFRA-FILIGREE-UNREACHABLE` rather than propagated — the +/// and logged as `LMWV-INFRA-FILIGREE-UNREACHABLE` rather than propagated — the /// analyze run never fails because a sibling tool is unreachable. Returns /// [`serde_json::Value::Null`] when emission is disabled; otherwise a /// `filigree_emission` stats object folded into `stats.json`. @@ -3368,7 +3369,7 @@ async fn emit_findings_to_filigree( } let rows = match Connection::open(db_path) { - Ok(conn) => match clarion_storage::findings_for_emit(&conn, run_id) { + Ok(conn) => match loomweave_storage::findings_for_emit(&conn, run_id) { Ok(rows) => rows, Err(err) => { tracing::warn!(run_id, error = %err, "read findings for emission failed; skipping emission"); @@ -3397,7 +3398,7 @@ async fn emit_findings_to_filigree( // synthetic-entity findings — the subsystem-anchored tier facts — to the // project root (mirroring the `core:project:*` finding anchor) so they POST // rather than being dropped as `skipped_no_path`. The wire layer flags these - // `metadata.clarion.synthetic_anchor=true`. The Phase-8 pass passes `None`, + // `metadata.loomweave.synthetic_anchor=true`. The Phase-8 pass passes `None`, // so during-run path-less findings (e.g. the weak-modularity subsystem fact) // keep their existing store-only treatment. let default_path = rule_filter.map(|_| project_root.display().to_string()); @@ -3439,7 +3440,7 @@ async fn emit_findings_to_filigree( .await } -fn federation_finding_for_emit(row: clarion_storage::FindingForEmitRow) -> FindingForEmit { +fn federation_finding_for_emit(row: loomweave_storage::FindingForEmitRow) -> FindingForEmit { FindingForEmit { id: row.id, rule_id: row.rule_id, @@ -3462,7 +3463,7 @@ fn federation_finding_for_emit(row: clarion_storage::FindingForEmitRow) -> Findi /// the `filigree_emission` stats blob. Split out of [`emit_findings_to_filigree`] /// so the Phase-8 read/filter logic and this network lifecycle stay independently /// readable. Best-effort: a build/transport failure becomes an -/// `CLA-INFRA-FILIGREE-UNREACHABLE` stats blob via [`unreachable_stats`]. +/// `LMWV-INFRA-FILIGREE-UNREACHABLE` stats blob via [`unreachable_stats`]. async fn post_findings_batch( filigree_cfg: &FiligreeConfig, project_root: &Path, @@ -3475,7 +3476,7 @@ async fn post_findings_batch( let skipped_no_path = batch.skipped_no_path; // Resolve the live Filigree URL (ephemeral port over stale config), the same - // resolution `clarion serve` and `project_status` use. + // resolution `loomweave serve` and `project_status` use. let resolution = resolve_filigree_url(filigree_cfg, project_root); let mut resolved_cfg = filigree_cfg.clone(); if let Some(url) = resolution.resolved_url { @@ -3555,11 +3556,11 @@ async fn post_findings_batch( } /// Build the `filigree_emission` stats blob for a failed POST and log it as -/// `CLA-INFRA-FILIGREE-UNREACHABLE`. The infra finding is recorded in +/// `LMWV-INFRA-FILIGREE-UNREACHABLE`. The infra finding is recorded in /// `stats.json` and the log (two of the three surfaces REQ-ANALYZE-06 names); /// the local `findings` table is not used because its `entity_id` is a /// non-null FK to `entities` and an infra finding has no anchor entity — the -/// same reason every other `CLA-INFRA-*` finding is log-only today. +/// same reason every other `LMWV-INFRA-*` finding is log-only today. fn unreachable_stats( run_id: &str, endpoint: &str, @@ -3571,13 +3572,13 @@ fn unreachable_stats( tracing::warn!( run_id, endpoint, - rule_id = "CLA-INFRA-FILIGREE-UNREACHABLE", + rule_id = "LMWV-INFRA-FILIGREE-UNREACHABLE", error, "could not post findings to Filigree; continuing (enrich-only)", ); serde_json::json!({ "status": "unreachable", - "rule_id": "CLA-INFRA-FILIGREE-UNREACHABLE", + "rule_id": "LMWV-INFRA-FILIGREE-UNREACHABLE", "endpoint": endpoint, "findings_total": total_findings, "emitted_attempted": emitted, @@ -3587,12 +3588,12 @@ fn unreachable_stats( } /// `--prune-unseen` retention sweep (WP9-B, REQ-FINDING-06): asks Filigree to -/// soft-archive its own `unseen_in_latest` Clarion findings older than the +/// soft-archive its own `unseen_in_latest` Loomweave findings older than the /// configured age. Returns [`serde_json::Value::Null`] when not requested; /// otherwise a `filigree_prune` stats object folded into `stats.json`. Like /// emission, this is enrich-only — a disabled integration or a Filigree outage /// is recorded in stats, never fails the run. `scan_source` scoping is enforced -/// by Filigree, so the sweep can only touch Clarion's findings. +/// by Filigree, so the sweep can only touch Loomweave's findings. async fn prune_unseen_findings_in_filigree( project_root: &Path, run_id: &str, @@ -3622,7 +3623,7 @@ async fn prune_unseen_findings_in_filigree( } let endpoint = clean_stale_url(&resolved_cfg.base_url); let request = CleanStaleRequest { - scan_source: CLARION_SCAN_SOURCE.to_owned(), + scan_source: LOOMWEAVE_SCAN_SOURCE.to_owned(), older_than_days, actor: resolved_cfg.actor.clone(), }; @@ -3670,7 +3671,7 @@ async fn prune_unseen_findings_in_filigree( } /// Build the `filigree_prune` stats blob for a failed sweep and log it as -/// `CLA-INFRA-FILIGREE-UNREACHABLE` — the enrich-only degrade, identical in +/// `LMWV-INFRA-FILIGREE-UNREACHABLE` — the enrich-only degrade, identical in /// spirit to [`unreachable_stats`] for emission. fn prune_unreachable_stats( run_id: &str, @@ -3681,13 +3682,13 @@ fn prune_unreachable_stats( tracing::warn!( run_id, endpoint, - rule_id = "CLA-INFRA-FILIGREE-UNREACHABLE", + rule_id = "LMWV-INFRA-FILIGREE-UNREACHABLE", error, "could not prune unseen findings in Filigree; continuing (enrich-only)", ); serde_json::json!({ "status": "unreachable", - "rule_id": "CLA-INFRA-FILIGREE-UNREACHABLE", + "rule_id": "LMWV-INFRA-FILIGREE-UNREACHABLE", "endpoint": endpoint, "older_than_days": older_than_days, "error": error, @@ -3822,7 +3823,7 @@ fn handle_plugin_task_join_result( /// Returned from the blocking plugin task on success. struct BatchResult { /// Findings accumulated by the host during the session. - findings: Vec, + findings: Vec, } #[allow(clippy::large_enum_variant)] @@ -3865,25 +3866,25 @@ struct PluginKindRoles { } impl PluginKindRoles { - fn from_manifest(manifest: &clarion_core::Manifest) -> Self { + fn from_manifest(manifest: &loomweave_core::Manifest) -> Self { let mut roles = Self::default(); for kind in &manifest.ontology.entity_kinds { if manifest .ontology - .kind_has_role(kind, clarion_core::OntologyEntityRole::FileScope) + .kind_has_role(kind, loomweave_core::OntologyEntityRole::FileScope) { roles.file_scope.insert(kind.clone()); } if manifest .ontology - .kind_has_role(kind, clarion_core::OntologyEntityRole::Callable) + .kind_has_role(kind, loomweave_core::OntologyEntityRole::Callable) { roles.callable.insert(kind.clone()); } - if manifest - .ontology - .kind_has_role(kind, clarion_core::OntologyEntityRole::SyntaxDegradedModule) - { + if manifest.ontology.kind_has_role( + kind, + loomweave_core::OntologyEntityRole::SyntaxDegradedModule, + ) { roles.syntax_degraded_module.insert(kind.clone()); } } @@ -4083,7 +4084,7 @@ struct PendingUnresolvedCallSites { sites: Vec, } -/// Per-file analysis-timeout watchdog (REQ-ANALYZE-06, `CLA-PY-TIMEOUT`). +/// Per-file analysis-timeout watchdog (REQ-ANALYZE-06, `LMWV-PY-TIMEOUT`). /// /// `analyze_file` blocks on a synchronous read of the plugin's stdout, which has /// no read deadline. The watchdog runs on its own thread holding a shared handle @@ -4176,18 +4177,18 @@ fn spawn_plugin_watchdog( /// zombie into the kernel process table per spawn. #[allow(clippy::too_many_lines, clippy::too_many_arguments)] fn run_plugin_blocking( - manifest: clarion_core::Manifest, + manifest: loomweave_core::Manifest, project_root: &Path, plugin_id: &str, executable: &Path, files: &[PathBuf], - briefing_blocks: &Arc>, + briefing_blocks: &Arc>, scanned_source_files: &Arc>, progress: &ProgressReporter, file_timeout: std::time::Duration, batch_tx: &tokio::sync::mpsc::Sender, ) -> Result { - use clarion_core::PluginHost; + use loomweave_core::PluginHost; let manifest_language = manifest.plugin.language.clone(); let kind_roles = PluginKindRoles::from_manifest(&manifest); @@ -4398,7 +4399,7 @@ fn run_plugin_blocking( drop(host); // REQ-ANALYZE-06: a per-file timeout is a recoverable failure that must be - // visible. Add a CLA-PY-TIMEOUT host finding; it rides out through + // visible. Add a LMWV-PY-TIMEOUT host finding; it rides out through // PluginRunError.findings and is persisted by the run's crash path. if timed_out { let mut metadata = BTreeMap::new(); @@ -4657,7 +4658,7 @@ fn core_file_entity_record( project_root: &Path, file: &Path, manifest_language: &str, - briefing_blocks: &BTreeMap, + briefing_blocks: &BTreeMap, scanned_source_files: &BTreeSet, ) -> Result<(String, EntityRecord)> { let canonical_root = project_root @@ -4670,7 +4671,7 @@ fn core_file_entity_record( core_file_entity_id_from_canonical(&canonical_root, &canonical_file)?; let briefing_blocked = briefing_blocks.get(&canonical_file).copied().or_else(|| { (!scanned_source_files.contains(&canonical_file)) - .then_some(clarion_core::BriefingBlockReason::UnscannedSource) + .then_some(loomweave_core::BriefingBlockReason::UnscannedSource) }); let source_file_path = canonical_file .into_os_string() @@ -4752,7 +4753,7 @@ fn core_file_entity_id_from_canonical( ) })?; let qualified_name = project_relative_posix(relative)?; - let id = clarion_core::entity_id::entity_id("core", "file", &qualified_name)?.to_string(); + let id = loomweave_core::entity_id::entity_id("core", "file", &qualified_name)?.to_string(); Ok((id, qualified_name)) } @@ -4869,7 +4870,7 @@ fn source_line_range(entity: &AcceptedEntity) -> Option { /// read — callers fail toward re-analysis. fn whole_file_hash(project_root: &Path, path: &Path) -> Option { use std::io::Read; - let mut file = clarion_core::plugin::jail::safe_open(project_root, path).ok()?; + let mut file = loomweave_core::plugin::jail::safe_open(project_root, path).ok()?; let mut bytes = Vec::new(); file.read_to_end(&mut bytes).ok()?; Some(blake3::hash(&bytes).to_hex().to_string()) @@ -4923,7 +4924,7 @@ fn content_hash_for_entity( let range = source_line_range?; let mut file = - clarion_core::plugin::jail::safe_open(project_root, Path::new(&entity.source_file_path)) + loomweave_core::plugin::jail::safe_open(project_root, Path::new(&entity.source_file_path)) .ok()?; let mut source = String::new(); file.read_to_string(&mut source).ok()?; @@ -4980,7 +4981,7 @@ fn map_edge_to_record(edge: AcceptedEdge, source_file_id: Option) -> Edg } fn map_unresolved_call_sites_for_file( - stats: &clarion_core::AnalyzeFileStats, + stats: &loomweave_core::AnalyzeFileStats, entities: &[(String, EntityRecord)], kind_roles: &PluginKindRoles, created_at: &str, @@ -5100,10 +5101,10 @@ fn unresolved_call_site_key( /// Skip-list for directory names during the source walk. /// -/// Sprint 1 conservative set: VCS directories, clarion's own state, and +/// Sprint 1 conservative set: VCS directories, loomweave's own state, and /// common virtual-environment directories. const SKIP_DIRS: &[&str] = &[ - ".clarion", + ".loomweave", ".git", ".hg", ".svn", @@ -5613,7 +5614,7 @@ mod tests { assert_eq!(finding.entity_id, "python:module:pkg.broken"); assert_eq!(finding.kind, "defect"); assert_eq!(finding.severity, "WARN"); - assert_eq!(finding.tool, "clarion"); + assert_eq!(finding.tool, "loomweave"); // Deterministic, run-scoped id keeps InsertFinding idempotent on resume. assert_eq!( finding.id, @@ -5747,23 +5748,23 @@ mod tests { #[test] fn infra_severity_escalates_crash_and_kill() { assert_eq!(infra_severity(INFRA_CRASH_RULE_ID), "ERROR"); - assert_eq!(infra_severity("CLA-INFRA-PLUGIN-OOM-KILLED"), "ERROR"); - assert_eq!(infra_severity("CLA-INFRA-PLUGIN-MALFORMED-ENTITY"), "WARN"); + assert_eq!(infra_severity("LMWV-INFRA-PLUGIN-OOM-KILLED"), "ERROR"); + assert_eq!(infra_severity("LMWV-INFRA-PLUGIN-MALFORMED-ENTITY"), "WARN"); } #[test] fn host_finding_to_record_anchors_and_carries_subcode() { let hf = HostFinding { - subcode: "CLA-INFRA-PLUGIN-MALFORMED-ENTITY".to_owned(), + subcode: "LMWV-INFRA-PLUGIN-MALFORMED-ENTITY".to_owned(), message: "entity failed to deserialise".to_owned(), metadata: std::collections::BTreeMap::new(), }; let rec = host_finding_to_record(&hf, "python", "core:project:demo", "run-1", "t"); - assert_eq!(rec.rule_id, "CLA-INFRA-PLUGIN-MALFORMED-ENTITY"); + assert_eq!(rec.rule_id, "LMWV-INFRA-PLUGIN-MALFORMED-ENTITY"); assert_eq!(rec.entity_id, "core:project:demo"); assert_eq!(rec.severity, "WARN"); assert_eq!(rec.kind, "defect"); - assert_eq!(rec.tool, "clarion"); + assert_eq!(rec.tool, "loomweave"); assert!(rec.evidence_json.contains("python")); } @@ -5779,7 +5780,7 @@ mod tests { source.to_string_lossy().into_owned(), ); let hf = HostFinding { - subcode: "CLA-PY-PYRIGHT-RESTART".to_owned(), + subcode: "LMWV-PY-PYRIGHT-RESTART".to_owned(), message: "pyright restarted".to_owned(), metadata, }; @@ -5840,7 +5841,7 @@ mod tests { kind: "imports".to_owned(), from_id: from_id.to_owned(), to_id: to_id.to_owned(), - confidence: clarion_core::EdgeConfidence::Resolved, + confidence: loomweave_core::EdgeConfidence::Resolved, properties_json: Some( serde_json::json!({ "imported_name": imported_name, @@ -5966,11 +5967,11 @@ mod tests { kind: "function".to_owned(), qualified_name: "demo.hello".to_owned(), source_file_path: source_path.display().to_string(), - raw: clarion_core::plugin::host::RawEntity { + raw: loomweave_core::plugin::host::RawEntity { id: "python:function:demo.hello".to_owned(), kind: "function".to_owned(), qualified_name: "demo.hello".to_owned(), - source: clarion_core::plugin::host::RawSource { + source: loomweave_core::plugin::host::RawSource { file_path: source_path.display().to_string(), extra: source_range.as_object().unwrap().clone(), }, @@ -6045,16 +6046,16 @@ mod tests { ("python:module:demo".to_owned(), module), ("python:function:demo.caller".to_owned(), caller.clone()), ]; - let stats = clarion_core::AnalyzeFileStats { + let stats = loomweave_core::AnalyzeFileStats { unresolved_call_sites_total: 1, - unresolved_call_sites: vec![clarion_core::UnresolvedCallSite { + unresolved_call_sites: vec![loomweave_core::UnresolvedCallSite { caller_entity_id: caller.id.clone(), site_ordinal: 0, source_byte_start: 14, source_byte_end: 24, callee_expr: "dynamic_target".to_owned(), }], - ..clarion_core::AnalyzeFileStats::default() + ..loomweave_core::AnalyzeFileStats::default() }; let mapped = map_unresolved_call_sites_for_file( @@ -6109,7 +6110,7 @@ mod tests { updated_at: "2026-05-17T00:00:00.000Z".to_owned(), }; let entities = vec![("python:function:demo.caller".to_owned(), caller)]; - let stats = clarion_core::AnalyzeFileStats::default(); + let stats = loomweave_core::AnalyzeFileStats::default(); let mapped = map_unresolved_call_sites_for_file( &stats, @@ -6128,13 +6129,13 @@ mod tests { async fn semantic_embedding_population_skips_fresh_sidecar_rows() { use std::sync::Arc; - use clarion_core::{EmbeddingProvider, EmbeddingRecording, RecordingEmbeddingProvider}; - use clarion_federation::config::SemanticSearchConfig; - use clarion_storage::{EmbeddingKey, EmbeddingStore, pragma, schema}; + use loomweave_core::{EmbeddingProvider, EmbeddingRecording, RecordingEmbeddingProvider}; + use loomweave_federation::config::SemanticSearchConfig; + use loomweave_storage::{EmbeddingKey, EmbeddingStore, pragma, schema}; let project = tempfile::tempdir().unwrap(); - std::fs::create_dir(project.path().join(".clarion")).unwrap(); - let db_path = project.path().join(".clarion/clarion.db"); + std::fs::create_dir(project.path().join(".loomweave")).unwrap(); + let db_path = project.path().join(".loomweave/loomweave.db"); let mut conn = rusqlite::Connection::open(&db_path).unwrap(); pragma::apply_write_pragmas(&conn).unwrap(); schema::apply_migrations(&mut conn).unwrap(); @@ -6149,7 +6150,7 @@ mod tests { .unwrap(); drop(conn); - let store = EmbeddingStore::open_in_clarion_dir(project.path()).unwrap(); + let store = EmbeddingStore::open_in_loomweave_dir(project.path()).unwrap(); store .upsert( &EmbeddingKey { @@ -6197,13 +6198,13 @@ mod tests { async fn semantic_embedding_population_skips_briefing_blocked_entities() { use std::sync::Arc; - use clarion_core::{EmbeddingProvider, EmbeddingRecording, RecordingEmbeddingProvider}; - use clarion_federation::config::SemanticSearchConfig; - use clarion_storage::{pragma, schema}; + use loomweave_core::{EmbeddingProvider, EmbeddingRecording, RecordingEmbeddingProvider}; + use loomweave_federation::config::SemanticSearchConfig; + use loomweave_storage::{pragma, schema}; let project = tempfile::tempdir().unwrap(); - std::fs::create_dir(project.path().join(".clarion")).unwrap(); - let db_path = project.path().join(".clarion/clarion.db"); + std::fs::create_dir(project.path().join(".loomweave")).unwrap(); + let db_path = project.path().join(".loomweave/loomweave.db"); let mut conn = rusqlite::Connection::open(&db_path).unwrap(); pragma::apply_write_pragmas(&conn).unwrap(); schema::apply_migrations(&mut conn).unwrap(); diff --git a/crates/clarion-cli/src/analyze_lock.rs b/crates/loomweave-cli/src/analyze_lock.rs similarity index 70% rename from crates/clarion-cli/src/analyze_lock.rs rename to crates/loomweave-cli/src/analyze_lock.rs index 9108768d..e00f4947 100644 --- a/crates/clarion-cli/src/analyze_lock.rs +++ b/crates/loomweave-cli/src/analyze_lock.rs @@ -1,16 +1,16 @@ -//! Cross-process advisory lock for `clarion analyze`. +//! Cross-process advisory lock for `loomweave analyze`. //! -//! Two concurrent `clarion analyze` processes against the same project +//! Two concurrent `loomweave analyze` processes against the same project //! corrupt the run-attribution graph: each opens its own writer-actor, //! each calls `BeginRun` (insert a fresh `runs` row in `status='running'`), //! and each races on entity/edge inserts under `SQLite` WAL. The in-process -//! `ActorState::current_run` guard (clarion-storage `writer.rs`) prevents +//! `ActorState::current_run` guard (loomweave-storage `writer.rs`) prevents //! a single writer from issuing two `BeginRun`s; it does nothing across //! processes. //! //! This module acquires an exclusive `fs2`-advisory lock on a dedicated -//! sentinel file `.clarion/clarion.lock` for the duration of the analyze -//! run. The lock file is separate from `clarion.db` so `SQLite`'s own +//! sentinel file `.loomweave/loomweave.lock` for the duration of the analyze +//! run. The lock file is separate from `loomweave.db` so `SQLite`'s own //! locking (per-connection, transaction-scoped) is independent. The //! guard's `Drop` releases the OS-level lock. @@ -20,13 +20,13 @@ use std::path::Path; use anyhow::{Context, Result, bail}; use fs2::FileExt; -const LOCK_FILE_NAME: &str = "clarion.lock"; +const LOCK_FILE_NAME: &str = "loomweave.lock"; /// RAII guard holding the analyze lock. Drop releases the OS lock. /// /// **Drop order is load-bearing.** The guard must outlive the writer-actor's /// `JoinHandle::await` in `analyze::run_with_options`; otherwise a second -/// `clarion analyze` can grab the lock while writer-actor 1's final +/// `loomweave analyze` can grab the lock while writer-actor 1's final /// transaction is still landing through WAL. `fs2`'s `File` impl unlocks /// on file close, so dropping the `File` releases the OS lock; we rely on /// Drop rather than an explicit unlock so panic and happy paths behave @@ -37,21 +37,21 @@ pub(crate) struct AnalyzeLockGuard { _file: File, } -/// Acquire an exclusive cross-process lock on `/clarion.lock`. +/// Acquire an exclusive cross-process lock on `/loomweave.lock`. /// -/// `clarion_dir` is the `.clarion/` directory inside the project root. The +/// `loomweave_dir` is the `.loomweave/` directory inside the project root. The /// lock file is created on first use (0-byte sentinel) and kept across /// runs. The returned guard holds the lock for its lifetime. /// /// # Errors /// -/// - The lock file cannot be opened (missing `.clarion/` directory, +/// - The lock file cannot be opened (missing `.loomweave/` directory, /// permission denied, filesystem read-only). -/// - Another `clarion analyze` process already holds the lock. Returns +/// - Another `loomweave analyze` process already holds the lock. Returns /// an error containing the lock-file path so the operator can identify /// the conflict. -pub(crate) fn acquire_analyze_lock(clarion_dir: &Path) -> Result { - let lock_path = clarion_dir.join(LOCK_FILE_NAME); +pub(crate) fn acquire_analyze_lock(loomweave_dir: &Path) -> Result { + let lock_path = loomweave_dir.join(LOCK_FILE_NAME); let file = OpenOptions::new() .read(true) .write(true) @@ -70,7 +70,7 @@ pub(crate) fn acquire_analyze_lock(clarion_dir: &Path) -> Result Result, - /// Install the bundled clarion-workflow skill pack into .claude/skills/. + /// Install the bundled loomweave-workflow skill pack into .claude/skills/. #[arg(long)] skills: bool, - /// Install the bundled clarion-workflow skill pack into .agents/skills/. + /// Install the bundled loomweave-workflow skill pack into .agents/skills/. #[arg(long)] codex_skills: bool, @@ -52,13 +52,13 @@ pub enum Command { #[arg(long)] hooks: bool, - /// Do everything: .clarion/ init + MCP config + skills + hooks. + /// Do everything: .loomweave/ init + MCP config + skills + hooks. #[arg(long)] all: bool, }, /// Run an analysis pass: walk the source tree, dispatch discovered plugins - /// to extract entities/edges, and persist results to `.clarion/clarion.db`. + /// to extract entities/edges, and persist results to `.loomweave/loomweave.db`. /// Re-runs are idempotent (UPSERT on `entities.id`). If no plugins are on /// `$PATH`, exits 0 with a WARN and status `skipped_no_plugins` — see /// `docs/operator/getting-started.md` Troubleshooting. @@ -67,7 +67,7 @@ pub enum Command { #[arg(default_value = ".")] path: PathBuf, - /// Path to clarion.yaml (default: project-root/clarion.yaml if present). + /// Path to loomweave.yaml (default: project-root/loomweave.yaml if present). #[arg(long)] config: Option, @@ -90,7 +90,7 @@ pub enum Command { /// of starting fresh) and emit findings to Filigree with /// `mark_unseen=false`, so re-emitting does not flip the prior run's /// findings to `unseen_in_latest` on the peer (REQ-FINDING-05). The - /// run id is the UUID a normal `clarion analyze` reports on completion. + /// run id is the UUID a normal `loomweave analyze` reports on completion. /// This re-walks the tree from scratch (it is not incremental recovery) /// and assumes the corpus is unchanged; findings that no longer fire are /// not pruned from the resumed run. @@ -98,12 +98,12 @@ pub enum Command { resume: Option, /// After emitting findings, ask Filigree to soft-archive its own - /// `unseen_in_latest` Clarion findings older than + /// `unseen_in_latest` Loomweave findings older than /// `integrations.filigree.prune_unseen_days` (default 30) /// (REQ-FINDING-06). Opt-in retention sweep; enrich-only — a Filigree /// outage or the integration being disabled never fails the run. The /// sweep is `scan_source`-scoped server-side, so it only touches - /// Clarion's findings. + /// Loomweave's findings. #[arg(long)] prune_unseen: bool, @@ -131,19 +131,19 @@ pub enum Command { /// `legis`'s read-API base URL (e.g. `http://127.0.0.1:8615`), enabling /// the WS9 git-rename provider seam (REQ-C-05). Enrich-only and /// capability-aware: the operative working-tree rename window stays on - /// Clarion's own git probe, so an unset or unreachable `legis` leaves - /// behaviour byte-identical. Omit to use Clarion's shell git source only. + /// Loomweave's own git probe, so an unset or unreachable `legis` leaves + /// behaviour byte-identical. Omit to use Loomweave's shell git source only. #[arg(long)] legis_url: Option, }, /// Run the MCP stdio server. Serve { - /// Project directory containing .clarion/clarion.db. + /// Project directory containing .loomweave/loomweave.db. #[arg(long, default_value = ".")] path: PathBuf, - /// Path to clarion.yaml (default: project-root/clarion.yaml if present). + /// Path to loomweave.yaml (default: project-root/loomweave.yaml if present). #[arg(long)] config: Option, }, @@ -169,7 +169,7 @@ pub enum Command { }, /// Verify (and optionally repair) the installed agent-orientation surfaces: - /// the `clarion-workflow` skill pack, the `SessionStart` hook, and the + /// the `loomweave-workflow` skill pack, the `SessionStart` hook, and the /// `.mcp.json` MCP registration. Prints a per-surface report plus the index /// snapshot; exits non-zero if any problem remains (usable as a CI / /// pre-commit gate). @@ -203,15 +203,15 @@ pub enum DoctorOutputFormat { #[derive(Subcommand)] pub enum DbCommand { - /// Take a consistent, WAL-safe online backup of `.clarion/clarion.db`. + /// Take a consistent, WAL-safe online backup of `.loomweave/loomweave.db`. /// /// Unlike `cp`, this captures outstanding WAL frames into a standalone - /// single-file copy, so it is safe to run during a live `clarion analyze`. + /// single-file copy, so it is safe to run during a live `loomweave analyze`. Backup { /// Destination file for the backup copy. output: PathBuf, - /// Project directory containing .clarion/clarion.db (default: current). + /// Project directory containing .loomweave/loomweave.db (default: current). #[arg(long, default_value = ".")] path: PathBuf, @@ -230,7 +230,7 @@ pub enum GuidanceCommand { /// `entity:`. Content comes from `--content`, else stdin (when /// piped) or `$EDITOR`/`$VISUAL`. Create { - /// Project directory containing .clarion/clarion.db (default: current). + /// Project directory containing .loomweave/loomweave.db (default: current). #[arg(long, default_value = ".")] path: PathBuf, @@ -267,7 +267,7 @@ pub enum GuidanceCommand { /// Edit a sheet's content in `$EDITOR`/`$VISUAL` (other properties, including /// `authored_at` and provenance, are preserved). Edit { - /// Project directory containing .clarion/clarion.db (default: current). + /// Project directory containing .loomweave/loomweave.db (default: current). #[arg(long, default_value = ".")] path: PathBuf, /// The guidance sheet id (`core:guidance:`). @@ -276,7 +276,7 @@ pub enum GuidanceCommand { /// Print a guidance sheet (human-readable). Show { - /// Project directory containing .clarion/clarion.db (default: current). + /// Project directory containing .loomweave/loomweave.db (default: current). #[arg(long, default_value = ".")] path: PathBuf, /// The guidance sheet id. @@ -290,7 +290,7 @@ pub enum GuidanceCommand { /// filter (including `--for-entity`). Without any of them, behaves as the /// plain list. List { - /// Project directory containing .clarion/clarion.db (default: current). + /// Project directory containing .loomweave/loomweave.db (default: current). #[arg(long, default_value = ".")] path: PathBuf, /// Only list sheets whose `match_rules` apply to this entity id. @@ -313,7 +313,7 @@ pub enum GuidanceCommand { /// Delete a guidance sheet. Delete { - /// Project directory containing .clarion/clarion.db (default: current). + /// Project directory containing .loomweave/loomweave.db (default: current). #[arg(long, default_value = ".")] path: PathBuf, /// The guidance sheet id. @@ -324,10 +324,10 @@ pub enum GuidanceCommand { /// guidance sheet. The observation must have been produced by MCP /// `propose_guidance`; arbitrary observations are rejected. Promote { - /// Project directory containing .clarion/clarion.db (default: current). + /// Project directory containing .loomweave/loomweave.db (default: current). #[arg(long, default_value = ".")] path: PathBuf, - /// Path to clarion.yaml (default: project-root/clarion.yaml if present). + /// Path to loomweave.yaml (default: project-root/loomweave.yaml if present). #[arg(long)] config: Option, /// The Filigree observation id to promote. @@ -339,7 +339,7 @@ pub enum GuidanceCommand { /// (REQ-GUIDANCE-06). Output is byte-stable across runs on identical DB /// state. The target directory is created if absent. Export { - /// Project directory containing .clarion/clarion.db (default: current). + /// Project directory containing .loomweave/loomweave.db (default: current). #[arg(long, default_value = ".")] path: PathBuf, /// Directory to write the exported sheet files into. Export does NOT @@ -356,7 +356,7 @@ pub enum GuidanceCommand { /// untouched (never a destructive mirror). A malformed `*.json` aborts the /// import naming the offending file (a dropped sheet is silent data loss). Import { - /// Project directory containing .clarion/clarion.db (default: current). + /// Project directory containing .loomweave/loomweave.db (default: current). #[arg(long, default_value = ".")] path: PathBuf, /// Directory of exported sheet files to import. @@ -368,7 +368,7 @@ pub enum GuidanceCommand { pub enum HookCommand { /// Print a project snapshot and re-sync the skill pack on drift. SessionStart { - /// Project directory containing .clarion/clarion.db. + /// Project directory containing .loomweave/loomweave.db. #[arg(long, default_value = ".")] path: PathBuf, }, @@ -386,7 +386,7 @@ pub enum SarifCommand { #[arg(long)] scan_source: Option, - /// Project directory containing .clarion/clarion.db (default: current). + /// Project directory containing .loomweave/loomweave.db (default: current). #[arg(long, default_value = ".")] path: PathBuf, }, diff --git a/crates/clarion-cli/src/config.rs b/crates/loomweave-cli/src/config.rs similarity index 97% rename from crates/clarion-cli/src/config.rs rename to crates/loomweave-cli/src/config.rs index b7f2a5f8..db23073e 100644 --- a/crates/clarion-cli/src/config.rs +++ b/crates/loomweave-cli/src/config.rs @@ -2,7 +2,7 @@ use std::fs; use std::path::Path; use anyhow::{Context, Result, bail, ensure}; -use clarion_analysis::ClusterAlgorithm; +use loomweave_analysis::ClusterAlgorithm; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] @@ -18,7 +18,7 @@ impl AnalyzeConfig { .with_context(|| format!("load analyze config {}", path.display())); } - let default_path = project_root.join("clarion.yaml"); + let default_path = project_root.join("loomweave.yaml"); if default_path.exists() { Self::from_path(&default_path) .with_context(|| format!("load analyze config {}", default_path.display())) diff --git a/crates/clarion-cli/src/db.rs b/crates/loomweave-cli/src/db.rs similarity index 88% rename from crates/clarion-cli/src/db.rs rename to crates/loomweave-cli/src/db.rs index 54bce6db..ec63a7cd 100644 --- a/crates/clarion-cli/src/db.rs +++ b/crates/loomweave-cli/src/db.rs @@ -1,11 +1,11 @@ -//! `clarion db` maintenance subcommands. +//! `loomweave db` maintenance subcommands. //! //! Currently a single verb: `backup`, an online, WAL-safe copy of -//! `.clarion/clarion.db` (gap-register STO-04 / clarion-6d433b61ba). +//! `.loomweave/loomweave.db` (gap-register STO-04 / clarion-6d433b61ba). //! //! Why an online backup rather than `cp`: the live database runs in WAL mode, -//! so committed pages live in `clarion.db-wal` separately from the main file. -//! A naive file copy taken during a `clarion analyze` produces a *torn* copy — +//! so committed pages live in `loomweave.db-wal` separately from the main file. +//! A naive file copy taken during a `loomweave analyze` produces a *torn* copy — //! the main file without its outstanding WAL frames. `rusqlite::backup::Backup` //! reads through a real connection, so it captures a transactionally consistent //! snapshot and writes it into a fresh single-file database (no WAL sidecar to @@ -17,7 +17,7 @@ use std::time::Duration; use anyhow::{Context, Result, anyhow, bail, ensure}; use rusqlite::{Connection, OpenFlags}; -/// Back up the project's `.clarion/clarion.db` to `output`. +/// Back up the project's `.loomweave/loomweave.db` to `output`. /// /// The copy is taken with `rusqlite::backup::Backup` (a consistent online /// snapshot) and staged into a sibling temp file that is renamed over `output` @@ -30,10 +30,10 @@ use rusqlite::{Connection, OpenFlags}; /// exists and `force` is not set, if `output` resolves to the source database /// itself, or if the backup / integrity check fails. pub fn backup(project_root: &Path, output: &Path, force: bool) -> Result<()> { - let db_path = project_root.join(".clarion").join("clarion.db"); + let db_path = project_root.join(".loomweave").join("loomweave.db"); ensure!( db_path.exists(), - "Clarion database not found at {}; run `clarion analyze` first", + "Loomweave database not found at {}; run `loomweave analyze` first", db_path.display() ); @@ -118,10 +118,10 @@ fn run_backup(db_path: &Path, staging: &Path) -> Result<()> { Ok(()) } -/// Sibling staging path for the atomic write (`.clarion-backup.tmp-`). +/// Sibling staging path for the atomic write (`.loomweave-backup.tmp-`). fn staging_path(output: &Path) -> std::path::PathBuf { let mut name = output.as_os_str().to_os_string(); - name.push(format!(".clarion-backup.tmp-{}", std::process::id())); + name.push(format!(".loomweave-backup.tmp-{}", std::process::id())); std::path::PathBuf::from(name) } diff --git a/crates/clarion-cli/src/doctor.rs b/crates/loomweave-cli/src/doctor.rs similarity index 80% rename from crates/clarion-cli/src/doctor.rs rename to crates/loomweave-cli/src/doctor.rs index 760581b3..aa6b0f96 100644 --- a/crates/clarion-cli/src/doctor.rs +++ b/crates/loomweave-cli/src/doctor.rs @@ -1,27 +1,26 @@ -//! `clarion doctor [--fix]` — verify (and optionally repair) the installed +//! `loomweave doctor [--fix]` — verify (and optionally repair) the installed //! agent-orientation surfaces. //! //! Three surfaces are checked, each owned by an existing installer module: -//! the `clarion-workflow` skill pack ([`crate::skill_pack`]), the `SessionStart` +//! the `loomweave-workflow` skill pack ([`crate::skill_pack`]), the `SessionStart` //! hook ([`crate::hooks_settings`]), and the Claude Code `.mcp.json` MCP //! registration ([`crate::mcp_registration`]), plus the local -//! Clarion/Filigree/Wardline binding files ([`crate::integration_bindings`]). +//! Loomweave/Filigree/Wardline binding files ([`crate::integration_bindings`]). //! The repair for each is that module's idempotent installer, so -//! `doctor --fix` and `clarion install` converge to the same state. +//! `doctor --fix` and `loomweave install` converge to the same state. //! //! Output is a per-surface ✓/⚠/✗ report followed by the index snapshot (reused //! verbatim from the session-start hook). [`run`] returns whether every surface //! is healthy *after* any repairs; the caller maps an unhealthy result to a //! non-zero exit so `doctor` is usable as a CI / pre-commit gate. //! -//! Severity is deliberate. The Loom three-way integration bindings are an -//! *enrich-only* surface (per `docs/suite/loom.md` §5): a Clarion-solo or -//! Clarion+Filigree-only project is first-class, so their absence is a +//! Severity is deliberate. The Weft three-way integration bindings are an +//! *enrich-only* surface (per `docs/suite/weft.md` §5): a Loomweave-solo or +//! Loomweave+Filigree-only project is first-class, so their absence is a //! **warning** (surfaced, suggests `--fix`) and never a problem that fails the //! gate. Only a genuinely broken state — an unparseable config file, or a //! `--fix` repair that errors or does not converge — is a problem. -use std::env; use std::fs; use std::path::Path; @@ -36,7 +35,7 @@ use crate::mcp_registration::McpState; use crate::skill_pack::SkillPackState; use crate::{hook, hooks_settings, integration_bindings, mcp_registration, skill_pack}; -/// Run `clarion doctor`. Returns `Ok(true)` iff every orientation surface is +/// Run `loomweave doctor`. Returns `Ok(true)` iff every orientation surface is /// healthy after any requested repairs. /// /// # Errors @@ -61,7 +60,7 @@ pub fn run(path: &Path, fix: bool, json_output: bool) -> Result { return Ok(report.ok); } - println!("clarion doctor{}", if fix { " --fix" } else { "" }); + println!("loomweave doctor{}", if fix { " --fix" } else { "" }); let mut tally = Tally::default(); tally += check_skill(&project_root, fix); @@ -150,7 +149,7 @@ impl DoctorJsonCheck { fn json_report(project_root: &Path, fix: bool) -> DoctorJsonReport { let mut checks = vec![ - check_clarion_dir_json(project_root), + check_loomweave_dir_json(project_root), check_index_freshness_json(project_root), check_plugin_availability_json(), check_skill_json(project_root, fix), @@ -167,14 +166,22 @@ fn json_report(project_root: &Path, fix: bool) -> DoctorJsonReport { .iter() .filter(|check| check.status == "problem" || check.status == "warning") .map(|check| match check.id { - "skill.pack" => "Run `clarion doctor --fix` or `clarion install --skills`.".to_owned(), + "skill.pack" => { + "Run `loomweave doctor --fix` or `loomweave install --skills`.".to_owned() + } "hook.session_start" => { - "Run `clarion doctor --fix` or `clarion install --hooks`.".to_owned() + "Run `loomweave doctor --fix` or `loomweave install --hooks`.".to_owned() + } + "mcp.registration" | "integration.bindings" => { + "Run `loomweave doctor --fix`.".to_owned() + } + "index.freshness" => { + "Run `loomweave analyze ` to refresh the index.".to_owned() } - "mcp.registration" | "integration.bindings" => "Run `clarion doctor --fix`.".to_owned(), - "index.freshness" => "Run `clarion analyze ` to refresh the index.".to_owned(), "plugin.availability" => { - "Install or expose Clarion language plugins on PATH.".to_owned() + "Install a Loomweave language plugin (the Python plugin ships with `pip install \ + loomweave`)." + .to_owned() } _ => format!("Review doctor check `{}`.", check.id), }) @@ -189,21 +196,21 @@ fn json_report(project_root: &Path, fix: bool) -> DoctorJsonReport { } } -fn check_clarion_dir_json(project_root: &Path) -> DoctorJsonCheck { - let clarion_dir = project_root.join(".clarion"); - let db = clarion_dir.join("clarion.db"); - if clarion_dir.is_dir() && db.is_file() { +fn check_loomweave_dir_json(project_root: &Path) -> DoctorJsonCheck { + let loomweave_dir = project_root.join(".loomweave"); + let db = loomweave_dir.join("loomweave.db"); + if loomweave_dir.is_dir() && db.is_file() { DoctorJsonCheck::ok( - ".clarion.schema", - ".clarion directory and database are present", + ".loomweave.schema", + ".loomweave directory and database are present", ) - } else if clarion_dir.is_dir() { + } else if loomweave_dir.is_dir() { DoctorJsonCheck::warning( - ".clarion.schema", - ".clarion directory exists but clarion.db is absent", + ".loomweave.schema", + ".loomweave directory exists but loomweave.db is absent", ) } else { - DoctorJsonCheck::warning(".clarion.schema", ".clarion directory is absent") + DoctorJsonCheck::warning(".loomweave.schema", ".loomweave directory is absent") } } @@ -220,30 +227,40 @@ fn check_index_freshness_json(project_root: &Path) -> DoctorJsonCheck { } fn check_plugin_availability_json() -> DoctorJsonCheck { - let Some(path) = env::var_os("PATH") else { - return DoctorJsonCheck::warning("plugin.availability", "PATH is unset"); - }; - for dir in env::split_paths(&path) { - let Ok(entries) = fs::read_dir(dir) else { - continue; - }; - for entry in entries.flatten() { - if entry - .file_name() - .to_string_lossy() - .starts_with("clarion-plugin-") - { - return DoctorJsonCheck::ok( - "plugin.availability", - "at least one clarion-plugin-* executable is visible on PATH", - ); - } + // Use the same discovery path as `loomweave analyze` (`$PATH` *and* the running + // binary's directory), so doctor agrees with analyze about which plugins are + // visible. A manual `$PATH`-only scan here would report a co-located + // PyPI/venv-installed plugin as missing even though analyze can drive it. + let mut ids = Vec::new(); + let mut errs = Vec::new(); + for result in loomweave_core::plugin::discover() { + match result { + Ok(plugin) => ids.push(plugin.manifest.plugin.plugin_id), + Err(err) => errs.push(err.to_string()), } } - DoctorJsonCheck::warning( - "plugin.availability", - "no clarion-plugin-* executable is visible on PATH", - ) + + if !ids.is_empty() { + let plural = if ids.len() == 1 { "" } else { "s" }; + DoctorJsonCheck::ok( + "plugin.availability", + format!( + "{} language plugin{plural} discovered: {}", + ids.len(), + ids.join(", ") + ), + ) + } else if !errs.is_empty() { + DoctorJsonCheck::warning( + "plugin.availability", + format!("plugin discovery reported errors: {}", errs.join("; ")), + ) + } else { + DoctorJsonCheck::warning( + "plugin.availability", + "no loomweave language plugin discovered (on PATH or alongside the loomweave binary)", + ) + } } fn check_skill_json(project_root: &Path, fix: bool) -> DoctorJsonCheck { @@ -321,17 +338,18 @@ fn check_hook_json(project_root: &Path, fix: bool) -> DoctorJsonCheck { fn check_mcp_json(project_root: &Path, fix: bool) -> DoctorJsonCheck { match mcp_registration::mcp_entry_state(project_root) { - McpState::Present => { - DoctorJsonCheck::ok("mcp.registration", ".mcp.json clarion serve entry present") - } + McpState::Present => DoctorJsonCheck::ok( + "mcp.registration", + ".mcp.json loomweave serve entry present", + ), McpState::Unparseable => { DoctorJsonCheck::problem("mcp.registration", ".mcp.json is not parseable JSON") } McpState::UntrustedCommand => { - let cmd = mcp_registration::clarion_entry_command(project_root) + let cmd = mcp_registration::loomweave_entry_command(project_root) .unwrap_or_else(|| "".to_owned()); let what = format!( - ".mcp.json clarion entry uses an unrecognized command {cmd:?} (not the clarion \ + ".mcp.json loomweave entry uses an unrecognized command {cmd:?} (not the loomweave \ executable); doctor will not auto-replace it" ); if !fix { @@ -347,8 +365,8 @@ fn check_mcp_json(project_root: &Path, fix: bool) -> DoctorJsonCheck { } state => { let what = match state { - McpState::Missing => ".mcp.json has no clarion serve entry", - McpState::Stale => ".mcp.json clarion entry is stale or not runtime-discovered", + McpState::Missing => ".mcp.json has no loomweave serve entry", + McpState::Stale => ".mcp.json loomweave entry is stale or not runtime-discovered", McpState::Present | McpState::Unparseable | McpState::UntrustedCommand => { unreachable!() } @@ -360,7 +378,7 @@ fn check_mcp_json(project_root: &Path, fix: bool) -> DoctorJsonCheck { Ok(_) if mcp_registration::mcp_entry_state(project_root) == McpState::Present => { DoctorJsonCheck::fixed( "mcp.registration", - format!("{what}; merged clarion serve entry"), + format!("{what}; merged loomweave serve entry"), ) } Ok(_) => DoctorJsonCheck::problem( @@ -377,8 +395,8 @@ fn check_mcp_json(project_root: &Path, fix: bool) -> DoctorJsonCheck { } fn check_http_config_json(project_root: &Path) -> DoctorJsonCheck { - let Some(config) = read_clarion_yaml(project_root) else { - return DoctorJsonCheck::warning("http.config", "clarion.yaml is absent or unparseable"); + let Some(config) = read_loomweave_yaml(project_root) else { + return DoctorJsonCheck::warning("http.config", "loomweave.yaml is absent or unparseable"); }; let enabled = config .get("serve") @@ -400,8 +418,8 @@ fn check_http_config_json(project_root: &Path) -> DoctorJsonCheck { } fn check_filigree_url_json(project_root: &Path) -> DoctorJsonCheck { - let Some(config) = read_clarion_yaml(project_root) else { - return DoctorJsonCheck::warning("filigree.url", "clarion.yaml is absent or unparseable"); + let Some(config) = read_loomweave_yaml(project_root) else { + return DoctorJsonCheck::warning("filigree.url", "loomweave.yaml is absent or unparseable"); }; let enabled = config .get("integrations") @@ -426,9 +444,9 @@ fn check_filigree_url_json(project_root: &Path) -> DoctorJsonCheck { } fn check_sei_population_json(project_root: &Path) -> DoctorJsonCheck { - let db = project_root.join(".clarion/clarion.db"); + let db = project_root.join(".loomweave/loomweave.db"); let Ok(conn) = Connection::open(&db) else { - return DoctorJsonCheck::warning("sei.population", "clarion.db is absent or unreadable"); + return DoctorJsonCheck::warning("sei.population", "loomweave.db is absent or unreadable"); }; let count: rusqlite::Result = conn.query_row( "SELECT COUNT(*) FROM sei_bindings WHERE status = 'alive'", @@ -448,10 +466,10 @@ fn check_sei_population_json(project_root: &Path) -> DoctorJsonCheck { } fn check_wardline_taint_capability_json(project_root: &Path) -> DoctorJsonCheck { - let Some(config) = read_clarion_yaml(project_root) else { + let Some(config) = read_loomweave_yaml(project_root) else { return DoctorJsonCheck::warning( "wardline.taint_store", - "clarion.yaml is absent or unparseable", + "loomweave.yaml is absent or unparseable", ); }; if config @@ -484,7 +502,7 @@ fn check_integration_bindings_json(project_root: &Path, fix: bool) -> DoctorJson match integration_bindings::binding_state(project_root) { BindingState::Present => DoctorJsonCheck::ok( "integration.bindings", - "three-way integration bindings present (Clarion + Filigree + Wardline)", + "three-way integration bindings present (Loomweave + Filigree + Wardline)", ), BindingState::Unparseable => DoctorJsonCheck::problem( "integration.bindings", @@ -516,8 +534,8 @@ fn check_integration_bindings_json(project_root: &Path, fix: bool) -> DoctorJson } } -fn read_clarion_yaml(project_root: &Path) -> Option { - let raw = fs::read_to_string(project_root.join("clarion.yaml")).ok()?; +fn read_loomweave_yaml(project_root: &Path) -> Option { + let raw = fs::read_to_string(project_root.join("loomweave.yaml")).ok()?; serde_norway::from_str(&raw).ok() } @@ -579,7 +597,7 @@ fn check_skill(project_root: &Path, fix: bool) -> Tally { if !fix { return problem( &format!("skill pack {what}"), - Some("clarion install --skills"), + Some("loomweave install --skills"), ); } match skill_pack::install_skill_pack(project_root) { @@ -615,7 +633,7 @@ fn check_hook(project_root: &Path, fix: bool) -> Tally { HookState::Present | HookState::Unparseable => unreachable!(), }; if !fix { - return problem(what, Some("clarion install --hooks")); + return problem(what, Some("loomweave install --hooks")); } match hooks_settings::install_session_start_hook(project_root) { Ok(_) @@ -633,16 +651,16 @@ fn check_hook(project_root: &Path, fix: bool) -> Tally { fn check_mcp(project_root: &Path, fix: bool) -> Tally { match mcp_registration::mcp_entry_state(project_root) { - McpState::Present => ok(".mcp.json clarion serve entry present"), + McpState::Present => ok(".mcp.json loomweave serve entry present"), McpState::Unparseable => problem( ".mcp.json is not parseable JSON — fix it by hand, then re-run", None, ), McpState::UntrustedCommand => { - let cmd = mcp_registration::clarion_entry_command(project_root) + let cmd = mcp_registration::loomweave_entry_command(project_root) .unwrap_or_else(|| "".to_owned()); let what = format!( - ".mcp.json clarion entry uses an unrecognized command {cmd:?} (not the clarion \ + ".mcp.json loomweave entry uses an unrecognized command {cmd:?} (not the loomweave \ executable); doctor will not auto-replace it" ); if !fix { @@ -650,7 +668,7 @@ fn check_mcp(project_root: &Path, fix: bool) -> Tally { &what, Some( "if this is a deliberate wrapper, leave it; otherwise set `command` to \ - `clarion` or remove the entry — `--fix` will not clobber it", + `loomweave` or remove the entry — `--fix` will not clobber it", ), ); } @@ -665,8 +683,8 @@ fn check_mcp(project_root: &Path, fix: bool) -> Tally { } state => { let what = match state { - McpState::Missing => ".mcp.json has no clarion serve entry", - McpState::Stale => ".mcp.json clarion entry is stale or not runtime-discovered", + McpState::Missing => ".mcp.json has no loomweave serve entry", + McpState::Stale => ".mcp.json loomweave entry is stale or not runtime-discovered", McpState::Present | McpState::Unparseable | McpState::UntrustedCommand => { unreachable!() } @@ -674,12 +692,12 @@ fn check_mcp(project_root: &Path, fix: bool) -> Tally { if !fix { return problem( what, - Some("clarion doctor --fix (or add the entry to .mcp.json manually)"), + Some("loomweave doctor --fix (or add the entry to .mcp.json manually)"), ); } match mcp_registration::install_mcp_entry(project_root) { Ok(_) if mcp_registration::mcp_entry_state(project_root) == McpState::Present => { - ok(&format!("{what} — fixed (merged clarion serve entry)")) + ok(&format!("{what} — fixed (merged loomweave serve entry)")) } Ok(_) => problem(&format!("{what} — repair did not converge"), None), Err(err) => problem(&format!("{what} — repair failed: {err}"), None), @@ -691,7 +709,7 @@ fn check_mcp(project_root: &Path, fix: bool) -> Tally { fn check_integration_bindings(project_root: &Path, fix: bool) -> Tally { match integration_bindings::binding_state(project_root) { BindingState::Present => { - ok("three-way integration bindings present (Clarion + Filigree + Wardline)") + ok("three-way integration bindings present (Loomweave + Filigree + Wardline)") } BindingState::Unparseable => problem( "three-way integration bindings are not parseable — fix config files by hand, then re-run", @@ -701,7 +719,7 @@ fn check_integration_bindings(project_root: &Path, fix: bool) -> Tally { let what = "three-way integration bindings missing or stale"; if !fix { // Enrich-only surface: absence is a warning, not a gate failure. - return warn(what, Some("clarion doctor --fix")); + return warn(what, Some("loomweave doctor --fix")); } match integration_bindings::install_bindings(project_root) { Ok(_) diff --git a/crates/clarion-cli/src/guidance.rs b/crates/loomweave-cli/src/guidance.rs similarity index 96% rename from crates/clarion-cli/src/guidance.rs rename to crates/loomweave-cli/src/guidance.rs index dec40653..88feb23f 100644 --- a/crates/clarion-cli/src/guidance.rs +++ b/crates/loomweave-cli/src/guidance.rs @@ -1,9 +1,9 @@ -//! `clarion guidance` authoring subcommands (WS6 / REQ-GUIDANCE-03). +//! `loomweave guidance` authoring subcommands (WS6 / REQ-GUIDANCE-03). //! //! Operator-facing CLI to create, edit, show, list, and delete guidance sheets //! — the institutional-knowledge entities (`kind = 'guidance'`) the MCP read -//! path composes into briefings. All SQL lives in `clarion-storage` -//! (`clarion_storage::guidance`); this module owns only argument parsing, the +//! path composes into briefings. All SQL lives in `loomweave-storage` +//! (`loomweave_storage::guidance`); this module owns only argument parsing, the //! `$EDITOR` round-trip, and presentation. //! //! ## `--match` syntax @@ -27,8 +27,8 @@ use anyhow::{Context, Result, anyhow, bail}; use rusqlite::{Connection, OpenFlags}; use serde_json::{Value, json}; -use clarion_federation::filigree::{FiligreeHttpClient, FiligreeLookup}; -use clarion_storage::{ +use loomweave_federation::filigree::{FiligreeHttpClient, FiligreeLookup}; +use loomweave_storage::{ GuidanceProposal, GuidanceSheet, GuidanceSheetInput, PortableSheet, delete_guidance_sheet, get_guidance_sheet, guidance_sheet_is_expired, guidance_sheet_is_stale, guidance_sheet_matches_entity, import_portable_sheet, insert_guidance_sheet, @@ -37,14 +37,14 @@ use clarion_storage::{ use crate::cli::GuidanceCommand; -/// Map a `clarion_storage::StorageError` (which is `Send` but not `Sync`, so it +/// Map a `loomweave_storage::StorageError` (which is `Send` but not `Sync`, so it /// does not satisfy `anyhow`'s `From` bound) into an `anyhow::Error` via its /// Display — matching the convention in `analyze.rs`. trait StorageResultExt { fn into_anyhow(self) -> Result; } -impl StorageResultExt for clarion_storage::Result { +impl StorageResultExt for loomweave_storage::Result { fn into_anyhow(self) -> Result { self.map_err(|e| anyhow!("{e}")) } @@ -63,7 +63,7 @@ const SCOPE_LEVELS: &[&str] = &[ const PROVENANCE_MANUAL: &str = "manual"; -/// Dispatch a `clarion guidance `. +/// Dispatch a `loomweave guidance `. /// /// # Errors /// @@ -392,7 +392,7 @@ fn show(project_root: &Path, id: &str) -> Result<()> { Ok(()) } -/// Filters for `clarion guidance list`. All active filters compose by +/// Filters for `loomweave guidance list`. All active filters compose by /// **intersection** (AND): a sheet is shown only if it passes every one. The /// two date filters (`expired`, `stale`) are independent — combining them shows /// sheets that are expired AND stale, which is the intuitive "show me the worst @@ -419,7 +419,7 @@ fn list(project_root: &Path, filters: ListFilters<'_>) -> Result<()> { // storage predicates' lexical compares are valid instant compares. `now` // drives `--expired`; `stale_before` (now − N days) drives `--stale`. NB: // `--stale` here is the *age/review-cadence* signal (system-design §7.741), - // distinct from the churn-based `CLA-FACT-GUIDANCE-CHURN-STALE` finding. + // distinct from the churn-based `LMWV-FACT-GUIDANCE-CHURN-STALE` finding. let now = now_iso8601(&conn)?; let stale_before = if filters.stale { Some(now_minus_days(&conn, filters.days)?) @@ -529,7 +529,7 @@ fn promote(project_root: &Path, config_path: Option<&Path>, observation_id: &str Some(&canonical_root), ) .context("build Filigree client")? - .ok_or_else(|| anyhow!("Filigree integration is disabled in clarion.yaml"))?; + .ok_or_else(|| anyhow!("Filigree integration is disabled in loomweave.yaml"))?; let observation = client .observation_by_id(observation_id) @@ -538,7 +538,7 @@ fn promote(project_root: &Path, config_path: Option<&Path>, observation_id: &str let proposal = GuidanceProposal::from_observation_detail(&observation.detail) .map_err(|e| anyhow!("{e}")) .with_context(|| { - format!("Filigree observation {observation_id} is not a Clarion guidance proposal") + format!("Filigree observation {observation_id} is not a Loomweave guidance proposal") })?; let conn = open_db(&canonical_root)?; @@ -569,7 +569,7 @@ fn promote(project_root: &Path, config_path: Option<&Path>, observation_id: &str }; let dismissed = client - .dismiss_observation(observation_id, "promoted to Clarion guidance sheet") + .dismiss_observation(observation_id, "promoted to Loomweave guidance sheet") .is_ok(); println!("Promoted observation {observation_id} to {}", promoted.id); report_invalidation(invalidated); @@ -749,14 +749,14 @@ fn render_sheet(sheet: &GuidanceSheet) -> String { // ── I/O helpers ─────────────────────────────────────────────────────────────── -/// Open a read-write connection to `.clarion/clarion.db` with a generous busy +/// Open a read-write connection to `.loomweave/loomweave.db` with a generous busy /// timeout so a concurrently-running `serve` writer does not cause an immediate /// lock error. fn open_db(project_root: &Path) -> Result { - let db_path = project_root.join(".clarion").join("clarion.db"); + let db_path = project_root.join(".loomweave").join("loomweave.db"); if !db_path.exists() { bail!( - "Clarion database not found at {}; run `clarion analyze` first", + "Loomweave database not found at {}; run `loomweave analyze` first", db_path.display() ); } @@ -793,7 +793,7 @@ fn edit_in_editor(seed: &str) -> Result { .map_err(|_| anyhow!("neither $VISUAL nor $EDITOR is set; set one or pass --content"))?; let dir = std::env::temp_dir(); - let file = dir.join(format!("clarion-guidance-{}.md", std::process::id())); + let file = dir.join(format!("loomweave-guidance-{}.md", std::process::id())); { let mut f = std::fs::File::create(&file) .with_context(|| format!("create temp edit file {}", file.display()))?; @@ -844,7 +844,7 @@ fn now_iso8601(conn: &Connection) -> Result { /// Normalise an `--expires` value to a UTC instant in the exact /// `YYYY-MM-DDTHH:MM:SS.mmmZ` shape the read path compares against. The expiry -/// check (`crates/clarion-mcp/src/catalogue/inspection.rs`) is a *lexical* +/// check (`crates/loomweave-mcp/src/catalogue/inspection.rs`) is a *lexical* /// `expires < now` compare, so the stored string must be byte-format-identical /// to `now`: same UTC zone (`Z`), same 3-digit subsecond, same length. We run /// the input through the connection's own `strftime`, which: diff --git a/crates/clarion-cli/src/hook.rs b/crates/loomweave-cli/src/hook.rs similarity index 76% rename from crates/clarion-cli/src/hook.rs rename to crates/loomweave-cli/src/hook.rs index 8aa8ce19..38d00141 100644 --- a/crates/clarion-cli/src/hook.rs +++ b/crates/loomweave-cli/src/hook.rs @@ -1,14 +1,14 @@ -//! `clarion hook session-start` — fail-soft session-start orientation. +//! `loomweave hook session-start` — fail-soft session-start orientation. //! //! Never returns an error to the caller: the `SessionStart` hook must never //! block an agent's session start. All failures degrade to a printed note. use std::path::Path; -use clarion_mcp::snapshot::{ProjectSnapshot, Staleness, missing_db_snapshot, project_snapshot}; +use loomweave_mcp::snapshot::{ProjectSnapshot, Staleness, missing_db_snapshot, project_snapshot}; use rusqlite::{Connection, OpenFlags}; -/// Run `clarion hook session-start`. Always returns `Ok(())`. +/// Run `loomweave hook session-start`. Always returns `Ok(())`. /// /// The `anyhow::Result` return type is intentional even though no `Err` is /// ever produced: it keeps the `main.rs` dispatch arm uniform with the other @@ -17,7 +17,7 @@ use rusqlite::{Connection, OpenFlags}; pub fn session_start(path: &Path) -> anyhow::Result<()> { // (1) Re-sync the skill pack ONLY if it's already installed in at least one // skill root, and drifted. A bare session-start never bootstraps a - // never-installed project — that's `clarion install --skills`'s job. Note + // never-installed project — that's `loomweave install --skills`'s job. Note // the resync normalises BOTH roots once triggered: if a project installed // only `.claude/skills`, a drift repair also (re)creates // `.agents/skills`, keeping the two roots in lock-step. A drift repair @@ -30,11 +30,11 @@ pub fn session_start(path: &Path) -> anyhow::Result<()> { Ok(()) } -/// What [`load_snapshot`] could establish about the `.clarion/` index. +/// What [`load_snapshot`] could establish about the `.loomweave/` index. /// /// A *missing* db and a *present-but-unreadable* db are deliberately distinct: /// the missing case nudges toward `install` + `analyze`, but that advice is -/// wrong for a present-but-corrupt/locked db (`install` refuses while `.clarion/` +/// wrong for a present-but-corrupt/locked db (`install` refuses while `.loomweave/` /// exists; `analyze` cannot repair corruption). See [`print_snapshot`]. enum SnapshotOutcome { /// Either the db file is absent (a `missing_db_snapshot()`) or it opened and @@ -47,28 +47,28 @@ enum SnapshotOutcome { fn resync_skill_if_present(project_root: &Path) { let installed = project_root - .join(".claude/skills/clarion-workflow/SKILL.md") + .join(".claude/skills/loomweave-workflow/SKILL.md") .exists() || project_root - .join(".agents/skills/clarion-workflow/SKILL.md") + .join(".agents/skills/loomweave-workflow/SKILL.md") .exists(); if !installed { return; } if let Err(err) = crate::skill_pack::install_skill_pack(project_root) { - tracing::warn!(error = %err, "clarion-workflow skill resync failed"); + tracing::warn!(error = %err, "loomweave-workflow skill resync failed"); } } fn load_snapshot(project_root: &Path) -> SnapshotOutcome { - let db_path = project_root.join(".clarion").join("clarion.db"); + let db_path = project_root.join(".loomweave").join("loomweave.db"); if !db_path.exists() { return SnapshotOutcome::Ready(missing_db_snapshot()); } let conn = match Connection::open_with_flags(&db_path, OpenFlags::SQLITE_OPEN_READ_ONLY) { Ok(conn) => conn, Err(err) => { - tracing::warn!(error = %err, "open .clarion/clarion.db read-only failed"); + tracing::warn!(error = %err, "open .loomweave/loomweave.db read-only failed"); return SnapshotOutcome::DbUnreadable; } }; @@ -78,7 +78,7 @@ fn load_snapshot(project_root: &Path) -> SnapshotOutcome { // db is classified as unreadable rather than silently reported as 0 counts // (which would otherwise print the wrong "no analysis yet" nudge). if let Err(err) = conn.query_row("PRAGMA schema_version", [], |row| row.get::<_, i64>(0)) { - tracing::warn!(error = %err, "probe read of .clarion/clarion.db failed"); + tracing::warn!(error = %err, "probe read of .loomweave/loomweave.db failed"); return SnapshotOutcome::DbUnreadable; } let root = project_root @@ -94,7 +94,7 @@ fn print_snapshot(project_root: &Path, outcome: &SnapshotOutcome) { } /// Load the index snapshot and render it to lines, for reuse by both the -/// `SessionStart` hook (which prints them) and `clarion doctor` (which appends +/// `SessionStart` hook (which prints them) and `loomweave doctor` (which appends /// them under an `--- index ---` heading). Fail-soft: a missing/unreadable db /// yields an advisory line, never an error. #[must_use] @@ -111,12 +111,12 @@ fn snapshot_outcome_lines(project_root: &Path, outcome: &SnapshotOutcome) -> Vec let snapshot = match outcome { SnapshotOutcome::Ready(snapshot) => snapshot, SnapshotOutcome::DbUnreadable => { - let db_path = project_root.join(".clarion").join("clarion.db"); + let db_path = project_root.join(".loomweave").join("loomweave.db"); lines.push(format!( - "Clarion: an index exists at {} but could not be opened (it may be \ + "Loomweave: an index exists at {} but could not be opened (it may be \ corrupt, locked by another process, or unreadable). Check permissions, \ - ensure no other clarion process holds it, or remove .clarion/ and re-run \ - `clarion install` + `clarion analyze`. (Run with RUST_LOG=warn for the \ + ensure no other loomweave process holds it, or remove .loomweave/ and re-run \ + `loomweave install` + `loomweave analyze`. (Run with RUST_LOG=warn for the \ open error.)", db_path.display() )); @@ -125,8 +125,8 @@ fn snapshot_outcome_lines(project_root: &Path, outcome: &SnapshotOutcome) -> Vec }; if !snapshot.db_present() { lines.push(format!( - "Clarion: no index at {}/.clarion/clarion.db. \ - Run `clarion install --path {}` then `clarion analyze {}`.", + "Loomweave: no index at {}/.loomweave/loomweave.db. \ + Run `loomweave install --path {}` then `loomweave analyze {}`.", project_root.display(), project_root.display(), project_root.display() @@ -137,7 +137,7 @@ fn snapshot_outcome_lines(project_root: &Path, outcome: &SnapshotOutcome) -> Vec // subset of entity_count, not a parallel category — say so, or the two read // as disjoint (clarion-e4e80eff3f). lines.push(format!( - "Clarion index: {} entities (incl. {} subsystems), {} findings.", + "Loomweave index: {} entities (incl. {} subsystems), {} findings.", snapshot.entity_count(), snapshot.subsystem_count(), snapshot.finding_count() @@ -147,7 +147,7 @@ fn snapshot_outcome_lines(project_root: &Path, outcome: &SnapshotOutcome) -> Vec // understate a populated index. Distinct from the present-but-empty // case (which is not degraded). Operator detail is in the warn log. lines.push( - "Clarion: ⚠ snapshot is degraded — at least one index query failed and \ + "Loomweave: ⚠ snapshot is degraded — at least one index query failed and \ the counts above may be incomplete. (Run with RUST_LOG=warn for details.)" .to_string(), ); @@ -155,21 +155,21 @@ fn snapshot_outcome_lines(project_root: &Path, outcome: &SnapshotOutcome) -> Vec match snapshot.staleness() { Staleness::Fresh => { lines.push(format!( - "Index is fresh (last analyzed {}). Ask Clarion before re-exploring \ - the tree; see the clarion-workflow skill.", + "Index is fresh (last analyzed {}). Ask Loomweave before re-exploring \ + the tree; see the loomweave-workflow skill.", snapshot.last_analyzed_at().unwrap_or("unknown") )); } Staleness::Stale => { lines.push(format!( "Index may be stale: source files changed since the last run. \ - Run `clarion analyze {}` to refresh.", + Run `loomweave analyze {}` to refresh.", project_root.display() )); } Staleness::NeverAnalyzed => { lines.push(format!( - "No analysis recorded yet. Run `clarion analyze {}` to build the index.", + "No analysis recorded yet. Run `loomweave analyze {}` to build the index.", project_root.display() )); } @@ -184,7 +184,7 @@ fn snapshot_outcome_lines(project_root: &Path, outcome: &SnapshotOutcome) -> Vec Staleness::Unknown => { lines.push(format!( "Index freshness unknown (a freshness check failed). If briefings \ - look empty, run `clarion analyze {}`.", + look empty, run `loomweave analyze {}`.", project_root.display() )); } diff --git a/crates/clarion-cli/src/hooks_settings.rs b/crates/loomweave-cli/src/hooks_settings.rs similarity index 86% rename from crates/clarion-cli/src/hooks_settings.rs rename to crates/loomweave-cli/src/hooks_settings.rs index d1a1317e..bfabb80c 100644 --- a/crates/clarion-cli/src/hooks_settings.rs +++ b/crates/loomweave-cli/src/hooks_settings.rs @@ -1,9 +1,9 @@ //! `.claude/settings.json` SessionStart-hook merge. //! //! Merge semantics (never clobber): parse existing JSON and ensure exactly one -//! `SessionStart` matcher-group runs `clarion hook session-start --path +//! `SessionStart` matcher-group runs `loomweave hook session-start --path //! ` (the project path POSIX-single-quote-escaped for the shell). -//! Clarion-owned hooks are canonicalised — the first is refreshed to the +//! Loomweave-owned hooks are canonicalised — the first is refreshed to the //! desired command and any extras (a stale duplicate, or one pinned to a //! different project) are removed. Every other key is preserved. //! @@ -16,19 +16,19 @@ use std::path::Path; use anyhow::{Context, Result, bail}; use serde_json::{Map, Value, json}; -/// Substring that identifies Clarion's own `SessionStart` hook command. -pub const HOOK_COMMAND: &str = "clarion hook session-start"; +/// Substring that identifies Loomweave's own `SessionStart` hook command. +pub const HOOK_COMMAND: &str = "loomweave hook session-start"; -/// Read-only health of the installed `SessionStart` hook, for `clarion doctor`. +/// Read-only health of the installed `SessionStart` hook, for `loomweave doctor`. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum HookState { - /// Exactly one Clarion hook is present and runs the command this project + /// Exactly one Loomweave hook is present and runs the command this project /// would install ([`desired_hook_command`]). Present, - /// A Clarion hook exists but is stale — the old path-less form, one pinned + /// A Loomweave hook exists but is stale — the old path-less form, one pinned /// to a different project, or a duplicate. Repairable in place. Stale, - /// No `.claude/settings.json`, or it has no Clarion `SessionStart` hook. + /// No `.claude/settings.json`, or it has no Loomweave `SessionStart` hook. Missing, /// `.claude/settings.json` exists but is not parseable JSON. The merge /// refuses to clobber it, so this cannot be auto-repaired. @@ -36,7 +36,7 @@ pub enum HookState { } /// The `SessionStart` hook command this project would install: a bare -/// `clarion hook session-start` (PATH-resolved) pinned to the absolute project +/// `loomweave hook session-start` (PATH-resolved) pinned to the absolute project /// path (POSIX-single-quote-escaped). Shared by the installer and the /// `doctor` state check so the two never disagree on what "current" means. #[must_use] @@ -47,14 +47,14 @@ pub fn desired_hook_command(project_root: &Path) -> String { .canonicalize() .unwrap_or_else(|_| project_root.to_path_buf()); format!( - "clarion hook session-start --path {}", + "loomweave hook session-start --path {}", shell_single_quote(&canonical.display().to_string()) ) } /// Every `command` string across all `SessionStart` groups that looks like a -/// Clarion-owned hook (contains [`HOOK_COMMAND`]). -fn clarion_commands(settings: &Value) -> Vec { +/// Loomweave-owned hook (contains [`HOOK_COMMAND`]). +fn loomweave_commands(settings: &Value) -> Vec { settings["hooks"]["SessionStart"] .as_array() .into_iter() @@ -68,7 +68,7 @@ fn clarion_commands(settings: &Value) -> Vec { } /// Classify the installed `SessionStart` hook without writing anything, for -/// `clarion doctor`. The repair for `Missing`/`Stale` is the idempotent +/// `loomweave doctor`. The repair for `Missing`/`Stale` is the idempotent /// [`install_session_start_hook`]; `Unparseable` must be fixed by hand. #[must_use] pub fn session_start_hook_state(project_root: &Path) -> HookState { @@ -82,7 +82,7 @@ pub fn session_start_hook_state(project_root: &Path) -> HookState { let Ok(settings) = serde_json::from_str::(&raw) else { return HookState::Unparseable; }; - let cmds = clarion_commands(&settings); + let cmds = loomweave_commands(&settings); if cmds.is_empty() { HookState::Missing } else if cmds.len() == 1 && cmds[0] == desired_hook_command(project_root) { @@ -113,16 +113,16 @@ fn shell_single_quote(s: &str) -> String { out } -/// Merge Clarion's `SessionStart` hook into a parsed settings `Value` in place, +/// Merge Loomweave's `SessionStart` hook into a parsed settings `Value` in place, /// inserting the supplied `command` (which must contain [`HOOK_COMMAND`] so it -/// is recognised as Clarion-owned). Returns `true` if a change was made. +/// is recognised as Loomweave-owned). Returns `true` if a change was made. /// -/// Clarion-owned entries are keyed on the [`HOOK_COMMAND`] substring. The merge +/// Loomweave-owned entries are keyed on the [`HOOK_COMMAND`] substring. The merge /// canonicalises them to exactly one hook running `command`: the first is /// refreshed and any extras (a stale duplicate, or a hook pinned to a different /// project — possible in hand-merged settings) are removed, dropping any -/// Clarion-dedicated group left empty. If none exists, the hook is appended. -/// Returns `false` only when a single Clarion hook already runs `command` (the +/// Loomweave-dedicated group left empty. If none exists, the hook is appended. +/// Returns `false` only when a single Loomweave hook already runs `command` (the /// idempotent re-install case); otherwise `true`. #[must_use] pub fn merge_session_start_hook(settings: &mut Value, command: &str) -> bool { @@ -160,11 +160,11 @@ pub fn merge_session_start_hook(settings: &mut Value, command: &str) -> bool { tracing::warn!( "malformed .claude/settings.json shape (non-object settings/hooks or \ non-array SessionStart) was rewritten to the expected shape before \ - merging the clarion SessionStart hook" + merging the loomweave SessionStart hook" ); } - // Locate every Clarion-owned hook (its command contains HOOK_COMMAND), + // Locate every Loomweave-owned hook (its command contains HOOK_COMMAND), // across all matcher-groups. Pass 1 only reads, so the immutable borrow is // released before any mutation below. let mut locations: Vec<(usize, usize)> = Vec::new(); @@ -194,12 +194,12 @@ pub fn merge_session_start_hook(settings: &mut Value, command: &str) -> bool { return true; } - // Canonicalise to exactly one Clarion hook running `command`: refresh the + // Canonicalise to exactly one Loomweave hook running `command`: refresh the // first, then remove any extras (a stale duplicate, or a hook pinned to a // different project — e.g. hand-merged settings). This delivers "don't // no-op on a stale hook, don't leave duplicates, don't silently keep a // wrong-project pin" even when a current and a stale entry coexist or - // multiple stale entries exist. Returns `false` only when a single Clarion + // multiple stale entries exist. Returns `false` only when a single Loomweave // hook already runs `command` (the idempotent re-install case). let mut changed = false; let (kg, kh) = locations[0]; @@ -219,7 +219,7 @@ pub fn merge_session_start_hook(settings: &mut Value, command: &str) -> bool { changed = true; } } - // Drop any Clarion-dedicated group we just emptied (descending to keep + // Drop any Loomweave-dedicated group we just emptied (descending to keep // indices valid). A group still holding unrelated hooks is left intact. for gi in touched_groups.into_iter().rev() { if groups[gi]["hooks"] @@ -233,7 +233,7 @@ pub fn merge_session_start_hook(settings: &mut Value, command: &str) -> bool { } /// Read `.claude/settings.json` under `project_root` (creating an empty object -/// if absent), merge Clarion's `SessionStart` hook, and write it back +/// if absent), merge Loomweave's `SessionStart` hook, and write it back /// pretty-printed. Returns `true` if the file changed. /// /// # Errors @@ -332,11 +332,11 @@ mod tests { use serde_json::json; use super::{ - HOOK_COMMAND, HookState, clarion_commands, install_session_start_hook, + HOOK_COMMAND, HookState, install_session_start_hook, loomweave_commands, merge_session_start_hook, session_start_hook_state, }; - const TEST_COMMAND: &str = "clarion hook session-start --path \"/some/project\""; + const TEST_COMMAND: &str = "loomweave hook session-start --path \"/some/project\""; #[cfg(unix)] fn sh_roundtrip(quoted: &str) -> String { @@ -355,7 +355,7 @@ mod tests { // with shell metacharacters must survive as a single literal argument, // never expanded or split. Double-quote wrapping (the prior form) lets // $, backtick, and \ act; single-quote escaping does not. Prove the - // helper round-trips through `sh` exactly. (clarion review #5) + // helper round-trips through `sh` exactly. (loomweave review #5) for s in [ "/plain/path", "/with space/x", @@ -401,20 +401,20 @@ mod tests { } #[test] - fn refreshes_a_stale_clarion_hook_in_place() { - // A previously-installed Clarion hook (e.g. the old path-less form, or + fn refreshes_a_stale_loomweave_hook_in_place() { + // A previously-installed Loomweave hook (e.g. the old path-less form, or // one pinned to a different project) must be refreshed to the desired // command on re-install, not left stale. The idempotency check keys on // the HOOK_COMMAND substring, so a stale entry used to no-op forever. - // (clarion review #10) + // (loomweave review #10) let mut settings = json!({ "hooks": {"SessionStart": [ - {"hooks": [{"type": "command", "command": "clarion hook session-start"}]} + {"hooks": [{"type": "command", "command": "loomweave hook session-start"}]} ]} }); - let desired = "clarion hook session-start --path '/proj'"; + let desired = "loomweave hook session-start --path '/proj'"; let changed = merge_session_start_hook(&mut settings, desired); - assert!(changed, "a stale Clarion hook must be refreshed"); + assert!(changed, "a stale Loomweave hook must be refreshed"); let groups = settings["hooks"]["SessionStart"].as_array().unwrap(); assert_eq!( groups.len(), @@ -437,16 +437,16 @@ mod tests { fn refreshes_a_stale_hook_pinned_to_a_different_path() { // The realistic re-install case: the repo moved, so the existing hook // pins the old project path. It must be refreshed to the new path, not - // no-oped. (clarion review #4/#10) + // no-oped. (loomweave review #4/#10) let mut settings = json!({ "hooks": {"SessionStart": [ {"hooks": [{"type": "command", - "command": "clarion hook session-start --path '/old/proj'"}]} + "command": "loomweave hook session-start --path '/old/proj'"}]} ]} }); - let desired = "clarion hook session-start --path '/new/proj'"; + let desired = "loomweave hook session-start --path '/new/proj'"; assert!(merge_session_start_hook(&mut settings, desired)); - assert_eq!(clarion_commands(&settings), vec![desired.to_string()]); + assert_eq!(loomweave_commands(&settings), vec![desired.to_string()]); } #[test] @@ -454,14 +454,14 @@ mod tests { // A current hook coexisting with a stale one pinned to a different // project (e.g. hand-merged settings). The stale one silently orients // the wrong project every session, so it must be reconciled away — - // leaving exactly one Clarion hook running the desired command. - // (clarion review #10 — found_current must not short-circuit the sweep) - let desired = "clarion hook session-start --path '/proj'"; + // leaving exactly one Loomweave hook running the desired command. + // (loomweave review #10 — found_current must not short-circuit the sweep) + let desired = "loomweave hook session-start --path '/proj'"; let mut settings = json!({ "hooks": {"SessionStart": [ {"hooks": [{"type": "command", "command": desired}]}, {"hooks": [{"type": "command", - "command": "clarion hook session-start --path '/other'"}]} + "command": "loomweave hook session-start --path '/other'"}]} ]} }); assert!( @@ -469,26 +469,26 @@ mod tests { "a stale entry coexisting with the current one must be reconciled" ); assert_eq!( - clarion_commands(&settings), + loomweave_commands(&settings), vec![desired.to_string()], - "exactly one Clarion hook must remain, running the desired command" + "exactly one Loomweave hook must remain, running the desired command" ); } #[test] - fn dedups_multiple_stale_clarion_hooks() { - // Two stale Clarion hooks, no current one. Must converge to a single - // hook running the desired command, not leave survivors. (clarion #10) - let desired = "clarion hook session-start --path '/proj'"; + fn dedups_multiple_stale_loomweave_hooks() { + // Two stale Loomweave hooks, no current one. Must converge to a single + // hook running the desired command, not leave survivors. (loomweave #10) + let desired = "loomweave hook session-start --path '/proj'"; let mut settings = json!({ "hooks": {"SessionStart": [ - {"hooks": [{"type": "command", "command": "clarion hook session-start"}]}, + {"hooks": [{"type": "command", "command": "loomweave hook session-start"}]}, {"hooks": [{"type": "command", - "command": "clarion hook session-start --path '/old'"}]} + "command": "loomweave hook session-start --path '/old'"}]} ]} }); assert!(merge_session_start_hook(&mut settings, desired)); - assert_eq!(clarion_commands(&settings), vec![desired.to_string()]); + assert_eq!(loomweave_commands(&settings), vec![desired.to_string()]); // Convergent: a second merge is now a no-op. assert!(!merge_session_start_hook(&mut settings, desired)); } @@ -547,10 +547,10 @@ mod tests { let dir = tempfile::tempdir().unwrap(); let claude = dir.path().join(".claude"); fs::create_dir_all(&claude).unwrap(); - // A Clarion hook pinned to some other project: present-but-wrong. + // A Loomweave hook pinned to some other project: present-but-wrong. fs::write( claude.join("settings.json"), - r#"{"hooks":{"SessionStart":[{"hooks":[{"type":"command","command":"clarion hook session-start --path '/some/other/proj'"}]}]}}"#, + r#"{"hooks":{"SessionStart":[{"hooks":[{"type":"command","command":"loomweave hook session-start --path '/some/other/proj'"}]}]}}"#, ) .unwrap(); assert_eq!(session_start_hook_state(dir.path()), HookState::Stale); @@ -578,13 +578,13 @@ mod tests { assert_eq!( session_start_hook_state(dir.path()), HookState::Missing, - "an unrelated SessionStart hook is not a Clarion hook" + "an unrelated SessionStart hook is not a Loomweave hook" ); - // And clarion_commands sees nothing Clarion-owned here. + // And loomweave_commands sees nothing Loomweave-owned here. let settings: serde_json::Value = serde_json::from_str(&fs::read_to_string(claude.join("settings.json")).unwrap()) .unwrap(); - assert!(clarion_commands(&settings).is_empty()); + assert!(loomweave_commands(&settings).is_empty()); } #[test] @@ -661,7 +661,7 @@ mod tests { let cmd = parsed["hooks"]["SessionStart"][0]["hooks"][0]["command"] .as_str() .unwrap(); - assert!(cmd.contains("clarion hook session-start"), "cmd: {cmd}"); + assert!(cmd.contains("loomweave hook session-start"), "cmd: {cmd}"); assert!( cmd.contains("--path"), "installed hook must pin --path: {cmd}" @@ -722,7 +722,7 @@ mod tests { let dir = tempfile::tempdir().unwrap(); let claude_dir = dir.path().join(".claude"); fs::create_dir_all(&claude_dir).unwrap(); - // Hand-authored settings WITHOUT the clarion hook, so the install will + // Hand-authored settings WITHOUT the loomweave hook, so the install will // try to add it (changed = true) and reach the staged write. let settings_path = claude_dir.join("settings.json"); let original = "{\n \"model\": \"opus\"\n}\n"; diff --git a/crates/clarion-cli/src/http_read.rs b/crates/loomweave-cli/src/http_read.rs similarity index 93% rename from crates/clarion-cli/src/http_read.rs rename to crates/loomweave-cli/src/http_read.rs index 3bb9e9bb..41b4c16f 100644 --- a/crates/clarion-cli/src/http_read.rs +++ b/crates/loomweave-cli/src/http_read.rs @@ -12,9 +12,9 @@ use axum::http::{HeaderMap, Request, StatusCode}; use axum::response::{IntoResponse, Response}; use axum::routing::{get, post}; use axum::{Json, Router}; -use clarion_core::HttpErrorCode as ErrorCode; -use clarion_federation::config::HttpReadConfig; -use clarion_storage::ReaderPool; +use loomweave_core::HttpErrorCode as ErrorCode; +use loomweave_federation::config::HttpReadConfig; +use loomweave_storage::ReaderPool; use serde::Serialize; use tokio::sync::oneshot; use tower::ServiceBuilder; @@ -144,13 +144,13 @@ pub(crate) struct AppState { /// `Authorization: Bearer ` when `Some`. `/api/v1/_capabilities` /// is always unauthenticated so siblings can probe pre-auth. pub(crate) auth_token: Option>, - /// Resolved Loom component identity HMAC secret. When present, protected - /// routes require `X-Loom-Component: clarion:` plus freshness headers. + /// Resolved Weft component identity HMAC secret. When present, protected + /// routes require `X-Weft-Component: loomweave:` plus freshness headers. pub(crate) identity_secret: Option>, pub(crate) hmac_replay_cache: auth::SharedHmacReplayCache, /// Present only when `serve.http.wardline_taint_write` is true (ADR-036). /// `None` ⇒ the write API is disabled and returns 403 `WRITE_DISABLED`. - pub(crate) taint_writer: Option>, + pub(crate) taint_writer: Option>, } impl AppState { @@ -261,7 +261,7 @@ where let auth_token_thread = auth_token.clone(); let identity_secret_thread = identity_secret.clone(); let join = thread::Builder::new() - .name("clarion-http-read".to_owned()) + .name("loomweave-http-read".to_owned()) .spawn(move || -> Result<()> { let result = run_http_read_server( project_root, @@ -296,7 +296,7 @@ where tracing::warn!( bind = %local_addr, auth = %auth, - "Clarion HTTP read API listening on non-loopback interface without authentication" + "Loomweave HTTP read API listening on non-loopback interface without authentication" ); } if warn_unauthenticated_loopback { @@ -308,7 +308,7 @@ where identity_token_env or token_env for multi-tenant safety." ); } - tracing::info!(bind = %local_addr, auth = %auth, "Clarion HTTP read API listening"); + tracing::info!(bind = %local_addr, auth = %auth, "Loomweave HTTP read API listening"); Ok(Some(HttpReadServer { shutdown: Some(shutdown_tx), failure_rx, @@ -373,10 +373,10 @@ fn run_http_read_server( // resolves instead of deadlocking. The `taint_writer_join` is held // OUTSIDE the AppState so it survives to be awaited at shutdown. let (taint_writer, taint_writer_join) = if wardline_taint_write { - let (writer, join) = clarion_storage::Writer::spawn( + let (writer, join) = loomweave_storage::Writer::spawn( db_path.clone(), - clarion_storage::DEFAULT_BATCH_SIZE, - clarion_storage::DEFAULT_CHANNEL_CAPACITY, + loomweave_storage::DEFAULT_BATCH_SIZE, + loomweave_storage::DEFAULT_CHANNEL_CAPACITY, ) .map_err(|err| anyhow!("spawn taint writer-actor: {err}"))?; (Some(writer.sender()), Some(join)) @@ -457,7 +457,7 @@ async fn panic_trigger_watcher() { fn build_http_runtime() -> Result { tokio::runtime::Builder::new_multi_thread() - .thread_name("clarion-http-worker") + .thread_name("loomweave-http-worker") .enable_all() .build() .context("create HTTP read runtime") @@ -560,11 +560,11 @@ struct CapabilitiesResponse { /// the `/api/v1/entities/{id}/callers|callees` routes ship. linkages: LinkagesCapability, /// Stable Entity Identity (Wave 1 / WS1, ADR-038). Consumers degrade against - /// a pre-SEI Clarion by reading `sei.supported`. + /// a pre-SEI Loomweave by reading `sei.supported`. sei: SeiCapability, /// Wardline taint-store sub-capabilities (T3.4). `read_by_sei` advertises /// the `POST /api/wardline/taint-facts/by-sei` route discretely: an older - /// SEI-capable Clarion has `sei.supported: true` but lacks this route, so + /// SEI-capable Loomweave has `sei.supported: true` but lacks this route, so /// consumers MUST gate the rename-stable taint read on this flag rather /// than on `sei.supported`. taint_store: TaintStoreCapability, @@ -603,26 +603,26 @@ const WARDLINE_BODY_LIMIT_BYTES: usize = 4 * 1024 * 1024; const SCRUBBED_REQUEST_LOG_HEADERS: &[&str] = &[ "authorization", - "x-loom-component", - "x-loom-timestamp", - "x-loom-nonce", + "x-weft-component", + "x-weft-timestamp", + "x-weft-nonce", ]; #[derive(Debug, Clone, Default, PartialEq, Eq)] struct RequestLogContext { - loom_component: Option, + weft_component: Option, filigree_actor: Option, } fn request_log_context(headers: &HeaderMap) -> RequestLogContext { RequestLogContext { - loom_component: log_loom_component_kind(headers), + weft_component: log_weft_component_kind(headers), filigree_actor: log_non_sensitive_header_value(headers, "x-filigree-actor"), } } -fn log_loom_component_kind(headers: &HeaderMap) -> Option { - let value = headers.get("x-loom-component")?.to_str().ok()?.trim(); +fn log_weft_component_kind(headers: &HeaderMap) -> Option { + let value = headers.get("x-weft-component")?.to_str().ok()?.trim(); let component = value .split_once(':') .map_or(value, |(component, _)| component); @@ -649,11 +649,11 @@ fn http_request_span(request: &Request) -> tracing::Span { "http_read_request", method = %request.method(), path = %request.uri().path(), - loom_component = tracing::field::Empty, + weft_component = tracing::field::Empty, filigree_actor = tracing::field::Empty, ); - if let Some(loom_component) = context.loom_component { - span.record("loom_component", tracing::field::display(loom_component)); + if let Some(weft_component) = context.weft_component { + span.record("weft_component", tracing::field::display(weft_component)); } if let Some(filigree_actor) = context.filigree_actor { span.record("filigree_actor", tracing::field::display(filigree_actor)); @@ -737,22 +737,22 @@ mod tests { fn request_log_context_reads_optional_actor_headers() { let mut headers = HeaderMap::new(); headers.insert( - "X-Loom-Component", - HeaderValue::from_static("clarion:deadbeefsignature"), + "X-Weft-Component", + HeaderValue::from_static("loomweave:deadbeefsignature"), ); - headers.insert("X-Loom-Timestamp", HeaderValue::from_static("123456")); - headers.insert("X-Loom-Nonce", HeaderValue::from_static("nonce-value")); + headers.insert("X-Weft-Timestamp", HeaderValue::from_static("123456")); + headers.insert("X-Weft-Nonce", HeaderValue::from_static("nonce-value")); headers.insert("Authorization", HeaderValue::from_static("Bearer secret")); headers.insert("X-Filigree-Actor", HeaderValue::from_static("worker-f")); let context = request_log_context(&headers); - assert_eq!(context.loom_component.as_deref(), Some("clarion")); + assert_eq!(context.weft_component.as_deref(), Some("loomweave")); assert_eq!(context.filigree_actor.as_deref(), Some("worker-f")); assert!(is_scrubbed_request_log_header("authorization")); - assert!(is_scrubbed_request_log_header("x-loom-component")); - assert!(is_scrubbed_request_log_header("x-loom-timestamp")); - assert!(is_scrubbed_request_log_header("x-loom-nonce")); + assert!(is_scrubbed_request_log_header("x-weft-component")); + assert!(is_scrubbed_request_log_header("x-weft-timestamp")); + assert!(is_scrubbed_request_log_header("x-weft-nonce")); assert_eq!( log_non_sensitive_header_value(&headers, "authorization"), None @@ -768,7 +768,7 @@ mod tests { .expect("worker task") }); - assert_eq!(worker_name.as_deref(), Some("clarion-http-worker")); + assert_eq!(worker_name.as_deref(), Some("loomweave-http-worker")); } /// SEC-02: when the HTTP API binds to loopback and neither @@ -778,8 +778,8 @@ mod tests { /// "without authentication". #[test] fn spawn_emits_loopback_no_token_trust_warning() { - use clarion_federation::config::HttpReadConfig; - use clarion_storage::ReaderPool; + use loomweave_federation::config::HttpReadConfig; + use loomweave_storage::ReaderPool; use std::io; use std::net::{SocketAddr, TcpListener}; use tracing_subscriber::fmt::MakeWriter; @@ -831,14 +831,14 @@ mod tests { drop(probe); let tempdir = tempfile::tempdir().expect("temp project root"); - let db_path = tempdir.path().join("clarion.db"); + let db_path = tempdir.path().join("loomweave.db"); let readers = ReaderPool::open(&db_path, 4).expect("open reader pool"); let config = HttpReadConfig { enabled: true, bind, allow_non_loopback: false, - token_env: "CLARION_LOOPBACK_NO_TOKEN_TEST_UNSET".to_owned(), + token_env: "LOOMWEAVE_LOOPBACK_NO_TOKEN_TEST_UNSET".to_owned(), identity_token_env: None, wardline_taint_write: false, }; @@ -847,7 +847,7 @@ mod tests { .expect("parse synthetic instance id"); // Env lookup that returns None for every variable — emulates - // the operator running `clarion serve` on loopback with no + // the operator running `loomweave serve` on loopback with no // tokens configured. let env_lookup = |_: &str| -> Option { None }; @@ -889,8 +889,8 @@ mod tests { /// spawn→drop→join sequence end to end. #[test] fn spawn_with_taint_writer_shuts_down_cleanly() { - use clarion_federation::config::HttpReadConfig; - use clarion_storage::ReaderPool; + use loomweave_federation::config::HttpReadConfig; + use loomweave_storage::ReaderPool; use std::net::{SocketAddr, TcpListener}; let _guard = http_runtime_test_guard(); @@ -900,7 +900,7 @@ mod tests { drop(probe); let tempdir = tempfile::tempdir().expect("temp project root"); - let db_path = tempdir.path().join("clarion.db"); + let db_path = tempdir.path().join("loomweave.db"); // `Writer::spawn` creates the file and `verify_user_version` passes at // version 0; a shutdown-only test sends no commands. let readers = ReaderPool::open(&db_path, 4).expect("open reader pool"); @@ -942,8 +942,8 @@ mod tests { /// absorb the panic (i.e. anything outside per-request middleware). #[test] fn check_running_surfaces_supervisor_signal_after_runtime_panic() { - use clarion_federation::config::HttpReadConfig; - use clarion_storage::ReaderPool; + use loomweave_federation::config::HttpReadConfig; + use loomweave_storage::ReaderPool; use std::net::{SocketAddr, TcpListener}; let _guard = http_runtime_test_guard(); @@ -956,7 +956,7 @@ mod tests { drop(probe); let tempdir = tempfile::tempdir().expect("temp project root"); - let db_path = tempdir.path().join("clarion.db"); + let db_path = tempdir.path().join("loomweave.db"); // ReaderPool::open is lazy; no connection is acquired before the // panic trigger fires, so the absent SQLite file is irrelevant. let readers = ReaderPool::open(&db_path, 4).expect("open reader pool"); diff --git a/crates/clarion-cli/src/http_read/auth.rs b/crates/loomweave-cli/src/http_read/auth.rs similarity index 97% rename from crates/clarion-cli/src/http_read/auth.rs rename to crates/loomweave-cli/src/http_read/auth.rs index a3ce0f49..88227e61 100644 --- a/crates/clarion-cli/src/http_read/auth.rs +++ b/crates/loomweave-cli/src/http_read/auth.rs @@ -9,8 +9,8 @@ use axum::body::{Body, to_bytes}; use axum::extract::State; use axum::http::{Request, StatusCode}; use axum::response::Response; -use clarion_core::HttpErrorCode as ErrorCode; use hmac::{Hmac, Mac}; +use loomweave_core::HttpErrorCode as ErrorCode; use sha2::{Digest, Sha256}; use subtle::ConstantTimeEq; use time::OffsetDateTime; @@ -65,7 +65,7 @@ impl HmacReplayCache { } } -/// Enforce configured identity on protected routes. Prefer the Loom HMAC +/// Enforce configured identity on protected routes. Prefer the Weft HMAC /// identity when `identity_token_env` is configured; otherwise preserve the /// legacy bearer-token path for existing deployments. pub(crate) async fn require_http_identity( @@ -134,9 +134,9 @@ pub(crate) async fn require_hmac_identity( ); let presented = parts .headers - .get("x-loom-component") + .get("x-weft-component") .and_then(|value| value.to_str().ok()) - .and_then(|value| value.trim().strip_prefix("clarion:")) + .and_then(|value| value.trim().strip_prefix("loomweave:")) .filter(|signature| !signature.is_empty()) .map(str::to_owned); let Some(presented) = presented else { @@ -144,7 +144,7 @@ pub(crate) async fn require_hmac_identity( }; let timestamp = parts .headers - .get("x-loom-timestamp") + .get("x-weft-timestamp") .and_then(|value| value.to_str().ok()) .and_then(|value| value.trim().parse::().ok()); let Some(timestamp) = timestamp else { @@ -152,7 +152,7 @@ pub(crate) async fn require_hmac_identity( }; let nonce = parts .headers - .get("x-loom-nonce") + .get("x-weft-nonce") .and_then(|value| value.to_str().ok()) .map(str::trim) .filter(|nonce| !nonce.is_empty() && nonce.len() <= HMAC_NONCE_MAX_LEN) @@ -447,12 +447,12 @@ mod tests { let request = Request::builder() .method("POST") .uri("/api/v1/files/batch") - .header("X-Loom-Component", "clarion:deadbeef") + .header("X-Weft-Component", "loomweave:deadbeef") .header( - "X-Loom-Timestamp", + "X-Weft-Timestamp", OffsetDateTime::now_utc().unix_timestamp().to_string(), ) - .header("X-Loom-Nonce", "body-read-failure") + .header("X-Weft-Nonce", "body-read-failure") .body(Body::from(oversize)) .expect("request"); diff --git a/crates/clarion-cli/src/http_read/errors.rs b/crates/loomweave-cli/src/http_read/errors.rs similarity index 97% rename from crates/clarion-cli/src/http_read/errors.rs rename to crates/loomweave-cli/src/http_read/errors.rs index 2b0b2ca7..e7ed3a7a 100644 --- a/crates/clarion-cli/src/http_read/errors.rs +++ b/crates/loomweave-cli/src/http_read/errors.rs @@ -6,13 +6,13 @@ use std::error::Error as StdError; use axum::http::StatusCode; use axum::response::Response; -use clarion_core::HttpErrorCode as ErrorCode; -use clarion_storage::StorageError; +use loomweave_core::HttpErrorCode as ErrorCode; +use loomweave_storage::StorageError; use super::{HTTP_ERROR_DISPATCH, json_error}; /// ISO-8601 UTC "now" with millisecond precision (`YYYY-MM-DDTHH:MM:SS.sssZ`), -/// matching the caller-side timestamps `clarion analyze` stamps onto run rows. +/// matching the caller-side timestamps `loomweave analyze` stamps onto run rows. pub(crate) fn iso8601_now() -> String { use time::macros::format_description; const ISO8601_MILLIS_UTC: &[time::format_description::FormatItem<'_>] = @@ -48,7 +48,7 @@ pub(crate) fn classify_read_error(err: &StorageError) -> ReadError { code: ErrorCode::PathOutsideProject, message: "path is outside project root", }, - // A stored row that failed an integrity check is Clarion's fault, not + // A stored row that failed an integrity check is Loomweave's fault, not // the client's: 500 + logged (via `json_read_error`), never a 4xx that // blames the caller's request. A federation client routing on `code` // must see STORAGE_ERROR, not INVALID_PATH. @@ -71,7 +71,7 @@ pub(crate) fn classify_read_error(err: &StorageError) -> ReadError { code: ErrorCode::StorageError, message: "file lookup failed", }, - // STO-02 (ADR-035): the on-disk file is not a Clarion database, or + // STO-02 (ADR-035): the on-disk file is not a Loomweave database, or // was written by a newer build. Either condition is fatal for the // server; the writer-actor refuses to spawn against it. Surfacing // 500 here is defensive — in practice the HTTP API does not open diff --git a/crates/clarion-cli/src/http_read/files.rs b/crates/loomweave-cli/src/http_read/files.rs similarity index 97% rename from crates/clarion-cli/src/http_read/files.rs rename to crates/loomweave-cli/src/http_read/files.rs index 5e7afa5a..1ef548bb 100644 --- a/crates/clarion-cli/src/http_read/files.rs +++ b/crates/loomweave-cli/src/http_read/files.rs @@ -8,8 +8,8 @@ use axum::extract::rejection::QueryRejection; use axum::extract::{Query, State}; use axum::http::{HeaderMap, HeaderValue, StatusCode, header}; use axum::response::{IntoResponse, Response}; -use clarion_core::HttpErrorCode as ErrorCode; -use clarion_storage::{CanonicalProjectPath, StorageError, resolve_file_catalog_entry}; +use loomweave_core::HttpErrorCode as ErrorCode; +use loomweave_storage::{CanonicalProjectPath, StorageError, resolve_file_catalog_entry}; use serde::{Deserialize, Serialize}; use super::errors::{classify_read_error, json_read_error, log_briefing_blocked_refusal}; @@ -186,7 +186,7 @@ pub(crate) async fn get_file( Ok(None) => json_error( StatusCode::NOT_FOUND, ErrorCode::NotFound, - "file is not known to Clarion", + "file is not known to Loomweave", ), Err(err) => json_read_error(&err), } @@ -218,7 +218,7 @@ pub(crate) fn insert_etag(response: &mut Response, etag: &str) { /// single request, partitioning results into four lists: /// /// - `resolved` — paths that mapped to a file-kind entity. -/// - `not_found` — paths Clarion does not have a catalog row for. +/// - `not_found` — paths Loomweave does not have a catalog row for. /// - `briefing_blocked` — paths whose entity carries a `briefing_blocked` /// property (the partition equivalent of the single-file 403 surface). /// - `errors` — per-path resolution errors (`INVALID_PATH`, @@ -226,7 +226,7 @@ pub(crate) fn insert_etag(response: &mut Response, etag: &str) { /// /// The whole batch runs inside **one** `with_reader` closure so we /// check out one pooled connection per request, not one per query — -/// this is the perf win Filigree's `ClarionRegistry` needs for cold- +/// this is the perf win Filigree's `LoomweaveRegistry` needs for cold- /// start hydration. `ETag` is intentionally not applied to the batch /// surface; clients should `ETag` the single-file endpoint when they /// want conditional fetch semantics. @@ -347,7 +347,7 @@ pub(crate) async fn post_files_resolve( // ── Call-graph linkages (Wave 0 / WS2) ────────────────────────────────────── // -// Thin HTTP wrappers over `clarion_storage::call_edges_targeting` (callers) and +// Thin HTTP wrappers over `loomweave_storage::call_edges_targeting` (callers) and // `call_edges_from` (callees). Aggregated per neighbour entity: `call_site_count` // is the number of call sites (across all returned confidence tiers) and // `confidence` is the STRONGEST tier present (resolved > ambiguous > inferred) — @@ -403,7 +403,7 @@ pub(crate) fn resolve_file_query_item( Ok(None) => resolve_error_response( ResolveFileStatus::NotFound, ErrorCode::NotFound, - "file is not known to Clarion", + "file is not known to Loomweave", ), Err(err) => resolve_read_error_response(&err), } diff --git a/crates/clarion-cli/src/http_read/identity.rs b/crates/loomweave-cli/src/http_read/identity.rs similarity index 97% rename from crates/clarion-cli/src/http_read/identity.rs rename to crates/loomweave-cli/src/http_read/identity.rs index 843b17c3..cd75a498 100644 --- a/crates/clarion-cli/src/http_read/identity.rs +++ b/crates/loomweave-cli/src/http_read/identity.rs @@ -7,8 +7,8 @@ use axum::Json; use axum::extract::{Path, State}; use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; -use clarion_core::HttpErrorCode as ErrorCode; -use clarion_storage::{ +use loomweave_core::HttpErrorCode as ErrorCode; +use loomweave_storage::{ SeiLookupResult, StorageError, is_reserved_sei, resolve_locator, resolve_sei, sei_lineage, }; use serde::{Deserialize, Serialize}; @@ -45,7 +45,7 @@ pub(crate) struct SeiLineageEventBody { /// empty segment. Returns the documented client message on rejection. pub(crate) fn validate_locator(locator: &str) -> Result<(), &'static str> { if is_reserved_sei(locator) { - return Err("not a valid locator: input is an SEI (reserved clarion:eid: prefix)"); + return Err("not a valid locator: input is an SEI (reserved loomweave:eid: prefix)"); } let segments: Vec<&str> = locator.splitn(3, ':').collect(); if segments.len() != 3 || segments.iter().any(|s| s.is_empty()) { @@ -55,7 +55,7 @@ pub(crate) fn validate_locator(locator: &str) -> Result<(), &'static str> { } pub(crate) fn lineage_rows_to_body( - rows: Vec, + rows: Vec, ) -> Vec { rows.into_iter() .map(|r| SeiLineageEventBody { @@ -231,7 +231,7 @@ mod tests { fn validate_locator_rejects_reserved_sei_prefix() { // A real SEI has the same colon count as a locator — only the reserved // prefix distinguishes it, which is exactly what the rejection keys on. - let err = validate_locator("clarion:eid:0123456789abcdef0123456789abcdef") + let err = validate_locator("loomweave:eid:0123456789abcdef0123456789abcdef") .expect_err("an SEI-shaped input must be rejected"); assert!(err.contains("not a valid locator"), "message: {err}"); } diff --git a/crates/clarion-cli/src/http_read/linkages.rs b/crates/loomweave-cli/src/http_read/linkages.rs similarity index 98% rename from crates/clarion-cli/src/http_read/linkages.rs rename to crates/loomweave-cli/src/http_read/linkages.rs index 52b7be96..9d8fbdc9 100644 --- a/crates/clarion-cli/src/http_read/linkages.rs +++ b/crates/loomweave-cli/src/http_read/linkages.rs @@ -8,9 +8,9 @@ use axum::extract::rejection::QueryRejection; use axum::extract::{Path, Query, State}; use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; -use clarion_core::EdgeConfidence; -use clarion_core::HttpErrorCode as ErrorCode; -use clarion_storage::{ +use loomweave_core::EdgeConfidence; +use loomweave_core::HttpErrorCode as ErrorCode; +use loomweave_storage::{ CallEdgeMatch, EntityVisibility, StorageError, call_edges_from, call_edges_targeting, entity_visibility, }; @@ -217,7 +217,7 @@ pub(crate) async fn linkage_single( Ok(LinkageLookup::NotFound) => json_error( StatusCode::NOT_FOUND, ErrorCode::NotFound, - "entity is not known to Clarion", + "entity is not known to Loomweave", ), Ok(LinkageLookup::Blocked) => json_error( StatusCode::FORBIDDEN, @@ -355,7 +355,7 @@ pub(crate) async fn post_callees_batch( // (the source of truth); `entities` is joined only for `content_hash`. // // REQ-F-02 (fail-closed): `resolve(locator)` MUST reject an SEI-shaped input -// (reserved `clarion:eid:` prefix) — never silently mis-resolve. A colon-count +// (reserved `loomweave:eid:` prefix) — never silently mis-resolve. A colon-count // check is insufficient (an SEI carries the same two colons a locator does), so // the rejection keys on the reserved prefix. This is what makes the idempotent, // resumable cross-tool backfill safe (an already-migrated SEI is rejected). @@ -429,11 +429,11 @@ mod tests { entities: &[(&str, Option<&str>)], calls: &[(&str, &str, &str, &[&str])], ) -> (AppState, tempfile::TempDir) { - use clarion_storage::ReaderPool; - use clarion_storage::schema::apply_migrations; + use loomweave_storage::ReaderPool; + use loomweave_storage::schema::apply_migrations; let tempdir = tempfile::tempdir().expect("temp project root"); - let db_path = tempdir.path().join("clarion.db"); + let db_path = tempdir.path().join("loomweave.db"); let mut conn = rusqlite::Connection::open(&db_path).expect("open db"); apply_migrations(&mut conn).expect("apply migrations"); for (id, blocked) in entities { @@ -730,7 +730,7 @@ mod tests { use tower::ServiceExt; let secret = "linkage-secret"; let (state, _tempdir) = linkage_test_state(secret, &[("python:function:t", None)], &[]); - // No X-Loom-Component header → 401 (route is HMAC-gated like /api/v1/files). + // No X-Weft-Component header → 401 (route is HMAC-gated like /api/v1/files). let request = axum::http::Request::builder() .method("GET") .uri("/api/v1/entities/python:function:t/callers") @@ -866,7 +866,7 @@ mod tests { #[tokio::test] async fn capabilities_reports_taint_store_read_by_sei_true() { use tower::ServiceExt; - // Discrete from `sei.supported`: an older SEI-capable Clarion would set + // Discrete from `sei.supported`: an older SEI-capable Loomweave would set // `sei.supported: true` yet lack this route, so consumers gate the // rename-stable taint read on this flag specifically. let (state, _tempdir) = linkage_test_state("linkage-secret", &[], &[]); diff --git a/crates/clarion-cli/src/http_read/test_support.rs b/crates/loomweave-cli/src/http_read/test_support.rs similarity index 87% rename from crates/clarion-cli/src/http_read/test_support.rs rename to crates/loomweave-cli/src/http_read/test_support.rs index c3dc6022..5c2e3816 100644 --- a/crates/clarion-cli/src/http_read/test_support.rs +++ b/crates/loomweave-cli/src/http_read/test_support.rs @@ -26,9 +26,9 @@ pub(crate) fn hmac_request( axum::http::Request::builder() .method(method) .uri(path_and_query) - .header("X-Loom-Component", format!("clarion:{signature}")) - .header("X-Loom-Timestamp", timestamp.to_string()) - .header("X-Loom-Nonce", nonce) + .header("X-Weft-Component", format!("loomweave:{signature}")) + .header("X-Weft-Timestamp", timestamp.to_string()) + .header("X-Weft-Nonce", nonce) .header(header::CONTENT_TYPE, "application/json") .body(axum::body::Body::from(body.to_vec())) .expect("build request") diff --git a/crates/clarion-cli/src/http_read/wardline.rs b/crates/loomweave-cli/src/http_read/wardline.rs similarity index 95% rename from crates/clarion-cli/src/http_read/wardline.rs rename to crates/loomweave-cli/src/http_read/wardline.rs index 90ccd09e..5019db8d 100644 --- a/crates/clarion-cli/src/http_read/wardline.rs +++ b/crates/loomweave-cli/src/http_read/wardline.rs @@ -8,8 +8,8 @@ use axum::extract::rejection::QueryRejection; use axum::extract::{Query, State}; use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; -use clarion_core::HttpErrorCode as ErrorCode; -use clarion_storage::StorageError; +use loomweave_core::HttpErrorCode as ErrorCode; +use loomweave_storage::StorageError; use serde::{Deserialize, Serialize}; use tokio::sync::oneshot; @@ -102,7 +102,7 @@ pub(crate) struct BatchGetRequest { pub(crate) struct BatchGetBySeiRequest { #[serde(default)] project: String, - /// Opaque SEIs (`clarion:eid:`). Treated verbatim — NO locator-shape + /// Opaque SEIs (`loomweave:eid:`). Treated verbatim — NO locator-shape /// validation (SEI-shaped strings are the valid input here, the inverse of /// the `resolve` REQ-F-02 rejection). seis: Vec, @@ -123,8 +123,8 @@ pub(crate) struct TaintFactBySeiView { /// Exact-tier Wardline qualname resolve (ADR-036, W.4). Takes a batch of /// PRE-COMPOSED dotted qualnames that Wardline has already shaped to -/// byte-match Clarion's `canonical_qualified_name`; resolution is the direct -/// existence lookup in `clarion_storage::resolve_wardline_qualnames`. No +/// byte-match Loomweave's `canonical_qualified_name`; resolution is the direct +/// existence lookup in `loomweave_storage::resolve_wardline_qualnames`. No /// `&file=` disambiguator, no normalization — the generic resolve oracle /// remains deferred. pub(crate) async fn post_wardline_resolve( @@ -156,7 +156,7 @@ pub(crate) async fn post_wardline_resolve( let qualnames = req.qualnames; let result = state .readers - .with_reader(move |conn| clarion_storage::resolve_wardline_qualnames(conn, &qualnames)) + .with_reader(move |conn| loomweave_storage::resolve_wardline_qualnames(conn, &qualnames)) .await; match result { Ok(pairs) => { @@ -233,13 +233,13 @@ pub(crate) async fn post_wardline_taint_facts( let resolution = state .readers .with_reader(move |conn| { - let resolved = clarion_storage::resolve_wardline_qualnames(conn, &qualnames)?; + let resolved = loomweave_storage::resolve_wardline_qualnames(conn, &qualnames)?; let locators: Vec = resolved .iter() .filter_map(|(_, r)| r.entity_id().map(str::to_owned)) .collect(); - let seis = clarion_storage::seis_for_locators(conn, &locators)?; - Ok::<_, clarion_storage::StorageError>((resolved, seis)) + let seis = loomweave_storage::seis_for_locators(conn, &locators)?; + Ok::<_, loomweave_storage::StorageError>((resolved, seis)) }) .await; let (resolved, seis_by_locator) = match resolution { @@ -271,7 +271,7 @@ pub(crate) async fn post_wardline_taint_facts( ); } let sei = fact.sei.clone().or(resolved_sei); - let taint_fact = clarion_storage::TaintFact { + let taint_fact = loomweave_storage::TaintFact { entity_id, // Opaque + byte-verbatim: `RawValue::get()` returns the original // bytes of the blob exactly as the client sent them (no key @@ -284,7 +284,7 @@ pub(crate) async fn post_wardline_taint_facts( sei, }; let (ack_tx, ack_rx) = oneshot::channel(); - let cmd = clarion_storage::WriterCmd::UpsertWardlineTaintFact { + let cmd = loomweave_storage::WriterCmd::UpsertWardlineTaintFact { fact: Box::new(taint_fact), ack: ack_tx, }; @@ -335,7 +335,7 @@ pub(crate) async fn post_wardline_taint_facts( /// - for rows that exist, parse the stored blob byte-faithfully via /// `RawValue::from_string` (W.2 wrote it from a `RawValue`, so it /// round-trips) and derive `current_content_hash` live from the row's -/// `source_file_path` via `clarion_storage::current_file_hash`. +/// `source_file_path` via `loomweave_storage::current_file_hash`. /// /// File hashing is DEDUPED per request by `source_file_path`: a chain-walk /// batch hits many functions sharing one file, and a 425k-LOC project must @@ -353,18 +353,18 @@ pub(crate) async fn respond_taint_facts( .readers .with_reader(move |conn| { // 1. Resolve every qualname (exact tier), in input order. - let resolved = clarion_storage::resolve_wardline_qualnames(conn, &qualnames)?; + let resolved = loomweave_storage::resolve_wardline_qualnames(conn, &qualnames)?; // 2. Fetch facts for the resolved entity ids; map back by id. let entity_ids: Vec = resolved .iter() .filter_map(|(_, r)| r.entity_id().map(str::to_owned)) .collect(); - let rows = clarion_storage::get_taint_facts(conn, &entity_ids)?; - let by_entity: std::collections::HashMap = rows - .into_iter() - .map(|row| (row.entity_id.clone(), row)) - .collect(); + let rows = loomweave_storage::get_taint_facts(conn, &entity_ids)?; + let by_entity: std::collections::HashMap = + rows.into_iter() + .map(|row| (row.entity_id.clone(), row)) + .collect(); // 3. Build a view per qualname, deduping file hashing by path. let mut file_hash_cache: std::collections::HashMap> = @@ -391,7 +391,7 @@ pub(crate) async fn respond_taint_facts( Some(path) => file_hash_cache .entry(path.clone()) .or_insert_with(|| { - clarion_storage::current_file_hash(&project_root, path) + loomweave_storage::current_file_hash(&project_root, path) }) .clone(), None => None, @@ -507,8 +507,8 @@ pub(crate) async fn respond_taint_facts_by_sei( .readers .with_reader(move |conn| { // Most-recent fact per SEI (rename-stable lookup). - let rows = clarion_storage::get_taint_facts_by_sei(conn, &seis)?; - let by_sei: std::collections::HashMap = rows + let rows = loomweave_storage::get_taint_facts_by_sei(conn, &seis)?; + let by_sei: std::collections::HashMap = rows .into_iter() .filter_map(|row| row.sei.clone().map(|sei| (sei, row))) .collect(); @@ -536,7 +536,7 @@ pub(crate) async fn respond_taint_facts_by_sei( Some(path) => file_hash_cache .entry(path.clone()) .or_insert_with(|| { - clarion_storage::current_file_hash(&project_root, path) + loomweave_storage::current_file_hash(&project_root, path) }) .clone(), None => None, @@ -627,11 +627,11 @@ mod tests { secret: &str, seed_ids: &[&str], ) -> (AppState, tempfile::TempDir) { - use clarion_storage::ReaderPool; - use clarion_storage::schema::apply_migrations; + use loomweave_storage::ReaderPool; + use loomweave_storage::schema::apply_migrations; let tempdir = tempfile::tempdir().expect("temp project root"); - let db_path = tempdir.path().join("clarion.db"); + let db_path = tempdir.path().join("loomweave.db"); let mut conn = rusqlite::Connection::open(&db_path).expect("open db"); apply_migrations(&mut conn).expect("apply migrations"); for id in seed_ids { @@ -732,7 +732,7 @@ mod tests { ) -> ( AppState, std::path::PathBuf, - clarion_storage::Writer, + loomweave_storage::Writer, tempfile::TempDir, ) { wardline_write_test_state_with_bindings(secret, seed_ids, &[]) @@ -745,14 +745,14 @@ mod tests { ) -> ( AppState, std::path::PathBuf, - clarion_storage::Writer, + loomweave_storage::Writer, tempfile::TempDir, ) { - use clarion_storage::ReaderPool; - use clarion_storage::schema::apply_migrations; + use loomweave_storage::ReaderPool; + use loomweave_storage::schema::apply_migrations; let tempdir = tempfile::tempdir().expect("temp project root"); - let db_path = tempdir.path().join("clarion.db"); + let db_path = tempdir.path().join("loomweave.db"); let mut conn = rusqlite::Connection::open(&db_path).expect("open db"); apply_migrations(&mut conn).expect("apply migrations"); for id in seed_ids { @@ -788,10 +788,10 @@ mod tests { drop(conn); let readers = ReaderPool::open(&db_path, 4).expect("open reader pool"); - let (writer, _join) = clarion_storage::Writer::spawn( + let (writer, _join) = loomweave_storage::Writer::spawn( db_path.clone(), - clarion_storage::DEFAULT_BATCH_SIZE, - clarion_storage::DEFAULT_CHANNEL_CAPACITY, + loomweave_storage::DEFAULT_BATCH_SIZE, + loomweave_storage::DEFAULT_CHANNEL_CAPACITY, ) .expect("spawn taint writer-actor"); // The join handle is dropped here: the test reads the DB on a fresh @@ -887,11 +887,11 @@ mod tests { let secret = "wardline-write-secret"; let locator = "python:function:a.b.c"; - let resolved_sei = "clarion:eid:resolved"; + let resolved_sei = "loomweave:eid:resolved"; let (state, db_path, writer, _tempdir) = wardline_write_test_state_with_bindings(secret, &[locator], &[(resolved_sei, locator)]); - let body = br#"{"facts":[{"qualname":"a.b.c","sei":"clarion:eid:other","wardline_json":{"v":1}}]}"#; + let body = br#"{"facts":[{"qualname":"a.b.c","sei":"loomweave:eid:other","wardline_json":{"v":1}}]}"#; let request = hmac_request(secret, "POST", "/api/wardline/taint-facts", body); let response = router(state).oneshot(request).await.expect("oneshot"); @@ -912,11 +912,11 @@ mod tests { let secret = "wardline-write-secret"; let locator = "python:function:a.b.c"; - let resolved_sei = "clarion:eid:resolved"; + let resolved_sei = "loomweave:eid:resolved"; let (state, db_path, writer, _tempdir) = wardline_write_test_state_with_bindings(secret, &[locator], &[(resolved_sei, locator)]); - let body = br#"{"facts":[{"qualname":"a.b.c","sei":"clarion:eid:resolved","wardline_json":{"v":1}}]}"#; + let body = br#"{"facts":[{"qualname":"a.b.c","sei":"loomweave:eid:resolved","wardline_json":{"v":1}}]}"#; let request = hmac_request(secret, "POST", "/api/wardline/taint-facts", body); let response = router(state).oneshot(request).await.expect("oneshot"); @@ -1040,7 +1040,7 @@ mod tests { let request = axum::http::Request::builder() .method("POST") .uri("/api/wardline/taint-facts") - .header("X-Loom-Component", "clarion:deadbeefdeadbeef") + .header("X-Weft-Component", "loomweave:deadbeefdeadbeef") .header(header::CONTENT_TYPE, "application/json") .body(axum::body::Body::from(body.to_vec())) .expect("build request"); @@ -1054,7 +1054,7 @@ mod tests { let parsed: serde_json::Value = serde_json::from_slice(&bytes).expect("json"); assert_eq!(parsed["code"], "UNAUTHENTICATED"); - // (3) Absent X-Loom-Component header → 401. This is the case that + // (3) Absent X-Weft-Component header → 401. This is the case that // catches a regression dropping the route_layer: with no guard, this // request would reach the handler and 403/200, not 401. let (state, _td3) = wardline_resolve_test_state(secret, &[]); @@ -1239,11 +1239,11 @@ mod tests { /// is stored only when `blob` is `Some`. Returns the state and the /// `TempDir` guard (drop it last). fn wardline_read_test_state(secret: &str, seeds: &[SeedFn]) -> (AppState, tempfile::TempDir) { - use clarion_storage::ReaderPool; - use clarion_storage::schema::apply_migrations; + use loomweave_storage::ReaderPool; + use loomweave_storage::schema::apply_migrations; let tempdir = tempfile::tempdir().expect("temp project root"); - let db_path = tempdir.path().join("clarion.db"); + let db_path = tempdir.path().join("loomweave.db"); let mut conn = rusqlite::Connection::open(&db_path).expect("open db"); apply_migrations(&mut conn).expect("apply migrations"); @@ -1386,9 +1386,9 @@ mod tests { /// malformed client request. The validated write path (`RawValue` round-trip) /// cannot produce this — only storage corruption or an out-of-band write /// can — so the test injects it directly via the seed builder's verbatim - /// blob. The read must return 500 `STORAGE_ERROR` (Clarion's fault, and 5xx + /// blob. The read must return 500 `STORAGE_ERROR` (Loomweave's fault, and 5xx /// so `json_read_error` logs it), NOT 400 `INVALID_PATH` (which would blame - /// the federation client's request for Clarion's storage damage). + /// the federation client's request for Loomweave's storage damage). #[tokio::test] async fn wardline_taint_get_corrupt_blob_is_500_storage_error_not_400() { use tower::ServiceExt; @@ -1412,7 +1412,7 @@ mod tests { assert_eq!( response.status(), StatusCode::INTERNAL_SERVER_ERROR, - "a corrupt stored blob is Clarion's fault → 500, never a client 400" + "a corrupt stored blob is Loomweave's fault → 500, never a client 400" ); let bytes = to_bytes(response.into_body(), 4096).await.expect("body"); let parsed: serde_json::Value = serde_json::from_slice(&bytes).expect("json"); @@ -1538,15 +1538,15 @@ mod tests { #[tokio::test] async fn wardline_taint_batch_get_shared_file_yields_same_hash() { - use clarion_storage::ReaderPool; - use clarion_storage::schema::apply_migrations; + use loomweave_storage::ReaderPool; + use loomweave_storage::schema::apply_migrations; use tower::ServiceExt; let secret = "wardline-read-secret"; // Build state by hand so two entities share ONE file (exercises the // per-request file-hash dedup; both must report the same hash). let tempdir = tempfile::tempdir().expect("temp project root"); - let db_path = tempdir.path().join("clarion.db"); + let db_path = tempdir.path().join("loomweave.db"); let mut conn = rusqlite::Connection::open(&db_path).expect("open db"); apply_migrations(&mut conn).expect("migrations"); let shared = tempdir.path().join("shared.py"); @@ -1715,7 +1715,7 @@ mod tests { let secret = "wardline-write-secret"; let old = "python:function:old.pkg.fn"; let new = "python:function:new.pkg.fn"; - let sei = "clarion:eid:rename-stable"; + let sei = "loomweave:eid:rename-stable"; // Both pre- and post-rename entity rows exist (entities is cumulative). // Alive SEI binding at the OLD locator, as it stands at write time. let (state, db_path, _writer, _tempdir) = @@ -1843,7 +1843,7 @@ mod tests { let secret = "wardline-write-secret"; let (state, _db_path, _writer, _tempdir) = wardline_write_test_state(secret, &["python:function:a.b.c"]); - let body = serde_json::json!({ "seis": ["clarion:eid:nope"] }).to_string(); + let body = serde_json::json!({ "seis": ["loomweave:eid:nope"] }).to_string(); let request = hmac_request( secret, "POST", @@ -1855,7 +1855,7 @@ mod tests { let parsed = json_body(response).await; let arr = parsed.as_array().expect("array"); assert_eq!(arr.len(), 1); - assert_eq!(arr[0]["sei"], "clarion:eid:nope"); + assert_eq!(arr[0]["sei"], "loomweave:eid:nope"); assert_eq!(arr[0]["exists"], false); assert!(arr[0].get("wardline_json").is_none()); } @@ -1873,7 +1873,7 @@ mod tests { .method("POST") .uri("/api/wardline/taint-facts/by-sei") .header("content-type", "application/json") - .body(axum::body::Body::from(r#"{"seis":["clarion:eid:x"]}"#)) + .body(axum::body::Body::from(r#"{"seis":["loomweave:eid:x"]}"#)) .expect("build request"); let response = router(state).oneshot(request).await.expect("oneshot"); assert_eq!(response.status(), StatusCode::UNAUTHORIZED); diff --git a/crates/clarion-cli/src/install.rs b/crates/loomweave-cli/src/install.rs similarity index 75% rename from crates/clarion-cli/src/install.rs rename to crates/loomweave-cli/src/install.rs index ecda594f..7a1ff576 100644 --- a/crates/clarion-cli/src/install.rs +++ b/crates/loomweave-cli/src/install.rs @@ -1,16 +1,16 @@ -//! `clarion install` — initialise .clarion/ in the target directory. +//! `loomweave install` — initialise .loomweave/ in the target directory. //! //! Creates: -//! - `.clarion/clarion.db` (migrated) -//! - `.clarion/config.json` (internal state stub) -//! - `.clarion/.gitignore` (UQ-WP1-04 rules; ADR-005) -//! - `/clarion.yaml` (user-edited config stub at project root; +//! - `.loomweave/loomweave.db` (migrated) +//! - `.loomweave/config.json` (internal state stub) +//! - `.loomweave/.gitignore` (UQ-WP1-04 rules; ADR-005) +//! - `/loomweave.yaml` (user-edited config stub at project root; //! see detailed-design.md §File layout) //! -//! A bare `clarion install` (no flags) does everything: init + MCP config + -//! skills + hooks + local Loom integration bindings. If `.clarion/` already +//! A bare `loomweave install` (no flags) does everything: init + MCP config + +//! skills + hooks + local Weft integration bindings. If `.loomweave/` already //! exists, init is skipped and the idempotent components are still applied. -//! Pass `--force` to wipe and reinitialise `.clarion/`. Component flags and +//! Pass `--force` to wipe and reinitialise `.loomweave/`. Component flags and //! `--all` are still accepted for explicit partial installs. use std::fs; @@ -19,7 +19,7 @@ use std::path::Path; use anyhow::{Context, Result, bail}; use rusqlite::Connection; -use clarion_storage::{pragma, schema}; +use loomweave_storage::{pragma, schema}; const CONFIG_JSON_STUB: &str = r#"{ "schema_version": 1, @@ -30,8 +30,8 @@ const CONFIG_JSON_STUB: &str = r#"{ // NOTE: Do not use `\` line-continuation here — Rust strips both the newline // AND all leading whitespace on the continuation line, producing flat (and // therefore broken) YAML. Use raw newlines + explicit indentation. -const CLARION_YAML_STUB: &str = "# clarion.yaml — user-edited config. -# Do not delete this file: clarion serve reads MCP, LLM, and integration +const LOOMWEAVE_YAML_STUB: &str = "# loomweave.yaml — user-edited config. +# Do not delete this file: loomweave serve reads MCP, LLM, and integration # settings from here when present. version: 1 llm_policy: @@ -42,8 +42,8 @@ llm_policy: endpoint_url: https://openrouter.ai/api/v1 api_key_env: OPENROUTER_API_KEY attribution: - referer: https://github.com/tachyon-beep/clarion - title: Clarion + referer: https://github.com/foundryside-dev/loomweave + title: Loomweave codex_cli: executable: codex model: null @@ -67,7 +67,7 @@ integrations: filigree: enabled: false base_url: http://127.0.0.1:8766 - actor: clarion-mcp + actor: loomweave-mcp token_env: FILIGREE_API_TOKEN timeout_seconds: 5 serve: @@ -79,8 +79,8 @@ serve: "; const GITIGNORE_CONTENTS: &str = "\ -# Clarion .gitignore — ADR-005 tracked-vs-excluded list. -# Tracked (committed): clarion.db, config.json, .gitignore itself. +# Loomweave .gitignore — ADR-005 tracked-vs-excluded list. +# Tracked (committed): loomweave.db, config.json, .gitignore itself. # Excluded (ignored): WAL sidecars, shadow DB, per-run logs, tmp scratch. # SQLite write-ahead files never belong in the repo. @@ -94,7 +94,7 @@ const GITIGNORE_CONTENTS: &str = "\ *.db.new # Semantic-search embeddings sidecar (ADR-040): large + rebuildable, never -# committed (keeps clarion.db unbloated). WAL files are covered by *.db-wal/-shm. +# committed (keeps loomweave.db unbloated). WAL files are covered by *.db-wal/-shm. embeddings.db # Scratch / temp space. @@ -107,7 +107,7 @@ logs/ runs/*/log.jsonl "; -/// A single component selected by a partial `clarion install`. +/// A single component selected by a partial `loomweave install`. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum InstallComponent { ClaudeCode, @@ -117,21 +117,21 @@ pub enum InstallComponent { Hooks, } -/// What `clarion install` should do, resolved from the CLI flags. +/// What `loomweave install` should do, resolved from the CLI flags. /// /// Modeled as an enum rather than three independent bools so the derived and -/// illegal states the bool form allowed are unrepresentable: `init_clarion` is +/// illegal states the bool form allowed are unrepresentable: `init_loomweave` is /// no longer a peer field that can contradict an explicit component request, /// and the do-nothing `{false, false, false}` state (which PR #21 had to guard /// against at the `run()` entry) cannot be produced by /// [`InstallPlan::from_components`] /// at all (clarion-c6b8dc27f3). Component booleans are derived on demand via -/// [`init_clarion`](Self::init_clarion) / [`skills`](Self::skills) / +/// [`init_loomweave`](Self::init_loomweave) / [`skills`](Self::skills) / /// [`hooks`](Self::hooks). #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum InstallPlan { /// Component flags without `--all`: apply the named components and do NOT - /// initialise `.clarion/`. `from_components` only constructs this when at + /// initialise `.loomweave/`. `from_components` only constructs this when at /// least one component is present. Components { claude_code: bool, @@ -140,14 +140,14 @@ pub enum InstallPlan { codex_skills: bool, hooks: bool, }, - /// No flags or `--all`: initialise `.clarion/` + every integration. + /// No flags or `--all`: initialise `.loomweave/` + every integration. All, } impl InstallPlan { /// Resolve the CLI flags into a plan. `--all` wins; otherwise any of /// the component flags selects [`Components`](Self::Components); no flag - /// selects [`All`](Self::All) so that a naked `clarion install` does + /// selects [`All`](Self::All) so that a naked `loomweave install` does /// everything. Never yields a do-nothing plan. #[must_use] pub fn from_components(all: bool, components: &[InstallComponent]) -> Self { @@ -164,9 +164,9 @@ impl InstallPlan { } } - /// Whether to initialise `.clarion/` (the index). True for `All`. + /// Whether to initialise `.loomweave/` (the index). True for `All`. #[must_use] - pub fn init_clarion(self) -> bool { + pub fn init_loomweave(self) -> bool { matches!(self, Self::All) } @@ -189,13 +189,13 @@ impl InstallPlan { matches!(self, Self::All | Self::Components { codex: true, .. }) } - /// Whether to install the `clarion-workflow` skill pack for Claude Code. + /// Whether to install the `loomweave-workflow` skill pack for Claude Code. #[must_use] pub fn skills(self) -> bool { matches!(self, Self::All | Self::Components { skills: true, .. }) } - /// Whether to install the `clarion-workflow` skill pack for Codex. + /// Whether to install the `loomweave-workflow` skill pack for Codex. #[must_use] pub fn codex_skills(self) -> bool { matches!( @@ -219,7 +219,7 @@ impl InstallPlan { /// /// # Errors /// -/// Returns an error if `.clarion/` already exists without `--force`, if the +/// Returns an error if `.loomweave/` already exists without `--force`, if the /// target directory cannot be canonicalised, or if any filesystem or database /// operation fails. pub fn run( @@ -243,7 +243,7 @@ pub fn run( // defensive guard rather than silently succeeding. validate_plan(plan)?; - if plan.init_clarion() { + if plan.init_loomweave() { initialise_project(&project_root, force)?; } @@ -278,7 +278,7 @@ fn validate_plan(plan: InstallPlan) -> Result<()> { // `from_components` cannot produce a do-nothing plan, but a hand-built // `Components { skills: false, hooks: false }` still could, so keep a // defensive guard rather than silently succeeding. - if !plan.init_clarion() + if !plan.init_loomweave() && !plan.claude_code() && !plan.codex() && !plan.skills() @@ -288,59 +288,60 @@ fn validate_plan(plan: InstallPlan) -> Result<()> { bail!( "nothing to install: pass --claude-code, --codex, --skills, \ --codex-skills, --hooks, --all, \ - or run bare `clarion install` to do everything." + or run bare `loomweave install` to do everything." ); } Ok(()) } fn initialise_project(project_root: &Path, force: bool) -> Result<()> { - let clarion_dir = project_root.join(".clarion"); - let exists = clarion_dir.exists(); - // `All` (including naked install) treats an existing .clarion/ as + let loomweave_dir = project_root.join(".loomweave"); + let exists = loomweave_dir.exists(); + // `All` (including naked install) treats an existing .loomweave/ as // already-initialised and skips re-init, still applying the idempotent - // components. A non-directory .clarion is not a usable index, so refuse - // rather than "succeed" with skills/hooks atop a project with no clarion.db. + // components. A non-directory .loomweave is not a usable index, so refuse + // rather than "succeed" with skills/hooks atop a project with no loomweave.db. // Component-only installs skip this block. if exists && !force { - if !clarion_dir.is_dir() { + if !loomweave_dir.is_dir() { bail!( - "found a non-directory at {}; expected an initialised .clarion/ \ + "found a non-directory at {}; expected an initialised .loomweave/ \ directory. Remove it (or pass --force) and re-run.", - clarion_dir.display() + loomweave_dir.display() ); } println!( - "{} already initialised; skipping .clarion/ init (pass --force to recreate).", - clarion_dir.display() + "{} already initialised; skipping .loomweave/ init (pass --force to recreate).", + loomweave_dir.display() ); return Ok(()); } if exists { // --force overwrite path. - if !clarion_dir.is_dir() { + if !loomweave_dir.is_dir() { bail!( - "--force can only overwrite an existing .clarion/ directory; \ + "--force can only overwrite an existing .loomweave/ directory; \ found non-directory at {}.", - clarion_dir.display() + loomweave_dir.display() ); } - fs::remove_dir_all(&clarion_dir) - .with_context(|| format!("remove existing {}", clarion_dir.display()))?; + fs::remove_dir_all(&loomweave_dir) + .with_context(|| format!("remove existing {}", loomweave_dir.display()))?; } - fs::create_dir_all(&clarion_dir).with_context(|| format!("mkdir {}", clarion_dir.display()))?; + fs::create_dir_all(&loomweave_dir) + .with_context(|| format!("mkdir {}", loomweave_dir.display()))?; - // Cleanup guard: if any post-mkdir step fails, remove .clarion/ before + // Cleanup guard: if any post-mkdir step fails, remove .loomweave/ before // bubbling the error so the next install attempt isn't blocked by the // "already exists" check (clarion-ed5017139f). - if let Err(err) = populate_after_mkdir(&clarion_dir, project_root) { - if let Err(cleanup_err) = fs::remove_dir_all(&clarion_dir) { + if let Err(err) = populate_after_mkdir(&loomweave_dir, project_root) { + if let Err(cleanup_err) = fs::remove_dir_all(&loomweave_dir) { tracing::warn!( - clarion_dir = %clarion_dir.display(), + loomweave_dir = %loomweave_dir.display(), error = %cleanup_err, - "install failed and cleanup of partial .clarion/ also failed; \ + "install failed and cleanup of partial .loomweave/ also failed; \ manual rm -rf may be required" ); } @@ -348,10 +349,10 @@ fn initialise_project(project_root: &Path, force: bool) -> Result<()> { } tracing::info!( - clarion_dir = %clarion_dir.display(), - "clarion install complete" + loomweave_dir = %loomweave_dir.display(), + "loomweave install complete" ); - println!("Initialised {}", clarion_dir.display()); + println!("Initialised {}", loomweave_dir.display()); Ok(()) } @@ -388,28 +389,28 @@ fn install_codex(codex_config_path: Option<&Path>) -> Result<()> { fn install_claude_skills(project_root: &Path) -> Result<()> { let report = crate::skill_pack::install_claude_skill_pack(project_root) - .context("install clarion-workflow skill pack for Claude Code")?; + .context("install loomweave-workflow skill pack for Claude Code")?; if report.copied { println!( - "Installed clarion-workflow skill into {}/.claude/skills", + "Installed loomweave-workflow skill into {}/.claude/skills", project_root.display() ); } else { - println!("clarion-workflow Claude Code skill already up to date"); + println!("loomweave-workflow Claude Code skill already up to date"); } Ok(()) } fn install_codex_skills(project_root: &Path) -> Result<()> { let report = crate::skill_pack::install_codex_skill_pack(project_root) - .context("install clarion-workflow skill pack for Codex")?; + .context("install loomweave-workflow skill pack for Codex")?; if report.copied { println!( - "Installed clarion-workflow skill into {}/.agents/skills", + "Installed loomweave-workflow skill into {}/.agents/skills", project_root.display() ); } else { - println!("clarion-workflow Codex skill already up to date"); + println!("loomweave-workflow Codex skill already up to date"); } Ok(()) } @@ -419,46 +420,46 @@ fn install_hooks(project_root: &Path) -> Result<()> { .context("merge SessionStart hook into .claude/settings.json")?; if changed { println!( - "Added clarion SessionStart hook to {}/.claude/settings.json", + "Added loomweave SessionStart hook to {}/.claude/settings.json", project_root.display() ); } else { - println!("clarion SessionStart hook already present"); + println!("loomweave SessionStart hook already present"); } Ok(()) } fn install_integration_bindings(project_root: &Path) -> Result<()> { let changed = crate::integration_bindings::install_bindings(project_root) - .context("install local Clarion/Filigree/Wardline integration bindings")?; + .context("install local Loomweave/Filigree/Wardline integration bindings")?; if changed { - println!("Installed local Clarion/Filigree/Wardline integration bindings"); + println!("Installed local Loomweave/Filigree/Wardline integration bindings"); } else { - println!("Local Clarion/Filigree/Wardline integration bindings already up to date"); + println!("Local Loomweave/Filigree/Wardline integration bindings already up to date"); } Ok(()) } -fn populate_after_mkdir(clarion_dir: &Path, project_root: &Path) -> Result<()> { - let db_path = clarion_dir.join("clarion.db"); - initialise_db(&db_path).context("initialise clarion.db")?; +fn populate_after_mkdir(loomweave_dir: &Path, project_root: &Path) -> Result<()> { + let db_path = loomweave_dir.join("loomweave.db"); + initialise_db(&db_path).context("initialise loomweave.db")?; - let config_path = clarion_dir.join("config.json"); + let config_path = loomweave_dir.join("config.json"); fs::write(&config_path, CONFIG_JSON_STUB) .with_context(|| format!("write {}", config_path.display()))?; - let gitignore_path = clarion_dir.join(".gitignore"); + let gitignore_path = loomweave_dir.join(".gitignore"); fs::write(&gitignore_path, GITIGNORE_CONTENTS) .with_context(|| format!("write {}", gitignore_path.display()))?; - let yaml_path = project_root.join("clarion.yaml"); + let yaml_path = project_root.join("loomweave.yaml"); if yaml_path.exists() { tracing::debug!( path = %yaml_path.display(), - "clarion.yaml already exists; leaving untouched" + "loomweave.yaml already exists; leaving untouched" ); } else { - fs::write(&yaml_path, CLARION_YAML_STUB) + fs::write(&yaml_path, LOOMWEAVE_YAML_STUB) .with_context(|| format!("write {}", yaml_path.display()))?; } Ok(()) @@ -481,7 +482,7 @@ mod tests { // Naked install: no flags -> everything (same as --all). let naked = InstallPlan::from_components(false, &[]); assert_eq!(naked, InstallPlan::All); - assert!(naked.init_clarion()); + assert!(naked.init_loomweave()); assert!(naked.claude_code()); assert!(naked.codex()); assert!(naked.skills()); @@ -500,7 +501,7 @@ mod tests { hooks: false } ); - assert!(!skills.init_clarion()); + assert!(!skills.init_loomweave()); assert!(!skills.claude_code()); assert!(!skills.codex()); assert!(skills.skills()); @@ -519,7 +520,7 @@ mod tests { hooks: true } ); - assert!(!hooks.init_clarion()); + assert!(!hooks.init_loomweave()); assert!(!hooks.claude_code()); assert!(!hooks.codex()); assert!(!hooks.skills()); @@ -529,7 +530,7 @@ mod tests { // --all: everything (component flags ignored). let all = InstallPlan::from_components(true, &[]); assert_eq!(all, InstallPlan::All); - assert!(all.init_clarion()); + assert!(all.init_loomweave()); assert!(all.claude_code()); assert!(all.codex()); assert!(all.skills()); @@ -557,7 +558,7 @@ mod tests { hooks: true } ); - assert!(!both.init_clarion()); + assert!(!both.init_loomweave()); assert!(both.claude_code()); assert!(both.codex()); assert!(both.skills()); @@ -590,7 +591,7 @@ mod tests { for components in cases { let plan = InstallPlan::from_components(all, components); assert!( - plan.init_clarion() + plan.init_loomweave() || plan.claude_code() || plan.codex() || plan.skills() diff --git a/crates/clarion-cli/src/instance.rs b/crates/loomweave-cli/src/instance.rs similarity index 95% rename from crates/clarion-cli/src/instance.rs rename to crates/loomweave-cli/src/instance.rs index 075cfdf4..235a3fab 100644 --- a/crates/clarion-cli/src/instance.rs +++ b/crates/loomweave-cli/src/instance.rs @@ -9,7 +9,7 @@ use uuid::Uuid; const INSTANCE_ID_FILE: &str = "instance_id"; -/// A validated Clarion project instance ID — guaranteed to be a UUID at the +/// A validated Loomweave project instance ID — guaranteed to be a UUID at the /// type level. The inner `Uuid` is private and the only ways to construct /// one are [`load_or_create`] (reads/creates the persisted file) and /// [`parse_instance_id`] (parses a candidate string, used by tests). @@ -42,7 +42,7 @@ impl Serialize for InstanceId { } pub fn load_or_create(project_root: &Path) -> Result { - let path = project_root.join(".clarion").join(INSTANCE_ID_FILE); + let path = project_root.join(".loomweave").join(INSTANCE_ID_FILE); match fs::read_to_string(&path) { Ok(raw) => read_existing_instance_id(&path, &raw), Err(err) if err.kind() == io::ErrorKind::NotFound => create_instance_id(&path), @@ -142,7 +142,7 @@ pub(crate) fn parse_instance_id_for_test(raw: &str) -> Result { fn invalid_instance_id(path: &Path, source: &uuid::Error) -> anyhow::Error { anyhow!( - "invalid Clarion instance ID in {}: {source}; expected a UUID", + "invalid Loomweave instance ID in {}: {source}; expected a UUID", path.display() ) } diff --git a/crates/clarion-cli/src/integration_bindings.rs b/crates/loomweave-cli/src/integration_bindings.rs similarity index 90% rename from crates/clarion-cli/src/integration_bindings.rs rename to crates/loomweave-cli/src/integration_bindings.rs index a9d407a3..13825548 100644 --- a/crates/clarion-cli/src/integration_bindings.rs +++ b/crates/loomweave-cli/src/integration_bindings.rs @@ -1,7 +1,7 @@ -//! Local three-way Clarion/Filigree/Wardline dogfood bindings. +//! Local three-way Loomweave/Filigree/Wardline dogfood bindings. //! //! These are intentionally configuration bindings, not a shared runtime: -//! Clarion enables its own optional HTTP and Filigree read surfaces, Wardline +//! Loomweave enables its own optional HTTP and Filigree read surfaces, Wardline //! receives the two peer URLs, and the project-local `.mcp.json` launches //! Wardline with the same URLs for MCP scans. @@ -12,8 +12,8 @@ use std::path::{Path, PathBuf}; use anyhow::{Context, Result, bail}; use serde_json::{Map, Value, json}; -const CLARION_HTTP_BIND: &str = "127.0.0.1:9111"; -const CLARION_HTTP_URL: &str = "http://127.0.0.1:9111"; +const LOOMWEAVE_HTTP_BIND: &str = "127.0.0.1:9111"; +const LOOMWEAVE_HTTP_URL: &str = "http://127.0.0.1:9111"; const DEFAULT_FILIGREE_BASE_URL: &str = "http://127.0.0.1:8766"; #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -33,7 +33,7 @@ struct DesiredBindings { pub fn binding_state(project_root: &Path) -> BindingState { let desired = desired_bindings(project_root); match ( - clarion_yaml_ok(project_root, &desired), + loomweave_yaml_ok(project_root, &desired), wardline_yaml_ok(project_root, &desired), wardline_mcp_ok(project_root, &desired), ) { @@ -53,7 +53,7 @@ pub fn binding_state(project_root: &Path) -> BindingState { pub fn install_bindings(project_root: &Path) -> Result { let desired = desired_bindings(project_root); let mut changed = false; - changed |= install_clarion_yaml(project_root, &desired)?; + changed |= install_loomweave_yaml(project_root, &desired)?; changed |= install_wardline_yaml(project_root, &desired)?; changed |= install_wardline_mcp(project_root, &desired)?; Ok(changed) @@ -64,7 +64,7 @@ fn desired_bindings(project_root: &Path) -> DesiredBindings { .or_else(|| configured_filigree_base_url(project_root)) .unwrap_or_else(|| DEFAULT_FILIGREE_BASE_URL.to_owned()); let wardline_filigree_url = format!( - "{}/api/loom/scan-results", + "{}/api/weft/scan-results", filigree_base_url.trim_end_matches('/') ); DesiredBindings { @@ -80,7 +80,7 @@ fn live_filigree_base_url(project_root: &Path) -> Option { } fn configured_filigree_base_url(project_root: &Path) -> Option { - let path = project_root.join("clarion.yaml"); + let path = project_root.join("loomweave.yaml"); let value = read_yaml_value(&path).ok()?; value .get("integrations") @@ -91,8 +91,8 @@ fn configured_filigree_base_url(project_root: &Path) -> Option { .map(str::to_owned) } -fn clarion_yaml_ok(project_root: &Path, desired: &DesiredBindings) -> Result { - let path = project_root.join("clarion.yaml"); +fn loomweave_yaml_ok(project_root: &Path, desired: &DesiredBindings) -> Result { + let path = project_root.join("loomweave.yaml"); if !path.exists() { return Ok(false); } @@ -114,7 +114,7 @@ fn clarion_yaml_ok(project_root: &Path, desired: &DesiredBindings) -> Result Result Result Result { - let path = project_root.join("clarion.yaml"); +fn install_loomweave_yaml(project_root: &Path, desired: &DesiredBindings) -> Result { + let path = project_root.join("loomweave.yaml"); let mut value = read_yaml_value_or_empty(&path)?; let root = object_mut(&mut value, &path)?; root.entry("version".to_owned()).or_insert(json!(1)); @@ -172,7 +172,7 @@ fn install_clarion_yaml(project_root: &Path, desired: &DesiredBindings) -> Resul let filigree = ensure_object(integrations, "filigree")?; filigree.insert("enabled".to_owned(), json!(true)); filigree.insert("base_url".to_owned(), json!(desired.filigree_base_url)); - ensure_string(filigree, "actor", "clarion-mcp"); + ensure_string(filigree, "actor", "loomweave-mcp"); ensure_string(filigree, "token_env", "FILIGREE_API_TOKEN"); filigree .entry("timeout_seconds".to_owned()) @@ -181,7 +181,7 @@ fn install_clarion_yaml(project_root: &Path, desired: &DesiredBindings) -> Resul let serve = ensure_object(root, "serve")?; let http = ensure_object(serve, "http")?; http.insert("enabled".to_owned(), json!(true)); - http.insert("bind".to_owned(), json!(CLARION_HTTP_BIND)); + http.insert("bind".to_owned(), json!(LOOMWEAVE_HTTP_BIND)); http.insert("wardline_taint_write".to_owned(), json!(true)); write_yaml_if_changed(&path, &value) } @@ -190,8 +190,8 @@ fn install_wardline_yaml(project_root: &Path, desired: &DesiredBindings) -> Resu let path = project_root.join("wardline.yaml"); let mut value = read_yaml_value_or_empty(&path)?; let root = object_mut(&mut value, &path)?; - let clarion = ensure_object(root, "clarion")?; - clarion.insert("url".to_owned(), json!(CLARION_HTTP_URL)); + let loomweave = ensure_object(root, "loomweave")?; + loomweave.insert("url".to_owned(), json!(LOOMWEAVE_HTTP_URL)); let filigree = ensure_object(root, "filigree")?; filigree.insert("url".to_owned(), json!(desired.wardline_filigree_url)); write_yaml_if_changed(&path, &value) @@ -245,8 +245,8 @@ fn desired_wardline_args(desired: &DesiredBindings) -> Value { "mcp", "--root", ".", - "--clarion-url", - CLARION_HTTP_URL, + "--loomweave-url", + LOOMWEAVE_HTTP_URL, "--filigree-url", desired.wardline_filigree_url ]) diff --git a/crates/clarion-cli/src/main.rs b/crates/loomweave-cli/src/main.rs similarity index 92% rename from crates/clarion-cli/src/main.rs rename to crates/loomweave-cli/src/main.rs index 37f8b2cd..43c75988 100644 --- a/crates/clarion-cli/src/main.rs +++ b/crates/loomweave-cli/src/main.rs @@ -190,14 +190,14 @@ mod tests { #[test] fn analyze_does_not_load_dotenv() { - assert!(!loads(&["clarion", "analyze", "."])); + assert!(!loads(&["loomweave", "analyze", "."])); } #[test] fn guidance_editor_subcommands_do_not_load_dotenv() { // create/edit spawn $VISUAL/$EDITOR; a repo .env must not feed them. assert!(!loads(&[ - "clarion", + "loomweave", "guidance", "create", "--scope-level", @@ -205,7 +205,12 @@ mod tests { "--match", "kind:function", ])); - assert!(!loads(&["clarion", "guidance", "edit", "core:guidance:x"])); + assert!(!loads(&[ + "loomweave", + "guidance", + "edit", + "core:guidance:x" + ])); } #[test] @@ -213,14 +218,14 @@ mod tests { // promote resolves a Filigree token from a .env-supplied token_env; // excluding it would regress authenticated promotion. These commands // never spawn an editor, so loading .env is safe. - assert!(loads(&["clarion", "guidance", "promote", "obs-123"])); - assert!(loads(&["clarion", "guidance", "show", "core:guidance:x"])); - assert!(loads(&["clarion", "guidance", "list"])); - assert!(loads(&["clarion", "guidance", "export", "--to", "out"])); + assert!(loads(&["loomweave", "guidance", "promote", "obs-123"])); + assert!(loads(&["loomweave", "guidance", "show", "core:guidance:x"])); + assert!(loads(&["loomweave", "guidance", "list"])); + assert!(loads(&["loomweave", "guidance", "export", "--to", "out"])); } #[test] fn other_commands_load_dotenv() { - assert!(loads(&["clarion", "doctor"])); + assert!(loads(&["loomweave", "doctor"])); } } diff --git a/crates/clarion-cli/src/mcp_registration.rs b/crates/loomweave-cli/src/mcp_registration.rs similarity index 77% rename from crates/clarion-cli/src/mcp_registration.rs rename to crates/loomweave-cli/src/mcp_registration.rs index 29a35869..07059268 100644 --- a/crates/clarion-cli/src/mcp_registration.rs +++ b/crates/loomweave-cli/src/mcp_registration.rs @@ -1,31 +1,31 @@ -//! Clarion MCP server-entry detection and never-clobber merge. +//! Loomweave MCP server-entry detection and never-clobber merge. //! -//! `clarion install --claude-code` writes the project-local `.mcp.json` entry -//! for Claude Code. `clarion install --codex` writes the user-level Codex -//! `config.toml` entry. `clarion doctor` detects a missing or mis-pointed +//! `loomweave install --claude-code` writes the project-local `.mcp.json` entry +//! for Claude Code. `loomweave install --codex` writes the user-level Codex +//! `config.toml` entry. `loomweave doctor` detects a missing or mis-pointed //! Claude Code entry and — under `--fix` — repairs it. //! //! Merge semantics mirror [`crate::hooks_settings`]: parse the existing JSON, -//! touch only the `mcpServers.clarion` key, and preserve every other server +//! touch only the `mcpServers.loomweave` key, and preserve every other server //! (e.g. a sibling `filigree` entry) and top-level key. A fresh entry uses the -//! current `clarion` executable; an existing entry refreshes stale `clarion` +//! current `loomweave` executable; an existing entry refreshes stale `loomweave` //! executable paths and corrects `args` to the runtime-autodiscovery form while -//! preserving deliberately customised non-Clarion wrapper commands. +//! preserving deliberately customised non-Loomweave wrapper commands. //! -//! Security posture for the owned `clarion` entry: `.mcp.json` is a +//! Security posture for the owned `loomweave` entry: `.mcp.json` is a //! repository-committed file, so a hostile checkout can ship an entry whose //! `command` points at an attacker-controlled executable that the MCP client -//! will later launch. `doctor` must therefore never report a `clarion` entry -//! whose `command` is not a Clarion executable as healthy — that would be a -//! false all-clear on a poisoned config. But Clarion also cannot tell a +//! will later launch. `doctor` must therefore never report a `loomweave` entry +//! whose `command` is not a Loomweave executable as healthy — that would be a +//! false all-clear on a poisoned config. But Loomweave also cannot tell a //! malicious command from a *deliberate* wrapper binary (a nix/bazel shim, a //! sandbox launcher, a pinned absolute path), so it must not silently clobber //! one either. The chosen policy is **warn, don't clobber**: an owned entry -//! whose `command` basename is not `clarion`/`clarion.exe` is classified +//! whose `command` basename is not `loomweave`/`loomweave.exe` is classified //! [`McpState::UntrustedCommand`], which `doctor` flags (failing the gate //! without `--fix`) while leaving the command in place for the operator to -//! adjudicate. `--fix` still repairs `args`/stale `clarion` paths but never -//! replaces a non-Clarion command. +//! adjudicate. `--fix` still repairs `args`/stale `loomweave` paths but never +//! replaces a non-Loomweave command. use std::ffi::OsStr; use std::fs; @@ -34,34 +34,34 @@ use std::path::{Path, PathBuf}; use anyhow::{Context, Result, bail}; use serde_json::{Map, Value, json}; -/// The `mcpServers` key Clarion owns. -pub const SERVER_KEY: &str = "clarion"; +/// The `mcpServers` key Loomweave owns. +pub const SERVER_KEY: &str = "loomweave"; -/// Read-only health of the `.mcp.json` Clarion registration, for `doctor`. +/// Read-only health of the `.mcp.json` Loomweave registration, for `doctor`. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum McpState { - /// A `clarion` stdio server is registered and runs `serve` using the MCP + /// A `loomweave` stdio server is registered and runs `serve` using the MCP /// client's current working directory for project discovery. Present, - /// A `clarion` entry exists but is not the runtime-autodiscovery form - /// (wrong args, stale `clarion` executable path, or not a `serve` + /// A `loomweave` entry exists but is not the runtime-autodiscovery form + /// (wrong args, stale `loomweave` executable path, or not a `serve` /// invocation). Repairable in place. Stale, - /// A `clarion` entry exists whose `command` is not a Clarion executable - /// (its basename is not `clarion`/`clarion.exe`). This may be a deliberate + /// A `loomweave` entry exists whose `command` is not a Loomweave executable + /// (its basename is not `loomweave`/`loomweave.exe`). This may be a deliberate /// wrapper binary or a malicious entry shipped in a hostile checkout; /// `doctor` cannot tell them apart, so it flags the entry for operator /// review and never auto-replaces the command. `--fix` still corrects /// `args` but leaves the `command` untouched. UntrustedCommand, - /// No `.mcp.json`, or it has no `clarion` server entry. + /// No `.mcp.json`, or it has no `loomweave` server entry. Missing, /// `.mcp.json` exists but is not parseable JSON (or has a non-object shape). /// The merge refuses to clobber it, so this cannot be auto-repaired. Unparseable, } -/// The `args` a stdio `clarion` MCP entry must carry. +/// The `args` a stdio `loomweave` MCP entry must carry. /// /// Claude Code project configs and Codex global configs should use runtime /// project autodiscovery from the client working directory. Pinning `--path` @@ -76,44 +76,44 @@ fn desired_arg_strings() -> Vec<&'static str> { } /// Return the command path to write into fresh MCP configs. -fn clarion_command() -> String { +fn loomweave_command() -> String { match std::env::current_exe() { - Ok(path) if executable_name_is_clarion(path.file_name()) => path.display().to_string(), - _ => "clarion".to_owned(), + Ok(path) if executable_name_is_loomweave(path.file_name()) => path.display().to_string(), + _ => "loomweave".to_owned(), } } -fn executable_name_is_clarion(name: Option<&OsStr>) -> bool { +fn executable_name_is_loomweave(name: Option<&OsStr>) -> bool { let Some(name) = name.and_then(OsStr::to_str) else { return false; }; - name == "clarion" || name == "clarion.exe" + name == "loomweave" || name == "loomweave.exe" } -fn command_string_is_clarion(command: &str) -> bool { - executable_name_is_clarion(Path::new(command).file_name()) +fn command_string_is_loomweave(command: &str) -> bool { + executable_name_is_loomweave(Path::new(command).file_name()) } /// True if `entry.args` runs `serve` (no pinned project path) under the current -/// Clarion executable. A non-Clarion command is handled separately as +/// Loomweave executable. A non-Loomweave command is handled separately as /// [`McpState::UntrustedCommand`] and is never treated as the healthy form. fn entry_uses_runtime_project(entry: &Value) -> bool { let Some(args) = entry.get("args").and_then(Value::as_array) else { return false; }; let strs: Vec<&str> = args.iter().filter_map(Value::as_str).collect(); - strs == desired_arg_strings() && entry_command_is_current_clarion(entry) + strs == desired_arg_strings() && entry_command_is_current_loomweave(entry) } -/// True if the entry's `command` is the current Clarion executable. -fn entry_command_is_current_clarion(entry: &Value) -> bool { - entry.get("command").and_then(Value::as_str) == Some(clarion_command().as_str()) +/// True if the entry's `command` is the current Loomweave executable. +fn entry_command_is_current_loomweave(entry: &Value) -> bool { + entry.get("command").and_then(Value::as_str) == Some(loomweave_command().as_str()) } -/// The `command` string of the owned `clarion` entry, if any. Used by `doctor` +/// The `command` string of the owned `loomweave` entry, if any. Used by `doctor` /// to name an unrecognized command in its report. #[must_use] -pub fn clarion_entry_command(project_root: &Path) -> Option { +pub fn loomweave_entry_command(project_root: &Path) -> Option { let raw = fs::read_to_string(project_root.join(".mcp.json")).ok()?; let root: Value = serde_json::from_str(&raw).ok()?; root.get("mcpServers")? @@ -123,7 +123,7 @@ pub fn clarion_entry_command(project_root: &Path) -> Option { .map(ToOwned::to_owned) } -/// Classify the `.mcp.json` Clarion entry without writing anything. +/// Classify the `.mcp.json` Loomweave entry without writing anything. #[must_use] pub fn mcp_entry_state(project_root: &Path) -> McpState { let path = project_root.join(".mcp.json"); @@ -146,13 +146,13 @@ pub fn mcp_entry_state(project_root: &Path) -> McpState { let Some(entry) = root.get("mcpServers").and_then(|m| m.get(SERVER_KEY)) else { return McpState::Missing; }; - // Security: an owned entry whose command is not a Clarion executable is + // Security: an owned entry whose command is not a Loomweave executable is // never reported healthy and never auto-replaced (see module docs). It is // surfaced for operator review regardless of whether its args look right. if entry .get("command") .and_then(Value::as_str) - .is_some_and(|command| !command_string_is_clarion(command)) + .is_some_and(|command| !command_string_is_loomweave(command)) { return McpState::UntrustedCommand; } @@ -164,13 +164,13 @@ pub fn mcp_entry_state(project_root: &Path) -> McpState { } /// Read `.mcp.json` under `project_root` (creating `{}` if absent), merge -/// Clarion's `serve` entry, and write it back pretty-printed. Returns `true` +/// Loomweave's `serve` entry, and write it back pretty-printed. Returns `true` /// if the file changed. /// /// Never-clobber: an existing object entry keeps `type` and `env`; `args` -/// are corrected, stale `clarion` executable paths are refreshed, and -/// non-Clarion wrapper commands are preserved. A fresh entry is written with -/// the current `clarion` command. All other servers and top-level keys are +/// are corrected, stale `loomweave` executable paths are refreshed, and +/// non-Loomweave wrapper commands are preserved. A fresh entry is written with +/// the current `loomweave` command. All other servers and top-level keys are /// preserved. /// /// # Errors @@ -212,7 +212,7 @@ pub fn install_mcp_entry(project_root: &Path) -> Result { } let want_args = desired_args(); - let want_command = clarion_command(); + let want_command = loomweave_command(); let obj = root.as_object_mut().expect("root is object"); let servers = obj .entry("mcpServers") @@ -221,7 +221,7 @@ pub fn install_mcp_entry(project_root: &Path) -> Result { let changed = match servers.get_mut(SERVER_KEY) { // Existing object entry: preserve type/env and deliberate wrappers, - // correct args, and refresh stale clarion executable paths. + // correct args, and refresh stale loomweave executable paths. Some(entry) if entry.is_object() => { let entry = entry.as_object_mut().expect("entry is object"); let mut changed = false; @@ -234,7 +234,7 @@ pub fn install_mcp_entry(project_root: &Path) -> Result { .get("command") .and_then(Value::as_str) .is_none_or(|command| { - command_string_is_clarion(command) && command != want_command + command_string_is_loomweave(command) && command != want_command }); if should_refresh_command { entry.insert("command".to_string(), Value::String(want_command.clone())); @@ -279,7 +279,7 @@ pub fn install_mcp_entry(project_root: &Path) -> Result { /// Codex reads a global config, so tests use [`install_codex_mcp_entry`] with /// an explicit path rather than writing to the real user config. pub fn codex_config_path() -> Result { - if let Some(path) = std::env::var_os("CLARION_CODEX_CONFIG") { + if let Some(path) = std::env::var_os("LOOMWEAVE_CODEX_CONFIG") { return Ok(PathBuf::from(path)); } let Some(home) = std::env::var_os("HOME") else { @@ -288,10 +288,10 @@ pub fn codex_config_path() -> Result { Ok(PathBuf::from(home).join(".codex").join("config.toml")) } -/// Merge Clarion's stdio MCP server into Codex's TOML config. +/// Merge Loomweave's stdio MCP server into Codex's TOML config. /// /// The global Codex entry deliberately does not include a project path; Codex -/// starts the stdio server in the active workspace and Clarion's `serve` +/// starts the stdio server in the active workspace and Loomweave's `serve` /// default path (`.`) resolves from there. pub fn install_codex_mcp_entry(config_path: &Path) -> Result { if let Some(parent) = config_path.parent() { @@ -308,23 +308,23 @@ pub fn install_codex_mcp_entry(config_path: &Path) -> Result { let parsed: toml::Value = existing .parse() .with_context(|| format!("parse {}", config_path.display()))?; - if codex_config_has_desired_clarion(&parsed) { + if codex_config_has_desired_loomweave(&parsed) { return Ok(false); } } let updated = upsert_toml_table( &existing, - "mcp_servers.clarion", - &codex_server_block(&clarion_command()), + "mcp_servers.loomweave", + &codex_server_block(&loomweave_command()), ); write_text_if_changed(config_path, &updated) } -fn codex_config_has_desired_clarion(parsed: &toml::Value) -> bool { +fn codex_config_has_desired_loomweave(parsed: &toml::Value) -> bool { let Some(entry) = parsed .get("mcp_servers") - .and_then(|servers| servers.get("clarion")) + .and_then(|servers| servers.get("loomweave")) .and_then(toml::Value::as_table) else { return false; @@ -346,12 +346,12 @@ fn codex_config_has_desired_clarion(parsed: &toml::Value) -> bool { } fn toml_command_is_current_or_custom(command: &str) -> bool { - !command_string_is_clarion(command) || command == clarion_command() + !command_string_is_loomweave(command) || command == loomweave_command() } fn codex_server_block(command: &str) -> String { format!( - "[mcp_servers.clarion]\ncommand = \"{}\"\nargs = [\"serve\"]\n", + "[mcp_servers.loomweave]\ncommand = \"{}\"\nargs = [\"serve\"]\n", toml_quote(command) ) } @@ -428,10 +428,10 @@ mod tests { install_mcp_entry(dir.path()).unwrap(); let raw = fs::read_to_string(dir.path().join(".mcp.json")).unwrap(); let v: Value = serde_json::from_str(&raw).unwrap(); - let entry = &v["mcpServers"]["clarion"]; + let entry = &v["mcpServers"]["loomweave"]; assert!( - entry["command"].as_str().unwrap().ends_with("clarion"), - "fresh entry should point at a clarion executable: {entry:?}" + entry["command"].as_str().unwrap().ends_with("loomweave"), + "fresh entry should point at a loomweave executable: {entry:?}" ); assert_eq!(entry["type"], "stdio"); assert_eq!( @@ -444,25 +444,25 @@ mod tests { #[test] fn install_preserves_other_servers_and_keeps_custom_wrapper_command() { let dir = tempfile::tempdir().unwrap(); - // Pre-existing file with a sibling server and a clarion entry that has a + // Pre-existing file with a sibling server and a loomweave entry that has a // deliberately customised wrapper command but a WRONG --path. fs::write( dir.path().join(".mcp.json"), r#"{ "mcpServers": { "filigree": {"type": "stdio", "command": "/opt/filigree-mcp", "args": []}, - "clarion": {"type": "stdio", "command": "/custom/bin/clarion-wrapper", "args": ["serve", "--path", "/old/proj"], "env": {}} + "loomweave": {"type": "stdio", "command": "/custom/bin/loomweave-wrapper", "args": ["serve", "--path", "/old/proj"], "env": {}} } }"#, ) .unwrap(); - // A non-Clarion command is flagged for review, never silently healthy — + // A non-Loomweave command is flagged for review, never silently healthy — // doctor cannot tell a deliberate wrapper from a malicious entry. assert_eq!( mcp_entry_state(dir.path()), McpState::UntrustedCommand, - "a non-clarion command is UntrustedCommand, regardless of args" + "a non-loomweave command is UntrustedCommand, regardless of args" ); assert!(install_mcp_entry(dir.path()).unwrap()); @@ -473,12 +473,12 @@ mod tests { assert_eq!(v["mcpServers"]["filigree"]["command"], "/opt/filigree-mcp"); // Custom wrapper command PRESERVED (never clobbered), args corrected. assert_eq!( - v["mcpServers"]["clarion"]["command"], "/custom/bin/clarion-wrapper", + v["mcpServers"]["loomweave"]["command"], "/custom/bin/loomweave-wrapper", "a customised wrapper command must be preserved, not clobbered" ); let canon = dir.path().canonicalize().unwrap().display().to_string(); assert_eq!( - v["mcpServers"]["clarion"]["args"], + v["mcpServers"]["loomweave"]["args"], serde_json::json!(["serve"]), "stale --path pin should be removed: {canon}" ); @@ -500,7 +500,7 @@ mod tests { fs::write( dir.path().join(".mcp.json"), format!( - r#"{{"mcpServers":{{"clarion":{{"type":"stdio","command":"./evil-mcp.sh","args":["serve","--path",{canon:?}],"env":{{}}}}}}}}"# + r#"{{"mcpServers":{{"loomweave":{{"type":"stdio","command":"./evil-mcp.sh","args":["serve","--path",{canon:?}],"env":{{}}}}}}}}"# ), ) .unwrap(); @@ -511,7 +511,7 @@ mod tests { "matching args must NOT make an untrusted command healthy" ); assert_eq!( - super::clarion_entry_command(dir.path()).as_deref(), + super::loomweave_entry_command(dir.path()).as_deref(), Some("./evil-mcp.sh") ); @@ -522,7 +522,7 @@ mod tests { serde_json::from_str(&fs::read_to_string(dir.path().join(".mcp.json")).unwrap()) .unwrap(); assert_eq!( - v["mcpServers"]["clarion"]["command"], "./evil-mcp.sh", + v["mcpServers"]["loomweave"]["command"], "./evil-mcp.sh", "doctor must not clobber the command on --fix" ); assert_eq!( @@ -533,13 +533,13 @@ mod tests { } #[test] - fn install_refreshes_stale_clarion_executable_path() { + fn install_refreshes_stale_loomweave_executable_path() { let dir = tempfile::tempdir().unwrap(); fs::write( dir.path().join(".mcp.json"), r#"{ "mcpServers": { - "clarion": {"type": "stdio", "command": "/tmp/old-target/release/clarion", "args": ["serve"], "env": {}} + "loomweave": {"type": "stdio", "command": "/tmp/old-target/release/loomweave", "args": ["serve"], "env": {}} } }"#, ) @@ -548,7 +548,7 @@ mod tests { assert_eq!( mcp_entry_state(dir.path()), McpState::Stale, - "a stale clarion executable path is Stale even when args are already correct" + "a stale loomweave executable path is Stale even when args are already correct" ); assert!(install_mcp_entry(dir.path()).unwrap()); @@ -556,21 +556,21 @@ mod tests { serde_json::from_str(&fs::read_to_string(dir.path().join(".mcp.json")).unwrap()) .unwrap(); assert!( - v["mcpServers"]["clarion"]["command"] + v["mcpServers"]["loomweave"]["command"] .as_str() .unwrap() - .ends_with("clarion"), - "clarion command should be refreshed to the current executable" + .ends_with("loomweave"), + "loomweave command should be refreshed to the current executable" ); assert_ne!( - v["mcpServers"]["clarion"]["command"], "/tmp/old-target/release/clarion", - "stale clarion executable path should not be preserved" + v["mcpServers"]["loomweave"]["command"], "/tmp/old-target/release/loomweave", + "stale loomweave executable path should not be preserved" ); assert_eq!(mcp_entry_state(dir.path()), McpState::Present); } #[test] - fn codex_entry_upserts_clarion_without_touching_other_servers() { + fn codex_entry_upserts_loomweave_without_touching_other_servers() { let dir = tempfile::tempdir().unwrap(); let config_path = dir.path().join("config.toml"); fs::write( @@ -588,8 +588,8 @@ mod tests { "sibling server was not preserved: {raw}" ); assert!( - raw.contains("[mcp_servers.clarion]"), - "clarion Codex server missing: {raw}" + raw.contains("[mcp_servers.loomweave]"), + "loomweave Codex server missing: {raw}" ); assert!( raw.contains("args = [\"serve\"]"), @@ -598,12 +598,12 @@ mod tests { } #[test] - fn codex_entry_refreshes_stale_clarion_executable_path() { + fn codex_entry_refreshes_stale_loomweave_executable_path() { let dir = tempfile::tempdir().unwrap(); let config_path = dir.path().join("config.toml"); fs::write( &config_path, - "[mcp_servers.clarion]\ncommand = \"/tmp/old-target/release/clarion\"\nargs = [\"serve\"]\n", + "[mcp_servers.loomweave]\ncommand = \"/tmp/old-target/release/loomweave\"\nargs = [\"serve\"]\n", ) .unwrap(); @@ -611,8 +611,8 @@ mod tests { let raw = fs::read_to_string(&config_path).unwrap(); assert!( - !raw.contains("/tmp/old-target/release/clarion"), - "stale clarion executable path should not be preserved: {raw}" + !raw.contains("/tmp/old-target/release/loomweave"), + "stale loomweave executable path should not be preserved: {raw}" ); assert!(raw.contains("args = [\"serve\"]")); } diff --git a/crates/clarion-cli/src/run_lifecycle.rs b/crates/loomweave-cli/src/run_lifecycle.rs similarity index 97% rename from crates/clarion-cli/src/run_lifecycle.rs rename to crates/loomweave-cli/src/run_lifecycle.rs index f624877f..1e7be1af 100644 --- a/crates/clarion-cli/src/run_lifecycle.rs +++ b/crates/loomweave-cli/src/run_lifecycle.rs @@ -1,5 +1,5 @@ use anyhow::{Context, Result}; -use clarion_storage::{Writer, commands::WriterCmd}; +use loomweave_storage::{Writer, commands::WriterCmd}; pub(crate) async fn begin_run( writer: &Writer, diff --git a/crates/clarion-cli/src/sarif.rs b/crates/loomweave-cli/src/sarif.rs similarity index 98% rename from crates/clarion-cli/src/sarif.rs rename to crates/loomweave-cli/src/sarif.rs index a7a89594..0c08869d 100644 --- a/crates/clarion-cli/src/sarif.rs +++ b/crates/loomweave-cli/src/sarif.rs @@ -4,8 +4,8 @@ use std::path::{Path, PathBuf}; use anyhow::{Context, Result, anyhow}; use serde_json::{Map, Value, json}; -use clarion_federation::filigree::FiligreeHttpClient; -use clarion_federation::scan_results::ScanResultsRequest; +use loomweave_federation::filigree::FiligreeHttpClient; +use loomweave_federation::scan_results::ScanResultsRequest; /// Translate SARIF findings from a file and post them to Filigree. #[allow(clippy::too_many_lines, clippy::collapsible_if)] @@ -22,7 +22,7 @@ pub fn run_import(file: &Path, scan_source_opt: Option, project_path: &P std::env::var(name).ok() }) .context("build Filigree HTTP client")? - .ok_or_else(|| anyhow!("Filigree integration is disabled in clarion.yaml"))?; + .ok_or_else(|| anyhow!("Filigree integration is disabled in loomweave.yaml"))?; // Read and parse SARIF file let content = fs::read_to_string(file) diff --git a/crates/clarion-cli/src/secret_scan.rs b/crates/loomweave-cli/src/secret_scan.rs similarity index 96% rename from crates/clarion-cli/src/secret_scan.rs rename to crates/loomweave-cli/src/secret_scan.rs index b828df5e..b64c9e09 100644 --- a/crates/clarion-cli/src/secret_scan.rs +++ b/crates/loomweave-cli/src/secret_scan.rs @@ -1,4 +1,4 @@ -//! Pre-ingest secret scanning for `clarion analyze`. +//! Pre-ingest secret scanning for `loomweave analyze`. //! //! Exit codes used by this module: //! - 0: analysis may continue, with or without an explicit secret override. @@ -23,17 +23,17 @@ mod files; mod findings; use anyhow::{Context, Result}; -use clarion_core::BriefingBlockReason; -use clarion_scanner::{Detection, Scanner, SuppressionResult}; -use clarion_storage::{Writer, commands::EntityRecord}; pub(crate) use files::collect_scan_files; use findings::{ FindingConfidence, FindingKind, FindingSeverity, PendingFinding, secret_detected_finding, }; +use loomweave_core::BriefingBlockReason; +use loomweave_scanner::{Detection, Scanner, SuppressionResult}; +use loomweave_storage::{Writer, commands::EntityRecord}; use serde_json::json; -const SECRET_OVERRIDE_ALLOWED: &str = "CLA-SEC-UNREDACTED-SECRETS-ALLOWED"; -const OVERRIDE_UNCONFIRMED: &str = "CLA-INFRA-SECRET-OVERRIDE-UNCONFIRMED"; +const SECRET_OVERRIDE_ALLOWED: &str = "LMWV-SEC-UNREDACTED-SECRETS-ALLOWED"; +const OVERRIDE_UNCONFIRMED: &str = "LMWV-INFRA-SECRET-OVERRIDE-UNCONFIRMED"; const CONFIRM_TOKEN: &str = "yes-i-understand"; #[derive(Debug, Clone, Default)] @@ -277,12 +277,12 @@ pub(crate) fn pre_ingest( continue; } // ADR-013 §"Override — --allow-unredacted-secrets": each detection - // emits its own `CLA-SEC-SECRET-DETECTED` finding regardless of + // emits its own `LMWV-SEC-SECRET-DETECTED` finding regardless of // whether the operator subsequently overrode the block. The override - // finding (`CLA-SEC-UNREDACTED-SECRETS-ALLOWED`) is additive — it + // finding (`LMWV-SEC-UNREDACTED-SECRETS-ALLOWED`) is additive — it // records the operator decision but does not replace the // per-(rule,file,line) audit row, so a security review running - // `filigree list --rule-id=CLA-SEC-SECRET-DETECTED` enumerates the + // `filigree list --rule-id=LMWV-SEC-SECRET-DETECTED` enumerates the // full detection population. findings.extend( allowed @@ -399,7 +399,7 @@ fn scan_one_source_file( where F: Fn(&[u8]) -> Vec + Sync + ?Sized, { - let mut handle = clarion_core::plugin::jail::safe_open(project_root, file) + let mut handle = loomweave_core::plugin::jail::safe_open(project_root, file) .with_context(|| format!("safe-open {}", file.display()))?; let canonical_file = canonical_or_original(file); let mut buf = Vec::new(); diff --git a/crates/clarion-cli/src/secret_scan/anchors.rs b/crates/loomweave-cli/src/secret_scan/anchors.rs similarity index 99% rename from crates/clarion-cli/src/secret_scan/anchors.rs rename to crates/loomweave-cli/src/secret_scan/anchors.rs index 84155e99..ae539703 100644 --- a/crates/clarion-cli/src/secret_scan/anchors.rs +++ b/crates/loomweave-cli/src/secret_scan/anchors.rs @@ -4,7 +4,7 @@ use std::{ }; use anyhow::{Context, Result}; -use clarion_storage::{ +use loomweave_storage::{ Writer, commands::{EntityRecord, WriterCmd}, }; diff --git a/crates/clarion-cli/src/secret_scan/baseline.rs b/crates/loomweave-cli/src/secret_scan/baseline.rs similarity index 81% rename from crates/clarion-cli/src/secret_scan/baseline.rs rename to crates/loomweave-cli/src/secret_scan/baseline.rs index 268038a8..fb03f880 100644 --- a/crates/clarion-cli/src/secret_scan/baseline.rs +++ b/crates/loomweave-cli/src/secret_scan/baseline.rs @@ -1,7 +1,7 @@ use std::path::Path; use anyhow::{Context, Result}; -use clarion_scanner::{Baseline, BaselineError}; +use loomweave_scanner::{Baseline, BaselineError}; use serde_json::json; use super::normalize_project_path; @@ -9,12 +9,12 @@ use crate::secret_scan::findings::{ FindingConfidence, FindingKind, FindingSeverity, PendingFinding, }; -const BASELINE_NO_JUSTIFICATION: &str = "CLA-INFRA-SECRET-BASELINE-NO-JUSTIFICATION"; -const BASELINE_MATCH: &str = "CLA-INFRA-SECRET-BASELINE-MATCH"; +const BASELINE_NO_JUSTIFICATION: &str = "LMWV-INFRA-SECRET-BASELINE-NO-JUSTIFICATION"; +const BASELINE_MATCH: &str = "LMWV-INFRA-SECRET-BASELINE-MATCH"; pub(super) fn load_for_scan(project_root: &Path) -> Result<(Baseline, Vec)> { - let path = project_root.join(".clarion/secrets-baseline.yaml"); - match clarion_scanner::load_baseline(&path) { + let path = project_root.join(".loomweave/secrets-baseline.yaml"); + match loomweave_scanner::load_baseline(&path) { Ok(baseline) => Ok((baseline, Vec::new())), Err(BaselineError::MissingJustifications { entries }) => Ok(( Baseline::empty(), diff --git a/crates/clarion-cli/src/secret_scan/files.rs b/crates/loomweave-cli/src/secret_scan/files.rs similarity index 97% rename from crates/clarion-cli/src/secret_scan/files.rs rename to crates/loomweave-cli/src/secret_scan/files.rs index b4cc74fd..654f8cbb 100644 --- a/crates/clarion-cli/src/secret_scan/files.rs +++ b/crates/loomweave-cli/src/secret_scan/files.rs @@ -8,7 +8,7 @@ use ignore::{DirEntry, WalkBuilder}; use super::canonical_or_original; const SKIP_DIRS: &[&str] = &[ - ".clarion", + ".loomweave", ".git", ".hg", ".svn", @@ -113,7 +113,7 @@ mod tests { write(root.join("nested/service.env"), "TOKEN=four\n"); write(root.join("nested/.env"), "TOKEN=five\n"); write(root.join("nested/not-env.txt"), "TOKEN=six\n"); - write(root.join(".clarion/.env"), "TOKEN=skip\n"); + write(root.join(".loomweave/.env"), "TOKEN=skip\n"); write(root.join("node_modules/.env"), "TOKEN=skip\n"); let files = collect_scan_files(root, &[root.join("src/app.py")]); @@ -126,7 +126,7 @@ mod tests { assert!(rel.contains(&"nested/.env".to_owned())); assert!(rel.contains(&"src/app.py".to_owned())); assert!(!rel.contains(&"nested/not-env.txt".to_owned())); - assert!(!rel.contains(&".clarion/.env".to_owned())); + assert!(!rel.contains(&".loomweave/.env".to_owned())); assert!(!rel.contains(&"node_modules/.env".to_owned())); } diff --git a/crates/clarion-cli/src/secret_scan/findings.rs b/crates/loomweave-cli/src/secret_scan/findings.rs similarity index 96% rename from crates/clarion-cli/src/secret_scan/findings.rs rename to crates/loomweave-cli/src/secret_scan/findings.rs index 74539014..532feeab 100644 --- a/crates/clarion-cli/src/secret_scan/findings.rs +++ b/crates/loomweave-cli/src/secret_scan/findings.rs @@ -4,8 +4,8 @@ use std::{ }; use anyhow::{Context, Result}; -use clarion_scanner::{Detection, SecretCategory}; -use clarion_storage::{ +use loomweave_scanner::{Detection, SecretCategory}; +use loomweave_storage::{ Writer, commands::{FindingRecord, WriterCmd}, }; @@ -13,7 +13,7 @@ use serde_json::json; use super::SecretScanOutcome; -const SECRET_DETECTED: &str = "CLA-SEC-SECRET-DETECTED"; +const SECRET_DETECTED: &str = "LMWV-SEC-SECRET-DETECTED"; #[derive(Debug, Clone)] pub(super) struct PendingFinding { @@ -133,7 +133,7 @@ pub(crate) async fn emit_findings( .send_wait(|ack| WriterCmd::InsertFinding { finding: Box::new(FindingRecord { id: finding_id.clone(), - tool: "clarion".to_owned(), + tool: "loomweave".to_owned(), tool_version: env!("CARGO_PKG_VERSION").to_owned(), run_id: run_id.to_owned(), rule_id: pending.rule_id.to_owned(), @@ -193,7 +193,7 @@ fn finding_entity_id(file_path: &Path, anchors: &BTreeMap) -> O #[cfg(test)] mod tests { use super::{FindingConfidence, finding_entity_id, secret_detected_finding}; - use clarion_scanner::{DetectSecretsRule, Detection, HashedSecret, SecretCategory}; + use loomweave_scanner::{DetectSecretsRule, Detection, HashedSecret, SecretCategory}; use std::{collections::BTreeMap, path::PathBuf}; #[test] diff --git a/crates/clarion-cli/src/sei_git.rs b/crates/loomweave-cli/src/sei_git.rs similarity index 98% rename from crates/clarion-cli/src/sei_git.rs rename to crates/loomweave-cli/src/sei_git.rs index 710e87a9..f6c85d93 100644 --- a/crates/clarion-cli/src/sei_git.rs +++ b/crates/loomweave-cli/src/sei_git.rs @@ -1,7 +1,7 @@ //! `GitRenameSource` implementations — the typed git-rename seam (REQ-C-05). //! //! The SEI matcher consumes a typed, locator-level git-rename signal -//! (`{old_locator, new_locator}`), never "Clarion's git code". Two suppliers +//! (`{old_locator, new_locator}`), never "Loomweave's git code". Two suppliers //! implement the same trait, with a shared file→locator translation: //! //! - [`ShellGitRenameSource`] — the v1 concrete supplier: shells @@ -30,8 +30,8 @@ //! So [`select_git_rename_source`] is **capability-aware**: for the staged-index //! window (empty base) the shell source is the authority regardless of `legis`; //! `legis` is selected only for a committed rev-range when configured AND -//! reachable. This guarantees Clarion-with-`legis` is never *worse* than -//! Clarion-without (the enrich-only invariant, loom.md §5). The matcher is +//! reachable. This guarantees Loomweave-with-`legis` is never *worse* than +//! Loomweave-without (the enrich-only invariant, weft.md §5). The matcher is //! fail-closed anyway — a rename is only a hint, confirmed by byte-identical body //! hash — so neither window choice can cause a *false* carry, only a missed one. //! @@ -54,8 +54,8 @@ use std::path::{Path, PathBuf}; use std::time::Duration; -use clarion_core::hardened_git_command; -use clarion_storage::{GitRename, GitRenameSource}; +use loomweave_core::hardened_git_command; +use loomweave_storage::{GitRename, GitRenameSource}; /// How long to wait on a `legis` HTTP probe/read before giving up and degrading /// to an empty signal. Short on purpose: `legis` is enrich-only, so a slow or @@ -327,7 +327,7 @@ fn legis_reachable(base_url: &str) -> bool { } /// Capability-aware, enrich-only selection of the git-rename supplier (REQ-C-05, -/// loom.md §5). `legis` is chosen ONLY when it is configured, the window is a +/// weft.md §5). `legis` is chosen ONLY when it is configured, the window is a /// committed rev-range (`!base.is_empty()`), AND it is reachable; in every other /// case — `legis` absent/unset/unreachable, or the staged-index window the shell /// source alone can answer — the shell source is the authority. The @@ -678,7 +678,7 @@ mod tests { #[test] fn selector_falls_back_to_shell_when_legis_absent() { - // No legis URL, committed base: shell source. Enrich-only — Clarion + // No legis URL, committed base: shell source. Enrich-only — Loomweave // without legis is unchanged. let tmp = std::env::temp_dir(); let src = select_git_rename_source(&tmp, None, "base123", vec![]); @@ -702,7 +702,7 @@ mod tests { /// selector handed the staged-index window to `legis`, the shell-detectable /// rename would be LOST and the entity's SEI would orphan instead of carry. /// The capability guard (`!base.is_empty()`) keeps the shell source for this - /// window, so Clarion-with-`legis` is never worse than Clarion-without. + /// window, so Loomweave-with-`legis` is never worse than Loomweave-without. #[test] fn selector_keeps_working_tree_rename_even_when_a_reachable_legis_sees_nothing() { let dir = tempfile::tempdir().unwrap(); diff --git a/crates/clarion-cli/src/serve.rs b/crates/loomweave-cli/src/serve.rs similarity index 92% rename from crates/clarion-cli/src/serve.rs rename to crates/loomweave-cli/src/serve.rs index 95b0e360..9496be06 100644 --- a/crates/clarion-cli/src/serve.rs +++ b/crates/loomweave-cli/src/serve.rs @@ -6,22 +6,22 @@ use std::thread; use std::time::Duration; use anyhow::{Context, Result, anyhow, ensure}; -use clarion_core::{ +use loomweave_core::{ ApiEmbeddingProvider, ApiEmbeddingProviderConfig, ClaudeCliProvider, ClaudeCliProviderConfig, CodexCliProvider, CodexCliProviderConfig, EmbeddingProvider, EmbeddingProviderError, LlmProvider, OpenRouterProvider, OpenRouterProviderConfig, Recording, RecordingProvider, }; -use clarion_federation::config::{ +use loomweave_federation::config::{ LlmConfig, McpConfig, ProviderSelection, SemanticSearchConfig, select_provider_with_env, }; -use clarion_federation::filigree::FiligreeHttpClient; -use clarion_storage::{DEFAULT_BATCH_SIZE, DEFAULT_CHANNEL_CAPACITY, ReaderPool, Writer}; +use loomweave_federation::filigree::FiligreeHttpClient; +use loomweave_storage::{DEFAULT_BATCH_SIZE, DEFAULT_CHANNEL_CAPACITY, ReaderPool, Writer}; pub fn run(path: &Path, config_path: Option<&Path>) -> Result<()> { - let db_path = path.join(".clarion").join("clarion.db"); + let db_path = path.join(".loomweave").join("loomweave.db"); ensure!( db_path.exists(), - "Clarion database not found at {}; run `clarion install --path {}` first", + "Loomweave database not found at {}; run `loomweave install --path {}` first", db_path.display(), path.display() ); @@ -30,8 +30,8 @@ pub fn run(path: &Path, config_path: Option<&Path>) -> Result<()> { .canonicalize() .with_context(|| format!("canonicalize project path {}", path.display()))?; let instance_id = crate::instance::load_or_create(&project_root) - .context("load Clarion project instance ID")?; - let default_config_path = path.join("clarion.yaml"); + .context("load Loomweave project instance ID")?; + let default_config_path = path.join("loomweave.yaml"); let config_path = config_path.unwrap_or(&default_config_path); let config = if config_path.exists() { McpConfig::from_path(config_path) @@ -50,7 +50,7 @@ pub fn run(path: &Path, config_path: Option<&Path>) -> Result<()> { // (which goes stale, the dogfood bug) — then build the client against the // resolved URL so `issues_for` reaches the running dashboard. The same // resolution is surfaced by `project_status`. - let filigree_resolution = clarion_federation::filigree_url::resolve_filigree_url( + let filigree_resolution = loomweave_federation::filigree_url::resolve_filigree_url( &config.integrations.filigree, &project_root, ); @@ -62,7 +62,7 @@ pub fn run(path: &Path, config_path: Option<&Path>) -> Result<()> { FiligreeHttpClient::from_config(&filigree_config, |name| std::env::var(name).ok()) .context("build Filigree HTTP client")?; - let diagnostics = clarion_mcp::DiagnosticsContext { + let diagnostics = loomweave_mcp::DiagnosticsContext { llm: llm_diagnostics, filigree: filigree_resolution, }; @@ -99,7 +99,7 @@ pub fn run(path: &Path, config_path: Option<&Path>) -> Result<()> { semantic_search_state(&config.semantic_search, embedding_provider), filigree_client, diagnostics, - clarion_mcp::McpToolPolicy { + loomweave_mcp::McpToolPolicy { enable_write_tools: config.serve.mcp.enable_write_tools, }, )?; @@ -109,7 +109,10 @@ pub fn run(path: &Path, config_path: Option<&Path>) -> Result<()> { /// Capture the LLM policy posture for `project_status`. `live` means a provider /// that actually dispatches (`OpenRouter` / Codex / Claude CLIs); the recording /// fixture and the disabled state are not live. -fn llm_diagnostics(selection: &ProviderSelection, llm: &LlmConfig) -> clarion_mcp::LlmDiagnostics { +fn llm_diagnostics( + selection: &ProviderSelection, + llm: &LlmConfig, +) -> loomweave_mcp::LlmDiagnostics { let (provider, live) = match selection { ProviderSelection::Disabled => ("disabled", false), ProviderSelection::Recording => ("recording", false), @@ -117,7 +120,7 @@ fn llm_diagnostics(selection: &ProviderSelection, llm: &LlmConfig) -> clarion_mc ProviderSelection::CodexCli => ("codex_cli", true), ProviderSelection::ClaudeCli => ("claude_cli", true), }; - clarion_mcp::LlmDiagnostics { + loomweave_mcp::LlmDiagnostics { provider: provider.to_owned(), live, allow_live_provider: llm.allow_live_provider, @@ -141,12 +144,12 @@ fn spawn_mcp_stdio( llm_provider: Option>, semantic_search: Option, filigree_client: Option, - diagnostics: clarion_mcp::DiagnosticsContext, - tool_policy: clarion_mcp::McpToolPolicy, + diagnostics: loomweave_mcp::DiagnosticsContext, + tool_policy: loomweave_mcp::McpToolPolicy, ) -> Result { let (result_tx, result_rx) = mpsc::channel(); let join = thread::Builder::new() - .name("clarion-mcp-stdio".to_owned()) + .name("loomweave-mcp-stdio".to_owned()) .spawn(move || { let result = run_mcp_stdio( project_root, @@ -174,8 +177,8 @@ fn run_mcp_stdio( llm_provider: Option>, semantic_search: Option, filigree_client: Option, - diagnostics: clarion_mcp::DiagnosticsContext, - tool_policy: clarion_mcp::McpToolPolicy, + diagnostics: loomweave_mcp::DiagnosticsContext, + tool_policy: loomweave_mcp::McpToolPolicy, ) -> Result<()> { let stdin = std::io::stdin(); let stdout = std::io::stdout(); @@ -187,7 +190,7 @@ fn run_mcp_stdio( .context("create MCP runtime")?; let _runtime_guard = runtime.enter(); let mut state = - clarion_mcp::ServerState::new(project_root, readers).with_tool_policy(tool_policy); + loomweave_mcp::ServerState::new(project_root, readers).with_tool_policy(tool_policy); let mut llm_writer = None; let mut llm_writer_join = None; if let Some(provider) = llm_provider { @@ -205,9 +208,13 @@ fn run_mcp_stdio( } state = state.with_diagnostics(diagnostics); - let serve_result = - clarion_mcp::serve_stdio_with_state_on_runtime(&runtime, &state, &mut reader, &mut writer) - .context("serve MCP stdio"); + let serve_result = loomweave_mcp::serve_stdio_with_state_on_runtime( + &runtime, + &state, + &mut reader, + &mut writer, + ) + .context("serve MCP stdio"); drop(state); drop(llm_writer); let writer_result = if let Some(handle) = llm_writer_join { diff --git a/crates/clarion-cli/src/skill_pack.rs b/crates/loomweave-cli/src/skill_pack.rs similarity index 89% rename from crates/clarion-cli/src/skill_pack.rs rename to crates/loomweave-cli/src/skill_pack.rs index 422922aa..c17c63ee 100644 --- a/crates/clarion-cli/src/skill_pack.rs +++ b/crates/loomweave-cli/src/skill_pack.rs @@ -1,9 +1,9 @@ -//! Embedded `clarion-workflow` skill pack and its on-disk installer. +//! Embedded `loomweave-workflow` skill pack and its on-disk installer. //! //! The pack is compiled into the binary with `include_str!` (matching the //! `include_str!` migration-embedding convention in -//! `clarion-storage/src/schema.rs`). The asset itself is owned by -//! `clarion-mcp` (which this crate depends on), so the embed below reaches +//! `loomweave-storage/src/schema.rs`). The asset itself is owned by +//! `loomweave-mcp` (which this crate depends on), so the embed below reaches //! *down* the dependency edge rather than inverting it (clarion-04391392c7). //! Each entry is `(relative_path, contents)`; //! growing the pack with a `references/` directory is a data change here, not a @@ -18,11 +18,11 @@ use anyhow::{Context, Result}; /// `(relative_path, contents)` for every file in the bundled skill pack. pub const SKILL_PACK: &[(&str, &str)] = &[( "SKILL.md", - include_str!("../../clarion-mcp/assets/skills/clarion-workflow/SKILL.md"), + include_str!("../../loomweave-mcp/assets/skills/loomweave-workflow/SKILL.md"), )]; /// The on-disk subdirectory name the pack installs into. -pub const PACK_DIR_NAME: &str = "clarion-workflow"; +pub const PACK_DIR_NAME: &str = "loomweave-workflow"; /// Deterministic blake3 hex digest over the pack's `(rel_path, contents)` /// entries. Order-stable because `SKILL_PACK` is a fixed slice. This is the @@ -49,7 +49,7 @@ const CLAUDE_SKILL_ROOT: &str = ".claude/skills"; /// The Codex / tool-agnostic skill root, relative to the project root. const CODEX_SKILL_ROOT: &str = ".agents/skills"; -/// The two skill roots Clarion can install into, relative to the project root. +/// The two skill roots Loomweave can install into, relative to the project root. const SKILL_ROOTS: &[&str] = &[CLAUDE_SKILL_ROOT, CODEX_SKILL_ROOT]; const FINGERPRINT_FILE: &str = ".fingerprint"; @@ -65,7 +65,7 @@ pub struct SkillInstallReport { /// Install (or re-sync on drift) the embedded skill pack into both skill roots /// under `project_root`, idempotently. /// -/// For each root, the pack lands at `/clarion-workflow/`. A +/// For each root, the pack lands at `/loomweave-workflow/`. A /// `.fingerprint` file recording [`pack_fingerprint`] is written alongside the /// pack files as a provenance marker. If the digest recomputed from the files /// on disk in every root (see [`installed_fingerprint`]) already equals the @@ -125,7 +125,7 @@ fn installed_fingerprint(dest: &Path) -> Option { Some(hasher.finalize().to_hex().to_string()) } -/// Read-only health of the installed skill pack, for `clarion doctor`. +/// Read-only health of the installed skill pack, for `loomweave doctor`. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SkillPackState { /// Every skill root holds the current pack bytes. @@ -167,7 +167,7 @@ pub fn skill_pack_state(project_root: &Path) -> SkillPackState { fn stage_and_swap(root: &Path, dest: &Path, fingerprint: &str) -> Result<()> { fs::create_dir_all(root).with_context(|| format!("mkdir {}", root.display()))?; // Stage in a sibling temp dir so the final rename is same-filesystem. - let staging = root.join(format!(".clarion-workflow.tmp-{}", std::process::id())); + let staging = root.join(format!(".loomweave-workflow.tmp-{}", std::process::id())); if staging.exists() { fs::remove_dir_all(&staging) .with_context(|| format!("clear stale staging {}", staging.display()))?; @@ -175,8 +175,8 @@ fn stage_and_swap(root: &Path, dest: &Path, fingerprint: &str) -> Result<()> { fs::create_dir_all(&staging).with_context(|| format!("mkdir {}", staging.display()))?; // Cleanup guard: if writing the staged files fails, remove the staging dir - // before bubbling the error so we don't leak a `.clarion-workflow.tmp-*` - // sibling. Matches the partial-state-cleanup precedent on the `.clarion/` + // before bubbling the error so we don't leak a `.loomweave-workflow.tmp-*` + // sibling. Matches the partial-state-cleanup precedent on the `.loomweave/` // path in install.rs. The original error is preserved. if let Err(err) = write_staged_pack(&staging, fingerprint) { let _ = fs::remove_dir_all(&staging); @@ -192,7 +192,7 @@ fn stage_and_swap(root: &Path, dest: &Path, fingerprint: &str) -> Result<()> { // backup, so the previously-installed pack is always recoverable; we never // delete `dest` ahead of a rename that might not happen. let had_existing = dest.exists(); - let backup = root.join(format!(".clarion-workflow.bak-{}", std::process::id())); + let backup = root.join(format!(".loomweave-workflow.bak-{}", std::process::id())); if had_existing { if backup.exists() { fs::remove_dir_all(&backup) @@ -252,7 +252,7 @@ mod tests { .expect("SKILL.md present in pack"); assert_eq!(*rel, "SKILL.md"); assert!( - contents.contains("name: clarion-workflow"), + contents.contains("name: loomweave-workflow"), "SKILL.md is missing its frontmatter name" ); } @@ -277,11 +277,11 @@ mod tests { let skill = dir .path() .join(root) - .join("clarion-workflow") + .join("loomweave-workflow") .join("SKILL.md"); assert!(skill.exists(), "missing {}", skill.display()); let body = std::fs::read_to_string(&skill).unwrap(); - assert!(body.contains("name: clarion-workflow")); + assert!(body.contains("name: loomweave-workflow")); } } @@ -317,7 +317,8 @@ mod tests { super::install_skill_pack(dir.path()).unwrap(); // Corrupt content (not delete) so the file is present but mismatched. std::fs::write( - dir.path().join(".claude/skills/clarion-workflow/SKILL.md"), + dir.path() + .join(".claude/skills/loomweave-workflow/SKILL.md"), "STALE", ) .unwrap(); @@ -329,7 +330,7 @@ mod tests { let dir = tempfile::tempdir().unwrap(); super::install_skill_pack(dir.path()).unwrap(); // Remove one root entirely: absence outranks drift -> Missing. - std::fs::remove_dir_all(dir.path().join(".agents/skills/clarion-workflow")).unwrap(); + std::fs::remove_dir_all(dir.path().join(".agents/skills/loomweave-workflow")).unwrap(); assert_eq!(skill_pack_state(dir.path()), SkillPackState::Missing); } @@ -339,7 +340,8 @@ mod tests { let dir = tempfile::tempdir().unwrap(); install_skill_pack(dir.path()).unwrap(); std::fs::write( - dir.path().join(".claude/skills/clarion-workflow/SKILL.md"), + dir.path() + .join(".claude/skills/loomweave-workflow/SKILL.md"), "STALE", ) .unwrap(); @@ -348,7 +350,7 @@ mod tests { for entry in std::fs::read_dir(&root).unwrap() { let name = entry.unwrap().file_name(); assert!( - !name.to_string_lossy().contains(".clarion-workflow.bak-"), + !name.to_string_lossy().contains(".loomweave-workflow.bak-"), "leftover backup dir: {name:?}" ); } @@ -360,18 +362,20 @@ mod tests { let dir = tempfile::tempdir().unwrap(); install_skill_pack(dir.path()).unwrap(); // Corrupt one installed copy + its fingerprint to simulate drift. - let skill = dir.path().join(".claude/skills/clarion-workflow/SKILL.md"); + let skill = dir + .path() + .join(".claude/skills/loomweave-workflow/SKILL.md"); std::fs::write(&skill, "STALE").unwrap(); let fp = dir .path() - .join(".claude/skills/clarion-workflow/.fingerprint"); + .join(".claude/skills/loomweave-workflow/.fingerprint"); std::fs::write(&fp, "deadbeef").unwrap(); let report = install_skill_pack(dir.path()).unwrap(); assert!(report.copied, "drift should trigger re-copy"); let body = std::fs::read_to_string(&skill).unwrap(); assert!( - body.contains("name: clarion-workflow"), + body.contains("name: loomweave-workflow"), "drift not repaired" ); } @@ -393,7 +397,7 @@ mod tests { } /// A failed re-install must be crash-safe: it surfaces the error, leaks no - /// `.clarion-workflow.tmp-*` / `.bak-*` sibling, and never destroys the + /// `.loomweave-workflow.tmp-*` / `.bak-*` sibling, and never destroys the /// already-installed pack (the guarantee the stage/backup/restore cleanup /// code exists to protect). The failure is the only portable injection /// point — `stage_and_swap` clears any pre-seeded staging dir on entry — so @@ -413,12 +417,12 @@ mod tests { let dir = tempfile::tempdir().unwrap(); install_skill_pack(dir.path()).expect("first install ok"); let root = dir.path().join(".claude/skills"); - let skill_md = root.join("clarion-workflow/SKILL.md"); + let skill_md = root.join("loomweave-workflow/SKILL.md"); // Force a drift so the next install attempts a swap, then make the skill // root read-only so staging the new pack into it fails with EACCES. // Drift is detected from pack *content* (installed_fingerprint rehashes - // SKILL.md), so corrupt SKILL.md itself; the clarion-workflow child dir + // SKILL.md), so corrupt SKILL.md itself; the loomweave-workflow child dir // stays writable, so this write succeeds before we lock the root. std::fs::write(&skill_md, "STALE — drifted content").unwrap(); std::fs::set_permissions(&root, std::fs::Permissions::from_mode(0o555)).unwrap(); @@ -430,8 +434,8 @@ mod tests { .filter_map(Result::ok) .map(|e| e.file_name().to_string_lossy().into_owned()) .filter(|name| { - name.starts_with(".clarion-workflow.tmp-") - || name.starts_with(".clarion-workflow.bak-") + name.starts_with(".loomweave-workflow.tmp-") + || name.starts_with(".loomweave-workflow.bak-") }) .collect(); diff --git a/crates/clarion-cli/src/stats.rs b/crates/loomweave-cli/src/stats.rs similarity index 100% rename from crates/clarion-cli/src/stats.rs rename to crates/loomweave-cli/src/stats.rs diff --git a/crates/clarion-cli/src/wardline_guidance.rs b/crates/loomweave-cli/src/wardline_guidance.rs similarity index 99% rename from crates/clarion-cli/src/wardline_guidance.rs rename to crates/loomweave-cli/src/wardline_guidance.rs index bd82f953..5d4a2762 100644 --- a/crates/clarion-cli/src/wardline_guidance.rs +++ b/crates/loomweave-cli/src/wardline_guidance.rs @@ -7,7 +7,7 @@ use rusqlite::{Connection, OpenFlags}; use serde::Deserialize; use serde_json::{Map, Value, json}; -use clarion_storage::{ +use loomweave_storage::{ GuidanceSheetInput, get_guidance_sheet, invalidate_summaries_for_sheet, slugify_guidance_name, upsert_guidance_sheet, }; @@ -371,7 +371,7 @@ fn collect_overlay_paths(dir: &Path, paths: &mut Vec) -> Res if entry.file_type()?.is_dir() { if matches!( file_name.as_ref(), - ".git" | ".clarion" | ".venv" | "target" | "node_modules" + ".git" | ".loomweave" | ".venv" | "target" | "node_modules" ) { continue; } @@ -761,7 +761,7 @@ fn now_iso8601(conn: &Connection) -> Result { #[cfg(test)] mod tests { use super::{overlay_scope, path_glob}; - use clarion_storage::glob_match; + use loomweave_storage::glob_match; #[test] fn root_overlay_scope_glob_matches_project_relative_paths() { diff --git a/crates/clarion-cli/tests/analyze.rs b/crates/loomweave-cli/tests/analyze.rs similarity index 91% rename from crates/clarion-cli/tests/analyze.rs rename to crates/loomweave-cli/tests/analyze.rs index ee8ecd4f..ef1651a8 100644 --- a/crates/clarion-cli/tests/analyze.rs +++ b/crates/loomweave-cli/tests/analyze.rs @@ -1,14 +1,14 @@ -//! `clarion analyze` Sprint-1 integration test. +//! `loomweave analyze` Sprint-1 integration test. use assert_cmd::Command; use rusqlite::Connection; -fn clarion_bin() -> Command { - let mut cmd = Command::cargo_bin("clarion").expect("clarion binary"); +fn loomweave_bin() -> Command { + let mut cmd = Command::cargo_bin("loomweave").expect("loomweave binary"); cmd.env( - "CLARION_CODEX_CONFIG", + "LOOMWEAVE_CODEX_CONFIG", std::env::temp_dir().join(format!( - "clarion-test-codex-config-{}.toml", + "loomweave-test-codex-config-{}.toml", std::process::id() )), ); @@ -16,7 +16,7 @@ fn clarion_bin() -> Command { } fn latest_run_config(project_root: &std::path::Path) -> serde_json::Value { - let conn = Connection::open(project_root.join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(project_root.join(".loomweave/loomweave.db")).unwrap(); let config_raw: String = conn .query_row( "SELECT config FROM runs ORDER BY started_at DESC LIMIT 1", @@ -28,7 +28,7 @@ fn latest_run_config(project_root: &std::path::Path) -> serde_json::Value { } fn latest_run_stats(project_root: &std::path::Path) -> serde_json::Value { - let conn = Connection::open(project_root.join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(project_root.join(".loomweave/loomweave.db")).unwrap(); let stats_raw: String = conn .query_row( "SELECT stats FROM runs ORDER BY started_at DESC LIMIT 1", @@ -77,7 +77,7 @@ while True: "jsonrpc": "2.0", "id": ident, "result": { - "name": "clarion-plugin-calls", + "name": "loomweave-plugin-calls", "version": "0.1.0", "ontology_version": "0.4.0", "capabilities": {}, @@ -170,11 +170,11 @@ while True: #[cfg(unix)] const AMBIGUOUS_CALLS_PLUGIN_MANIFEST: &str = r#" [plugin] -name = "clarion-plugin-calls" +name = "loomweave-plugin-calls" plugin_id = "callsfixture" version = "0.1.0" protocol_version = "1.0" -executable = "clarion-plugin-calls" +executable = "loomweave-plugin-calls" language = "callsfixture" extensions = ["call"] @@ -187,7 +187,7 @@ reads_outside_project_root = false [ontology] entity_kinds = ["module", "function"] edge_kinds = ["contains", "calls"] -rule_id_prefix = "CLA-CALLS-" +rule_id_prefix = "LMWV-CALLS-" ontology_version = "0.4.0" [ontology.roles] @@ -234,7 +234,7 @@ while True: "jsonrpc": "2.0", "id": ident, "result": { - "name": "clarion-plugin-imports", + "name": "loomweave-plugin-imports", "version": "0.1.0", "ontology_version": "0.6.0", "capabilities": {}, @@ -291,11 +291,11 @@ while True: #[cfg(unix)] const IMPORTS_PLUGIN_MANIFEST: &str = r#" [plugin] -name = "clarion-plugin-imports" +name = "loomweave-plugin-imports" plugin_id = "importsfixture" version = "0.1.0" protocol_version = "1.0" -executable = "clarion-plugin-imports" +executable = "loomweave-plugin-imports" language = "importsfixture" extensions = ["imp"] @@ -308,7 +308,7 @@ reads_outside_project_root = false [ontology] entity_kinds = ["module"] edge_kinds = ["imports"] -rule_id_prefix = "CLA-IMPORTS-" +rule_id_prefix = "LMWV-IMPORTS-" ontology_version = "0.6.0" [ontology.roles] @@ -363,7 +363,7 @@ while True: "jsonrpc": "2.0", "id": ident, "result": { - "name": "clarion-plugin-phase3", + "name": "loomweave-plugin-phase3", "version": "0.1.0", "ontology_version": "0.6.0", "capabilities": {}, @@ -410,11 +410,11 @@ while True: #[cfg(unix)] const PHASE3_PLUGIN_MANIFEST: &str = r#" [plugin] -name = "clarion-plugin-phase3" +name = "loomweave-plugin-phase3" plugin_id = "phase3fixture" version = "0.1.0" protocol_version = "1.0" -executable = "clarion-plugin-phase3" +executable = "loomweave-plugin-phase3" language = "phase3fixture" extensions = ["p3"] @@ -427,7 +427,7 @@ reads_outside_project_root = false [ontology] entity_kinds = ["module"] edge_kinds = ["imports"] -rule_id_prefix = "CLA-PHASE3-" +rule_id_prefix = "LMWV-PHASE3-" ontology_version = "0.6.0" [ontology.roles] @@ -438,7 +438,7 @@ file_scope = ["module"] fn write_ambiguous_calls_plugin(plugin_dir: &std::path::Path) { use std::os::unix::fs::PermissionsExt; - let plugin_script = plugin_dir.join("clarion-plugin-calls"); + let plugin_script = plugin_dir.join("loomweave-plugin-calls"); std::fs::write(&plugin_script, AMBIGUOUS_CALLS_PLUGIN_SCRIPT) .expect("write calls plugin script"); let mut perms = std::fs::metadata(&plugin_script) @@ -458,7 +458,7 @@ fn write_ambiguous_calls_plugin(plugin_dir: &std::path::Path) { fn write_imports_plugin(plugin_dir: &std::path::Path) { use std::os::unix::fs::PermissionsExt; - let plugin_script = plugin_dir.join("clarion-plugin-imports"); + let plugin_script = plugin_dir.join("loomweave-plugin-imports"); std::fs::write(&plugin_script, IMPORTS_PLUGIN_SCRIPT).expect("write imports plugin script"); let mut perms = std::fs::metadata(&plugin_script) .expect("stat imports plugin") @@ -474,7 +474,7 @@ fn write_imports_plugin(plugin_dir: &std::path::Path) { fn write_phase3_plugin(plugin_dir: &std::path::Path) { use std::os::unix::fs::PermissionsExt; - let plugin_script = plugin_dir.join("clarion-plugin-phase3"); + let plugin_script = plugin_dir.join("loomweave-plugin-phase3"); std::fs::write(&plugin_script, PHASE3_PLUGIN_SCRIPT).expect("write phase3 plugin script"); let mut perms = std::fs::metadata(&plugin_script) .expect("stat phase3 plugin") @@ -492,7 +492,7 @@ fn run_phase3_fixture(stems: &[&str], config_yaml: &str) -> tempfile::TempDir { let plugin_dir = tempfile::tempdir().unwrap(); write_phase3_plugin(plugin_dir.path()); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(project_dir.path()) .assert() @@ -501,12 +501,12 @@ fn run_phase3_fixture(stems: &[&str], config_yaml: &str) -> tempfile::TempDir { std::fs::write(project_dir.path().join(format!("{stem}.p3")), b"module\n") .expect("write phase3 fixture file"); } - let config_path = project_dir.path().join("phase3-clarion.yaml"); + let config_path = project_dir.path().join("phase3-loomweave.yaml"); std::fs::write(&config_path, config_yaml).expect("write phase3 config"); let plugin_path = std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap(); - clarion_bin() + loomweave_bin() .args(["analyze", "--config"]) .arg(&config_path) .arg(project_dir.path()) @@ -578,7 +578,7 @@ while True: "jsonrpc": "2.0", "id": ident, "result": { - "name": "clarion-plugin-categorised", + "name": "loomweave-plugin-categorised", "version": "0.1.0", "ontology_version": "0.1.0", "capabilities": {}, @@ -640,11 +640,11 @@ while True: #[cfg(unix)] const CATEGORISED_PLUGIN_MANIFEST: &str = r#" [plugin] -name = "clarion-plugin-categorised" +name = "loomweave-plugin-categorised" plugin_id = "catfixture" version = "0.1.0" protocol_version = "1.0" -executable = "clarion-plugin-categorised" +executable = "loomweave-plugin-categorised" language = "catfixture" extensions = ["cat"] @@ -657,7 +657,7 @@ reads_outside_project_root = false [ontology] entity_kinds = ["module", "function"] edge_kinds = ["contains"] -rule_id_prefix = "CLA-CAT-" +rule_id_prefix = "LMWV-CAT-" ontology_version = "0.1.0" [ontology.roles] @@ -669,7 +669,7 @@ callable = ["function"] fn write_categorised_plugin(plugin_dir: &std::path::Path) { use std::os::unix::fs::PermissionsExt; - let plugin_script = plugin_dir.join("clarion-plugin-categorised"); + let plugin_script = plugin_dir.join("loomweave-plugin-categorised"); std::fs::write(&plugin_script, CATEGORISED_PLUGIN_SCRIPT) .expect("write categorised plugin script"); let mut perms = std::fs::metadata(&plugin_script) @@ -787,7 +787,7 @@ fn analyze_persists_plugin_tags_and_populates_embedding_sidecar() { write_categorised_plugin(plugin_dir.path()); let (embedding_url, embedding_server) = spawn_embedding_mock(); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(project_dir.path()) .assert() @@ -797,7 +797,7 @@ fn analyze_persists_plugin_tags_and_populates_embedding_sidecar() { "def main():\n pass\n", ) .expect("write categorised fixture source"); - let config_path = project_dir.path().join("clarion.yaml"); + let config_path = project_dir.path().join("loomweave.yaml"); std::fs::write( &config_path, format!( @@ -818,7 +818,7 @@ semantic_search: let plugin_path = std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap(); - clarion_bin() + loomweave_bin() .args(["analyze", "--config"]) .arg(&config_path) .arg(project_dir.path()) @@ -839,7 +839,7 @@ semantic_search: requests[0] ); - let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap(); let tag_count: i64 = conn .query_row( "SELECT COUNT(*) FROM entity_tags \ @@ -852,7 +852,7 @@ semantic_search: .expect("query persisted tags"); assert_eq!(tag_count, 1, "plugin-emitted tags must be persisted"); - let sidecar = project_dir.path().join(".clarion/embeddings.db"); + let sidecar = project_dir.path().join(".loomweave/embeddings.db"); assert!(sidecar.exists(), "analyze should create embeddings sidecar"); let sidecar_conn = Connection::open(sidecar).unwrap(); let embedding_count: i64 = sidecar_conn @@ -874,26 +874,26 @@ semantic_search: fn analyze_without_plugins_writes_skipped_run_row() { let dir = tempfile::tempdir().unwrap(); - // Scrub PATH — if the developer or CI image has any clarion-plugin-* + // Scrub PATH — if the developer or CI image has any loomweave-plugin-* // binary installed (including the project's own fixture), discovery // will find it and the run transitions out of `skipped_no_plugins`. // The sibling test `analyze_failrun_exits_nonzero_with_run_row_marked_failed` // uses the same pattern. - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") .assert() .success(); - clarion_bin() + loomweave_bin() .args(["analyze"]) .arg(dir.path()) .env("PATH", "") .assert() .success(); - let conn = Connection::open(dir.path().join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(dir.path().join(".loomweave/loomweave.db")).unwrap(); let (count, status): (i64, String) = conn .query_row( "SELECT COUNT(*), COALESCE(MAX(status), '') FROM runs", @@ -919,14 +919,14 @@ fn analyze_without_plugins_writes_skipped_run_row() { #[test] fn analyze_migrates_a_stale_db_instead_of_failing() { let dir = tempfile::tempdir().unwrap(); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") .assert() .success(); - let db = dir.path().join(".clarion/clarion.db"); + let db = dir.path().join(".loomweave/loomweave.db"); // Rewind to the pre-0007 (v6) shape: no `analyzed_at_commit`, no v7 ledger // row, user_version back to 6 — exactly an upgraded-binary-vs-old-DB state. { @@ -943,7 +943,7 @@ fn analyze_migrates_a_stale_db_instead_of_failing() { assert_eq!(uv, 6, "precondition: DB rewound to v6"); } - clarion_bin() + loomweave_bin() .args(["analyze"]) .arg(dir.path()) .env("PATH", "") @@ -958,7 +958,7 @@ fn analyze_migrates_a_stale_db_instead_of_failing() { .unwrap(); assert_eq!( uv, - i64::from(clarion_storage::schema::CURRENT_SCHEMA_VERSION), + i64::from(loomweave_storage::schema::CURRENT_SCHEMA_VERSION), "analyze must apply all pending migrations" ); let has_column: i64 = conn @@ -975,14 +975,14 @@ fn analyze_migrates_a_stale_db_instead_of_failing() { fn analyze_default_config_records_clustering_defaults() { let dir = tempfile::tempdir().unwrap(); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") .assert() .success(); - clarion_bin() + loomweave_bin() .args(["analyze"]) .arg(dir.path()) .env("PATH", "") @@ -1009,13 +1009,13 @@ fn analyze_default_config_records_clustering_defaults() { fn analyze_config_file_overrides_clustering_seed_and_algorithm() { let dir = tempfile::tempdir().unwrap(); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") .assert() .success(); - let config_path = dir.path().join("custom-clarion.yaml"); + let config_path = dir.path().join("custom-loomweave.yaml"); std::fs::write( &config_path, r" @@ -1028,7 +1028,7 @@ analysis: ) .expect("write analyze config"); - clarion_bin() + loomweave_bin() .args(["analyze", "--config"]) .arg(&config_path) .arg(dir.path()) @@ -1052,13 +1052,13 @@ analysis: fn analyze_rejects_invalid_clustering_algorithm() { let dir = tempfile::tempdir().unwrap(); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") .assert() .success(); - let config_path = dir.path().join("bad-clarion.yaml"); + let config_path = dir.path().join("bad-loomweave.yaml"); std::fs::write( &config_path, r" @@ -1069,7 +1069,7 @@ analysis: ) .expect("write invalid analyze config"); - let out = clarion_bin() + let out = loomweave_bin() .args(["analyze", "--config"]) .arg(&config_path) .arg(dir.path()) @@ -1082,7 +1082,7 @@ analysis: "stderr should identify invalid clustering algorithm; got: {stderr}" ); - let conn = Connection::open(dir.path().join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(dir.path().join(".loomweave/loomweave.db")).unwrap(); let run_count: i64 = conn .query_row("SELECT COUNT(*) FROM runs", [], |row| row.get(0)) .expect("query run count"); @@ -1096,7 +1096,7 @@ fn analyze_phase3_emits_subsystem_entities_and_edges() { &["auth_a", "auth_b", "billing_a", "billing_b"], &phase3_config(2), ); - let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap(); let subsystem_count: i64 = conn .query_row( @@ -1138,7 +1138,7 @@ fn analyze_phase3_emits_subsystem_entities_and_edges() { #[test] fn analyze_phase3_is_deterministic_across_two_runs() { fn signature(project_root: &std::path::Path) -> Vec<(String, String)> { - let conn = Connection::open(project_root.join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(project_root.join(".loomweave/loomweave.db")).unwrap(); conn.prepare("SELECT id, properties FROM entities WHERE kind = 'subsystem' ORDER BY id") .unwrap() .query_map([], |row| Ok((row.get(0)?, row.get(1)?))) @@ -1158,7 +1158,7 @@ fn analyze_phase3_is_deterministic_across_two_runs() { #[test] fn analyze_phase3_skips_empty_graph_with_stats() { let project_dir = run_phase3_fixture(&["solo"], &phase3_config(2)); - let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap(); let subsystem_count: i64 = conn .query_row( "SELECT COUNT(*) FROM entities WHERE kind = 'subsystem'", @@ -1186,11 +1186,11 @@ fn analyze_phase3_skips_empty_graph_with_stats() { #[test] fn analyze_phase3_emits_weak_modularity_fact_when_below_threshold() { let project_dir = run_phase3_fixture(&["weak_a", "weak_b"], &phase3_config(2)); - let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap(); let row: (String, String, String, String, String) = conn .query_row( "SELECT rule_id, kind, severity, status, properties \ - FROM findings WHERE rule_id = 'CLA-FACT-CLUSTERING-WEAK-MODULARITY'", + FROM findings WHERE rule_id = 'LMWV-FACT-CLUSTERING-WEAK-MODULARITY'", [], |row| { Ok(( @@ -1203,7 +1203,7 @@ fn analyze_phase3_emits_weak_modularity_fact_when_below_threshold() { }, ) .expect("query weak modularity finding"); - assert_eq!(row.0, "CLA-FACT-CLUSTERING-WEAK-MODULARITY"); + assert_eq!(row.0, "LMWV-FACT-CLUSTERING-WEAK-MODULARITY"); assert_eq!(row.1, "fact"); assert_eq!(row.2, "INFO"); assert_eq!(row.3, "open"); @@ -1229,7 +1229,7 @@ fn phase3_project_for_rerun(stems: &[&str]) -> (tempfile::TempDir, tempfile::Tem let plugin_dir = tempfile::tempdir().unwrap(); write_phase3_plugin(plugin_dir.path()); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(project_dir.path()) .assert() @@ -1238,7 +1238,7 @@ fn phase3_project_for_rerun(stems: &[&str]) -> (tempfile::TempDir, tempfile::Tem std::fs::write(project_dir.path().join(format!("{stem}.p3")), b"module\n") .expect("write phase3 fixture file"); } - let config_path = project_dir.path().join("phase3-clarion.yaml"); + let config_path = project_dir.path().join("phase3-loomweave.yaml"); std::fs::write(&config_path, phase3_config(2)).expect("write phase3 config"); let plugin_path = std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap(); @@ -1253,7 +1253,7 @@ fn run_phase3_analyze( config_path: &std::path::Path, plugin_path: &std::ffi::OsStr, ) { - clarion_bin() + loomweave_bin() .args(["analyze", "--config"]) .arg(config_path) .arg(project_root) @@ -1327,7 +1327,7 @@ fn spawn_capturing_filigree_mock( } /// clarion-ef8f64d5fd: the post-`CommitRun` deletion finding -/// (`CLA-FACT-ENTITY-DELETED`) must reach Filigree in the SAME run, not only the +/// (`LMWV-FACT-ENTITY-DELETED`) must reach Filigree in the SAME run, not only the /// store. Phase-8 emission runs *before* `CommitRun`, while the SEI mint pass /// persists deletion findings *after* it, so without a second emission pass the /// finding is stranded store-only even with `emit_findings=true`. End-to-end: @@ -1346,7 +1346,7 @@ fn analyze_emits_post_commit_deletion_finding_to_filigree() { let plugin_path = std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap(); - let (base_url, server) = spawn_capturing_filigree_mock("CLA-FACT-ENTITY-DELETED"); + let (base_url, server) = spawn_capturing_filigree_mock("LMWV-FACT-ENTITY-DELETED"); // Run 2: rewrite the config to enable emission against the mock, delete a // source file, and re-run. @@ -1362,7 +1362,7 @@ fn analyze_emits_post_commit_deletion_finding_to_filigree() { let requests = server.join().expect("mock server thread"); let combined = requests.join("\n---POST BOUNDARY---\n"); assert!( - combined.contains("CLA-FACT-ENTITY-DELETED"), + combined.contains("LMWV-FACT-ENTITY-DELETED"), "the post-commit deletion finding must reach Filigree in the same run; \ captured {} POST(s): {combined}", requests.len(), @@ -1385,7 +1385,7 @@ fn analyze_emits_post_commit_tier_finding_to_filigree_at_project_anchor() { std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap(); { - let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap(); // Two subsystems → two tier findings, both anchored to the project root. // auth disagrees (MIXING); billing agrees (UNANIMOUS). They share // (rule-family, path, null line) but carry subsystem-distinct messages — @@ -1397,7 +1397,7 @@ fn analyze_emits_post_commit_tier_finding_to_filigree_at_project_anchor() { seed_wardline_tier(&conn, "phase3fixture:module:billing_b", "trusted"); } - let (base_url, server) = spawn_capturing_filigree_mock("CLA-FACT-SUBSYSTEM-TIER-UNANIMOUS"); + let (base_url, server) = spawn_capturing_filigree_mock("LMWV-FACT-SUBSYSTEM-TIER-UNANIMOUS"); std::fs::write(&config_path, phase3_config_with_filigree(2, &base_url)) .expect("rewrite config with filigree emission enabled"); @@ -1410,7 +1410,7 @@ fn analyze_emits_post_commit_tier_finding_to_filigree_at_project_anchor() { let requests = server.join().expect("mock server thread"); let posted = requests .iter() - .find(|r| r.contains("CLA-FACT-SUBSYSTEM-TIER-UNANIMOUS")) + .find(|r| r.contains("LMWV-FACT-SUBSYSTEM-TIER-UNANIMOUS")) .unwrap_or_else(|| { panic!( "the post-commit tier finding must reach Filigree; captured {} POST(s): {}", @@ -1420,8 +1420,8 @@ fn analyze_emits_post_commit_tier_finding_to_filigree_at_project_anchor() { }); // Both subsystems' tier findings ride the one Phase-8c batch... assert!( - posted.contains("CLA-FACT-TIER-SUBSYSTEM-MIXING") - && posted.contains("CLA-FACT-SUBSYSTEM-TIER-UNANIMOUS"), + posted.contains("LMWV-FACT-TIER-SUBSYSTEM-MIXING") + && posted.contains("LMWV-FACT-SUBSYSTEM-TIER-UNANIMOUS"), "both tier findings reach Filigree in one batch: {posted}" ); // ...anchored to the project root and flagged synthetic (non-file) so a @@ -1447,7 +1447,7 @@ fn analyze_emits_post_commit_tier_finding_to_filigree_at_project_anchor() { } /// REQ-ANALYZE-04 verification (verbatim): run analyze, delete a file, re-run; -/// assert a `CLA-FACT-ENTITY-DELETED` finding per previously-extracted entity in +/// assert a `LMWV-FACT-ENTITY-DELETED` finding per previously-extracted entity in /// the deleted file — and no false positives for entities still present. #[cfg(unix)] #[test] @@ -1464,12 +1464,12 @@ fn analyze_emits_entity_deleted_finding_when_file_removed() { &plugin_path, ); - let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap(); // The plugin's `module` entity carries the canonical finding shape. let (kind, severity, status): (String, String, String) = conn .query_row( "SELECT kind, severity, status FROM findings \ - WHERE rule_id = 'CLA-FACT-ENTITY-DELETED' \ + WHERE rule_id = 'LMWV-FACT-ENTITY-DELETED' \ AND entity_id = 'phase3fixture:module:billing_a'", [], |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)), @@ -1483,7 +1483,7 @@ fn analyze_emits_entity_deleted_finding_when_file_removed() { // entities — the core-minted `core:file:*` and the plugin `module` — and // nothing belonging to the surviving files. let deleted: std::collections::BTreeSet = conn - .prepare("SELECT entity_id FROM findings WHERE rule_id = 'CLA-FACT-ENTITY-DELETED'") + .prepare("SELECT entity_id FROM findings WHERE rule_id = 'LMWV-FACT-ENTITY-DELETED'") .unwrap() .query_map([], |row| row.get::<_, String>(0)) .unwrap() @@ -1500,7 +1500,7 @@ fn analyze_emits_entity_deleted_finding_when_file_removed() { } /// REQ-ANALYZE-04: a guidance sheet whose `guides` edge targets a deleted entity -/// produces `CLA-FACT-GUIDANCE-ORPHAN`, and the deleted entity's cached summaries +/// produces `LMWV-FACT-GUIDANCE-ORPHAN`, and the deleted entity's cached summaries /// are invalidated. Both halves are injected between runs (the fixture plugin /// emits neither guidance sheets nor summaries), then a file is deleted + re-run. #[cfg(unix)] @@ -1510,7 +1510,7 @@ fn analyze_emits_guidance_orphan_and_invalidates_summary_cache_on_deletion() { phase3_project_for_rerun(&["auth_a", "auth_b", "billing_a", "billing_b"]); let plugin_path = std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap(); - let db_path = project_dir.path().join(".clarion/clarion.db"); + let db_path = project_dir.path().join(".loomweave/loomweave.db"); let target = "phase3fixture:module:billing_a"; // Inject a guidance sheet that `guides` the soon-to-be-deleted entity, plus a @@ -1555,12 +1555,12 @@ fn analyze_emits_guidance_orphan_and_invalidates_summary_cache_on_deletion() { let (rule_id, severity, anchor, related): (String, String, String, String) = conn .query_row( "SELECT rule_id, severity, entity_id, related_entities \ - FROM findings WHERE rule_id = 'CLA-FACT-GUIDANCE-ORPHAN'", + FROM findings WHERE rule_id = 'LMWV-FACT-GUIDANCE-ORPHAN'", [], |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)), ) .expect("query guidance-orphan finding"); - assert_eq!(rule_id, "CLA-FACT-GUIDANCE-ORPHAN"); + assert_eq!(rule_id, "LMWV-FACT-GUIDANCE-ORPHAN"); assert_eq!(severity, "WARN"); assert_eq!(anchor, "core:guidance:g1"); let related: serde_json::Value = serde_json::from_str(&related).unwrap(); @@ -1580,7 +1580,7 @@ fn analyze_emits_guidance_orphan_and_invalidates_summary_cache_on_deletion() { } /// T4a (WS6): a guidance sheet whose `match_rules` carries `{"type":"entity","id":X}` -/// pointing at a deleted entity also produces `CLA-FACT-GUIDANCE-ORPHAN`. When the +/// pointing at a deleted entity also produces `LMWV-FACT-GUIDANCE-ORPHAN`. When the /// SAME deleted target is reachable via BOTH a `guides` edge and a `match_rule`, only /// one finding is emitted for that (sheet, target) pair (idempotent run-scoped id). #[cfg(unix)] @@ -1590,7 +1590,7 @@ fn analyze_emits_guidance_orphan_for_match_rule_entity_and_dedupes() { phase3_project_for_rerun(&["auth_a", "auth_b", "billing_a", "billing_b"]); let plugin_path = std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap(); - let db_path = project_dir.path().join(".clarion/clarion.db"); + let db_path = project_dir.path().join(".loomweave/loomweave.db"); let target = "phase3fixture:module:billing_a"; { @@ -1638,7 +1638,7 @@ fn analyze_emits_guidance_orphan_for_match_rule_entity_and_dedupes() { let match_count: i64 = conn .query_row( "SELECT COUNT(*) FROM findings \ - WHERE rule_id = 'CLA-FACT-GUIDANCE-ORPHAN' AND entity_id = 'core:guidance:g_match'", + WHERE rule_id = 'LMWV-FACT-GUIDANCE-ORPHAN' AND entity_id = 'core:guidance:g_match'", [], |row| row.get(0), ) @@ -1649,7 +1649,7 @@ fn analyze_emits_guidance_orphan_for_match_rule_entity_and_dedupes() { let both_count: i64 = conn .query_row( "SELECT COUNT(*) FROM findings \ - WHERE rule_id = 'CLA-FACT-GUIDANCE-ORPHAN' AND entity_id = 'core:guidance:g_both'", + WHERE rule_id = 'LMWV-FACT-GUIDANCE-ORPHAN' AND entity_id = 'core:guidance:g_both'", [], |row| row.get(0), ) @@ -1660,7 +1660,7 @@ fn analyze_emits_guidance_orphan_for_match_rule_entity_and_dedupes() { ); } -/// T4a (WS6): `CLA-FACT-GUIDANCE-EXPIRED` fires for a sheet whose `expires` is in +/// T4a (WS6): `LMWV-FACT-GUIDANCE-EXPIRED` fires for a sheet whose `expires` is in /// the past, and does NOT fire for a future `expires` or a sheet with no `expires`. /// Runs on every analyze (independent of deletions), so this re-runs with no source /// change. Severity INFO, confidence 1.0. @@ -1670,7 +1670,7 @@ fn analyze_emits_guidance_expired_for_past_expiry_only() { let (project_dir, plugin_dir, config_path) = phase3_project_for_rerun(&["auth_a", "auth_b"]); let plugin_path = std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap(); - let db_path = project_dir.path().join(".clarion/clarion.db"); + let db_path = project_dir.path().join(".loomweave/loomweave.db"); { let conn = Connection::open(&db_path).unwrap(); @@ -1701,7 +1701,7 @@ fn analyze_emits_guidance_expired_for_past_expiry_only() { let conn = Connection::open(&db_path).unwrap(); let anchors: Vec = conn - .prepare("SELECT entity_id FROM findings WHERE rule_id = 'CLA-FACT-GUIDANCE-EXPIRED'") + .prepare("SELECT entity_id FROM findings WHERE rule_id = 'LMWV-FACT-GUIDANCE-EXPIRED'") .unwrap() .query_map([], |row| row.get(0)) .unwrap() @@ -1712,7 +1712,7 @@ fn analyze_emits_guidance_expired_for_past_expiry_only() { let (severity, confidence): (String, f64) = conn .query_row( "SELECT severity, confidence FROM findings \ - WHERE rule_id = 'CLA-FACT-GUIDANCE-EXPIRED'", + WHERE rule_id = 'LMWV-FACT-GUIDANCE-EXPIRED'", [], |row| Ok((row.get(0)?, row.get(1)?)), ) @@ -1730,7 +1730,7 @@ fn analyze_emits_guidance_expired_under_no_sei() { let (project_dir, plugin_dir, config_path) = phase3_project_for_rerun(&["auth_a", "auth_b"]); let plugin_path = std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap(); - let db_path = project_dir.path().join(".clarion/clarion.db"); + let db_path = project_dir.path().join(".loomweave/loomweave.db"); { let conn = Connection::open(&db_path).unwrap(); @@ -1748,7 +1748,7 @@ fn analyze_emits_guidance_expired_under_no_sei() { .unwrap(); } - clarion_bin() + loomweave_bin() .args(["analyze", "--config"]) .arg(std::path::Path::new(&config_path)) .arg("--no-sei") @@ -1760,7 +1760,7 @@ fn analyze_emits_guidance_expired_under_no_sei() { let conn = Connection::open(&db_path).unwrap(); let count: i64 = conn .query_row( - "SELECT COUNT(*) FROM findings WHERE rule_id = 'CLA-FACT-GUIDANCE-EXPIRED'", + "SELECT COUNT(*) FROM findings WHERE rule_id = 'LMWV-FACT-GUIDANCE-EXPIRED'", [], |row| row.get(0), ) @@ -1768,7 +1768,7 @@ fn analyze_emits_guidance_expired_under_no_sei() { assert_eq!(count, 1, "EXPIRED must fire under --no-sei"); } -/// T4a (WS6): `CLA-FACT-GUIDANCE-CHURN-STALE` asymmetric threshold. A pinned sheet +/// T4a (WS6): `LMWV-FACT-GUIDANCE-CHURN-STALE` asymmetric threshold. A pinned sheet /// matching entities whose aggregate `git_churn_count` is in [20, 49] fires; an /// identical non-pinned sheet at the same churn does not. Below 20 neither fires; /// at/above 50 both fire. With churn unpopulated (production), nothing fires. @@ -1778,7 +1778,7 @@ fn analyze_emits_guidance_churn_stale_with_asymmetric_pinned_threshold() { let (project_dir, plugin_dir, config_path) = phase3_project_for_rerun(&["auth_a", "auth_b"]); let plugin_path = std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap(); - let db_path = project_dir.path().join(".clarion/clarion.db"); + let db_path = project_dir.path().join(".loomweave/loomweave.db"); // Seed git_churn_count on the matched module via properties JSON (the analyze // pipeline does not populate it). A `kind:module` match_rule selects both @@ -1818,7 +1818,7 @@ fn analyze_emits_guidance_churn_stale_with_asymmetric_pinned_threshold() { let fired: i64 = conn .query_row( "SELECT COUNT(*) FROM findings \ - WHERE rule_id = 'CLA-FACT-GUIDANCE-CHURN-STALE' AND entity_id = ?1", + WHERE rule_id = 'LMWV-FACT-GUIDANCE-CHURN-STALE' AND entity_id = ?1", rusqlite::params![format!("core:guidance:{slug}")], |row| row.get(0), ) @@ -1848,7 +1848,7 @@ fn analyze_emits_guidance_churn_stale_with_asymmetric_pinned_threshold() { let (severity, confidence): (String, f64) = conn .query_row( "SELECT severity, confidence FROM findings \ - WHERE rule_id = 'CLA-FACT-GUIDANCE-CHURN-STALE' LIMIT 1", + WHERE rule_id = 'LMWV-FACT-GUIDANCE-CHURN-STALE' LIMIT 1", [], |row| Ok((row.get(0)?, row.get(1)?)), ) @@ -1866,7 +1866,7 @@ fn analyze_guidance_churn_stale_is_honest_empty_without_churn() { let (project_dir, plugin_dir, config_path) = phase3_project_for_rerun(&["auth_a", "auth_b"]); let plugin_path = std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap(); - let db_path = project_dir.path().join(".clarion/clarion.db"); + let db_path = project_dir.path().join(".loomweave/loomweave.db"); { let conn = Connection::open(&db_path).unwrap(); @@ -1895,7 +1895,7 @@ fn analyze_guidance_churn_stale_is_honest_empty_without_churn() { let conn = Connection::open(&db_path).unwrap(); let count: i64 = conn .query_row( - "SELECT COUNT(*) FROM findings WHERE rule_id = 'CLA-FACT-GUIDANCE-CHURN-STALE'", + "SELECT COUNT(*) FROM findings WHERE rule_id = 'LMWV-FACT-GUIDANCE-CHURN-STALE'", [], |row| row.get(0), ) @@ -2030,7 +2030,7 @@ fn analyze_generates_pinned_wardline_derived_guidance() { phase3_project_for_rerun(&["auth_a", "auth_b", "billing_a"]); let plugin_path = std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap(); - let db_path = project_dir.path().join(".clarion/clarion.db"); + let db_path = project_dir.path().join(".loomweave/loomweave.db"); write_wardline_manifest(project_dir.path(), "Keep integral code isolated."); run_phase3_analyze( @@ -2094,7 +2094,7 @@ fn analyze_accepts_real_wardline_output_bundle() { let (project_dir, plugin_dir, config_path) = phase3_project_for_rerun(&["seed"]); let plugin_path = std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap(); - let db_path = project_dir.path().join(".clarion/clarion.db"); + let db_path = project_dir.path().join(".loomweave/loomweave.db"); write_real_wardline_output_fixture(project_dir.path()); run_phase3_analyze( @@ -2200,7 +2200,7 @@ fn analyze_preserves_wardline_override_and_emits_guidance_stale() { phase3_project_for_rerun(&["auth_a", "auth_b", "billing_a"]); let plugin_path = std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap(); - let db_path = project_dir.path().join(".clarion/clarion.db"); + let db_path = project_dir.path().join(".loomweave/loomweave.db"); write_wardline_manifest(project_dir.path(), "Initial Wardline guidance."); run_phase3_analyze( project_dir.path(), @@ -2248,7 +2248,7 @@ fn analyze_preserves_wardline_override_and_emits_guidance_stale() { let (severity, confidence, evidence): (String, f64, String) = conn .query_row( "SELECT severity, confidence, evidence FROM findings \ - WHERE rule_id = 'CLA-FACT-GUIDANCE-STALE' \ + WHERE rule_id = 'LMWV-FACT-GUIDANCE-STALE' \ AND entity_id = 'core:guidance:wardline-tier-integral'", [], |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)), @@ -2300,8 +2300,8 @@ fn findings_by_rule(conn: &Connection, rule_id: &str) -> Vec<(String, String, St } /// REQ-ANALYZE-05 verification (verbatim): a fixture with mixed Wardline tiers in -/// a subsystem produces `CLA-FACT-TIER-SUBSYSTEM-MIXING`; a uniform-tier subsystem -/// produces `CLA-FACT-SUBSYSTEM-TIER-UNANIMOUS`. Tier facts are seeded between +/// a subsystem produces `LMWV-FACT-TIER-SUBSYSTEM-MIXING`; a uniform-tier subsystem +/// produces `LMWV-FACT-SUBSYSTEM-TIER-UNANIMOUS`. Tier facts are seeded between /// runs (analyze never writes them — the enrich-only axiom), so run 1 builds the /// subsystems and run 2 emits the findings against the seeded tiers. #[cfg(unix)] @@ -2311,7 +2311,7 @@ fn analyze_emits_tier_mixing_and_unanimous_findings() { phase3_project_for_rerun(&["auth_a", "auth_b", "billing_a", "billing_b"]); let plugin_path = std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap(); - let db_path = project_dir.path().join(".clarion/clarion.db"); + let db_path = project_dir.path().join(".loomweave/loomweave.db"); { let conn = Connection::open(&db_path).unwrap(); @@ -2330,7 +2330,7 @@ fn analyze_emits_tier_mixing_and_unanimous_findings() { ); let conn = Connection::open(&db_path).unwrap(); - let mixing = findings_by_rule(&conn, "CLA-FACT-TIER-SUBSYSTEM-MIXING"); + let mixing = findings_by_rule(&conn, "LMWV-FACT-TIER-SUBSYSTEM-MIXING"); assert_eq!(mixing.len(), 1, "exactly the auth subsystem mixes tiers"); let related: serde_json::Value = serde_json::from_str(&mixing[0].1).unwrap(); assert_eq!( @@ -2341,7 +2341,7 @@ fn analyze_emits_tier_mixing_and_unanimous_findings() { assert_eq!(evidence["tier_distribution"]["public"], 1); assert_eq!(evidence["tier_distribution"]["internal"], 1); - let unanimous = findings_by_rule(&conn, "CLA-FACT-SUBSYSTEM-TIER-UNANIMOUS"); + let unanimous = findings_by_rule(&conn, "LMWV-FACT-SUBSYSTEM-TIER-UNANIMOUS"); assert_eq!( unanimous.len(), 1, @@ -2371,7 +2371,7 @@ fn analyze_resolves_function_tier_through_contains_chain_to_subsystem() { let (project_dir, plugin_dir, config_path) = phase3_project_for_rerun(&["auth_a", "auth_b"]); let plugin_path = std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap(); - let db_path = project_dir.path().join(".clarion/clarion.db"); + let db_path = project_dir.path().join(".loomweave/loomweave.db"); let func = "phase3fixture:function:auth_a.handler"; { @@ -2405,7 +2405,7 @@ fn analyze_resolves_function_tier_through_contains_chain_to_subsystem() { ); let conn = Connection::open(&db_path).unwrap(); - let unanimous = findings_by_rule(&conn, "CLA-FACT-SUBSYSTEM-TIER-UNANIMOUS"); + let unanimous = findings_by_rule(&conn, "LMWV-FACT-SUBSYSTEM-TIER-UNANIMOUS"); assert_eq!( unanimous.len(), 1, @@ -2432,11 +2432,11 @@ analysis: weak_modularity_threshold: 0.0 ", ); - let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap(); let finding_count: i64 = conn .query_row( "SELECT COUNT(*) FROM findings \ - WHERE rule_id = 'CLA-FACT-CLUSTERING-WEAK-MODULARITY'", + WHERE rule_id = 'LMWV-FACT-CLUSTERING-WEAK-MODULARITY'", [], |row| row.get(0), ) @@ -2461,11 +2461,11 @@ fn analyze_phase3_does_not_emit_weak_modularity_fact_when_threshold_is_met() { &["auth_a", "auth_b", "billing_a", "billing_b"], &phase3_config(2), ); - let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap(); let finding_count: i64 = conn .query_row( "SELECT COUNT(*) FROM findings \ - WHERE rule_id = 'CLA-FACT-CLUSTERING-WEAK-MODULARITY'", + WHERE rule_id = 'LMWV-FACT-CLUSTERING-WEAK-MODULARITY'", [], |row| row.get(0), ) @@ -2492,7 +2492,7 @@ fn analyze_phase3_min_cluster_size_drops_undersized_weighted_components() { &["auth_a", "auth_b", "billing_a", "billing_b"], &phase3_weighted_components_config(3), ); - let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap(); let subsystem_count: i64 = conn .query_row( "SELECT COUNT(*) FROM entities WHERE kind = 'subsystem'", @@ -2529,7 +2529,7 @@ fn analyze_phase3_persists_weighted_components_algorithm_when_selected() { &["auth_a", "auth_b", "billing_a", "billing_b"], &phase3_weighted_components_config(2), ); - let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap(); let properties_json: String = conn .query_row( "SELECT properties FROM entities \ @@ -2553,16 +2553,16 @@ fn analyze_phase3_persists_weighted_components_algorithm_when_selected() { } #[test] -fn analyze_fails_cleanly_if_clarion_dir_missing() { +fn analyze_fails_cleanly_if_loomweave_dir_missing() { let dir = tempfile::tempdir().unwrap(); - let out = clarion_bin() + let out = loomweave_bin() .args(["analyze"]) .arg(dir.path()) .assert() .failure(); let stderr = String::from_utf8(out.get_output().stderr.clone()).unwrap(); assert!( - stderr.contains("clarion install"), + stderr.contains("loomweave install"), "error did not point operator at install: {stderr}" ); } @@ -2574,7 +2574,7 @@ fn analyze_stats_reports_ambiguous_edges_total() { let plugin_dir = tempfile::tempdir().unwrap(); write_ambiguous_calls_plugin(plugin_dir.path()); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(project_dir.path()) .assert() @@ -2584,14 +2584,14 @@ fn analyze_stats_reports_ambiguous_edges_total() { let plugin_path = std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap(); - clarion_bin() + loomweave_bin() .args(["analyze"]) .arg(project_dir.path()) .env("PATH", &plugin_path) .assert() .success(); - let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap(); let stats_raw: String = conn .query_row("SELECT stats FROM runs LIMIT 1", [], |row| row.get(0)) .expect("query runs.stats"); @@ -2671,7 +2671,7 @@ fn analyze_mints_core_file_entity_for_registry_resolution() { let plugin_dir = tempfile::tempdir().unwrap(); write_ambiguous_calls_plugin(plugin_dir.path()); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(project_dir.path()) .assert() @@ -2681,15 +2681,15 @@ fn analyze_mints_core_file_entity_for_registry_resolution() { let plugin_path = std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap(); - clarion_bin() + loomweave_bin() .args(["analyze"]) .arg(project_dir.path()) .env("PATH", &plugin_path) .assert() .success(); - let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap(); - let resolved = clarion_storage::resolve_file(&conn, project_dir.path(), "demo.call", "") + let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap(); + let resolved = loomweave_storage::resolve_file(&conn, project_dir.path(), "demo.call", "") .expect("resolve_file should not error") .expect("analyzed ordinary source file should resolve as a core file entity"); @@ -2712,7 +2712,7 @@ fn analyze_filters_external_import_edges_before_writer_insert() { let plugin_dir = tempfile::tempdir().unwrap(); write_imports_plugin(plugin_dir.path()); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(project_dir.path()) .assert() @@ -2727,14 +2727,14 @@ fn analyze_filters_external_import_edges_before_writer_insert() { let plugin_path = std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap(); - clarion_bin() + loomweave_bin() .args(["analyze"]) .arg(project_dir.path()) .env("PATH", &plugin_path) .assert() .success(); - let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap(); let import_edges: Vec<(String, String)> = conn .prepare("SELECT from_id, to_id FROM edges WHERE kind = 'imports' ORDER BY from_id, to_id") .unwrap() @@ -2762,10 +2762,10 @@ fn analyze_filters_external_import_edges_before_writer_insert() { } /// Regression for wp2 review-2 (clarion-f56dc6ee43): `FailRun` must exit -/// non-zero so `clarion analyze && next` chains and CI gating work. +/// non-zero so `loomweave analyze && next` chains and CI gating work. /// /// Triggers the discovery-errors `FailRun` branch by placing a -/// `clarion-plugin-*` executable on `$PATH` next to a malformed +/// `loomweave-plugin-*` executable on `$PATH` next to a malformed /// `plugin.toml`. Before the fix, this exited 0; after, it exits non-zero /// AND the `runs.status` column still reads `failed` (the run row is /// marked before the bail). @@ -2777,16 +2777,16 @@ fn analyze_failrun_exits_nonzero_with_run_row_marked_failed() { let project_dir = tempfile::tempdir().unwrap(); let plugin_dir = tempfile::tempdir().unwrap(); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(project_dir.path()) .assert() .success(); - // Put a `clarion-plugin-broken` on the synthetic PATH alongside a + // Put a `loomweave-plugin-broken` on the synthetic PATH alongside a // malformed plugin.toml. Discovery will try to parse the toml and // collect the error; with no compliant plugins, FailRun fires. - let plugin_bin = plugin_dir.path().join("clarion-plugin-broken"); + let plugin_bin = plugin_dir.path().join("loomweave-plugin-broken"); symlink("/bin/true", &plugin_bin).expect("symlink /bin/true"); std::fs::write( plugin_dir.path().join("plugin.toml"), @@ -2796,8 +2796,8 @@ fn analyze_failrun_exits_nonzero_with_run_row_marked_failed() { // Scrub the ambient PATH — build the child's PATH from ONLY the // broken-plugin dir. If we inherited the parent's PATH, a real - // `clarion-plugin-*` binary installed on the developer's machine - // (e.g. `clarion-plugin-python` under ~/.local/bin) would be + // `loomweave-plugin-*` binary installed on the developer's machine + // (e.g. `loomweave-plugin-python` under ~/.local/bin) would be // discovered, the run would complete cleanly, and this FailRun test // would fail with "Unexpected success". The sibling tests // (`analyze_resume_*`, `analyze_prune_unseen_*`) build their PATH the @@ -2805,7 +2805,7 @@ fn analyze_failrun_exits_nonzero_with_run_row_marked_failed() { let new_path = std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).expect("join_paths"); - let out = clarion_bin() + let out = loomweave_bin() .args(["analyze"]) .arg(project_dir.path()) .env("PATH", &new_path) @@ -2820,7 +2820,7 @@ fn analyze_failrun_exits_nonzero_with_run_row_marked_failed() { // The run row must still be marked `failed` — the FailRun WriterCmd // runs before the bail, so the DB state is consistent with the exit // code. - let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap(); let status: String = conn .query_row( "SELECT status FROM runs ORDER BY started_at DESC LIMIT 1", @@ -2862,8 +2862,8 @@ integrations: /// WP9-B: emission is best-effort. With Filigree enabled but unreachable, the /// analyze run must still complete (exit 0, run row `completed`) and record the -/// failure in `stats.json` as `CLA-INFRA-FILIGREE-UNREACHABLE` — the enrich-only -/// federation contract: a sibling being down never changes Clarion's outcome. +/// failure in `stats.json` as `LMWV-INFRA-FILIGREE-UNREACHABLE` — the enrich-only +/// federation contract: a sibling being down never changes Loomweave's outcome. #[cfg(unix)] #[test] fn analyze_finding_emission_is_best_effort_when_filigree_unreachable() { @@ -2875,7 +2875,7 @@ fn analyze_finding_emission_is_best_effort_when_filigree_unreachable() { // run_phase3_fixture already asserted the analyze invocation `.success()`; // confirm the run row landed `completed` despite the emission failure. - let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap(); let status: String = conn .query_row( "SELECT status FROM runs ORDER BY started_at DESC LIMIT 1", @@ -2897,7 +2897,7 @@ fn analyze_finding_emission_is_best_effort_when_filigree_unreachable() { ); assert_eq!( emission["rule_id"].as_str(), - Some("CLA-INFRA-FILIGREE-UNREACHABLE"), + Some("LMWV-INFRA-FILIGREE-UNREACHABLE"), ); assert!( emission["endpoint"] @@ -2954,7 +2954,7 @@ fn analyze_finding_emission_posts_and_records_emitted_on_success() { "analyze POSTed to the scan-results route: {request}", ); assert!( - request.contains("\"scan_source\":\"clarion\""), + request.contains("\"scan_source\":\"loomweave\""), "request body carries scan_source: {request}", ); @@ -3009,7 +3009,7 @@ fn analyze_resume_reuses_run_row_and_emits_mark_unseen_false() { let project_dir = tempfile::tempdir().unwrap(); let plugin_dir = tempfile::tempdir().unwrap(); write_phase3_plugin(plugin_dir.path()); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(project_dir.path()) .assert() @@ -3018,7 +3018,7 @@ fn analyze_resume_reuses_run_row_and_emits_mark_unseen_false() { std::fs::write(project_dir.path().join(format!("{stem}.p3")), b"module\n") .expect("write phase3 fixture file"); } - let config_path = project_dir.path().join("phase3-clarion.yaml"); + let config_path = project_dir.path().join("phase3-loomweave.yaml"); std::fs::write( &config_path, phase3_config_with_filigree(2, &format!("http://{addr}")), @@ -3027,7 +3027,7 @@ fn analyze_resume_reuses_run_row_and_emits_mark_unseen_false() { let plugin_path = std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap(); - clarion_bin() + loomweave_bin() .args(["analyze", "--config"]) .arg(&config_path) .arg(project_dir.path()) @@ -3037,7 +3037,7 @@ fn analyze_resume_reuses_run_row_and_emits_mark_unseen_false() { // Capture the fresh run's id, then resume it (POST 2). let run_id: String = { - let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap(); conn.query_row( "SELECT id FROM runs ORDER BY started_at DESC LIMIT 1", [], @@ -3045,7 +3045,7 @@ fn analyze_resume_reuses_run_row_and_emits_mark_unseen_false() { ) .expect("read fresh run id") }; - clarion_bin() + loomweave_bin() .args(["analyze", "--config"]) .arg(&config_path) .args(["--resume", &run_id]) @@ -3067,7 +3067,7 @@ fn analyze_resume_reuses_run_row_and_emits_mark_unseen_false() { ); // Resume reused the run row — exactly one row in `runs`, finalized. - let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap(); let run_rows: i64 = conn .query_row("SELECT COUNT(*) FROM runs", [], |row| row.get(0)) .unwrap(); @@ -3093,7 +3093,7 @@ fn analyze_resume_reuses_run_row_and_emits_mark_unseen_false() { } /// REQ-FINDING-06 `--prune-unseen`: after emission, analyze POSTs a retention -/// sweep to Filigree's loom `clean-stale` route, scoped to `scan_source=clarion`, +/// sweep to Filigree's weft `clean-stale` route, scoped to `scan_source=loomweave`, /// and records the soft-archive count in `stats.json`. End-to-end through a mock /// Filigree that accepts both the emission POST and the prune POST. #[cfg(unix)] @@ -3107,7 +3107,7 @@ fn analyze_prune_unseen_posts_clean_stale_after_emission() { // One body satisfies both parsers (serde(default) ignores the other's // fields): scan-results counts + clean-stale counts. let server = std::thread::spawn(move || { - let body = r#"{"files_created":0,"files_updated":0,"findings_created":0,"findings_updated":0,"new_finding_ids":[],"observations_created":0,"observations_failed":0,"warnings":[],"findings_fixed":2,"scan_source":"clarion","older_than_days":30}"#; + let body = r#"{"files_created":0,"files_updated":0,"findings_created":0,"findings_updated":0,"new_finding_ids":[],"observations_created":0,"observations_failed":0,"warnings":[],"findings_fixed":2,"scan_source":"loomweave","older_than_days":30}"#; let mut requests = Vec::new(); for _ in 0..2 { let (mut stream, _) = listener.accept().expect("accept POST"); @@ -3128,7 +3128,7 @@ fn analyze_prune_unseen_posts_clean_stale_after_emission() { let project_dir = tempfile::tempdir().unwrap(); let plugin_dir = tempfile::tempdir().unwrap(); write_phase3_plugin(plugin_dir.path()); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(project_dir.path()) .assert() @@ -3137,7 +3137,7 @@ fn analyze_prune_unseen_posts_clean_stale_after_emission() { std::fs::write(project_dir.path().join(format!("{stem}.p3")), b"module\n") .expect("write phase3 fixture file"); } - let config_path = project_dir.path().join("phase3-clarion.yaml"); + let config_path = project_dir.path().join("phase3-loomweave.yaml"); std::fs::write( &config_path, phase3_config_with_filigree(2, &format!("http://{addr}")), @@ -3146,7 +3146,7 @@ fn analyze_prune_unseen_posts_clean_stale_after_emission() { let plugin_path = std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap(); - clarion_bin() + loomweave_bin() .args(["analyze", "--config"]) .arg(&config_path) .arg("--prune-unseen") @@ -3162,13 +3162,13 @@ fn analyze_prune_unseen_posts_clean_stale_after_emission() { requests[0], ); assert!( - requests[1].contains("POST /api/loom/findings/clean-stale HTTP/1.1"), - "second POST is the loom clean-stale sweep: {}", + requests[1].contains("POST /api/weft/findings/clean-stale HTTP/1.1"), + "second POST is the weft clean-stale sweep: {}", requests[1], ); assert!( - requests[1].contains("\"scan_source\":\"clarion\""), - "prune is scoped to scan_source=clarion: {}", + requests[1].contains("\"scan_source\":\"loomweave\""), + "prune is scoped to scan_source=loomweave: {}", requests[1], ); // Guard the wire field name: the live Filigree clean-stale route silently @@ -3196,7 +3196,7 @@ fn analyze_prune_unseen_posts_clean_stale_after_emission() { /// REQ-FINDING-06 `--prune-unseen` is enrich-only: with Filigree unreachable the /// analyze run still completes and the sweep failure is recorded in `stats.json` -/// as `CLA-INFRA-FILIGREE-UNREACHABLE` — never failing the run. +/// as `LMWV-INFRA-FILIGREE-UNREACHABLE` — never failing the run. #[cfg(unix)] #[test] fn analyze_prune_unseen_is_best_effort_when_filigree_unreachable() { @@ -3210,9 +3210,9 @@ fn analyze_prune_unseen_is_best_effort_when_filigree_unreachable() { // (port 1), so both emission and prune fail soft. let plugin = tempfile::tempdir().unwrap(); write_phase3_plugin(plugin.path()); - let config_path = project_dir.path().join("phase3-clarion.yaml"); + let config_path = project_dir.path().join("phase3-loomweave.yaml"); let plugin_path = std::env::join_paths(std::iter::once(plugin.path().to_path_buf())).unwrap(); - clarion_bin() + loomweave_bin() .args(["analyze", "--config"]) .arg(&config_path) .arg("--prune-unseen") @@ -3221,7 +3221,7 @@ fn analyze_prune_unseen_is_best_effort_when_filigree_unreachable() { .assert() .success(); - let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap(); let run_status: String = conn .query_row( "SELECT status FROM runs ORDER BY started_at DESC LIMIT 1", @@ -3241,7 +3241,7 @@ fn analyze_prune_unseen_is_best_effort_when_filigree_unreachable() { ); assert_eq!( stats["filigree_prune"]["rule_id"].as_str(), - Some("CLA-INFRA-FILIGREE-UNREACHABLE"), + Some("LMWV-INFRA-FILIGREE-UNREACHABLE"), ); } @@ -3288,7 +3288,7 @@ fn analyze_does_not_emit_when_emit_findings_false() { /// REQ-FINDING-06 `--prune-unseen` is enrich-only against a non-2xx response, /// not just connection refusal: when Filigree answers the clean-stale POST with /// HTTP 500, analyze still exits 0 with the run row `completed`, and the sweep -/// failure is recorded in `stats.json` as `CLA-INFRA-FILIGREE-UNREACHABLE`. The +/// failure is recorded in `stats.json` as `LMWV-INFRA-FILIGREE-UNREACHABLE`. The /// 500 is well-formed (content-length present) so it exercises the client's /// `!status.is_success()` branch rather than a torn-connection error. #[cfg(unix)] @@ -3331,7 +3331,7 @@ fn analyze_prune_unseen_is_best_effort_on_non_2xx() { let project_dir = tempfile::tempdir().unwrap(); let plugin_dir = tempfile::tempdir().unwrap(); write_phase3_plugin(plugin_dir.path()); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(project_dir.path()) .assert() @@ -3340,7 +3340,7 @@ fn analyze_prune_unseen_is_best_effort_on_non_2xx() { std::fs::write(project_dir.path().join(format!("{stem}.p3")), b"module\n") .expect("write phase3 fixture file"); } - let config_path = project_dir.path().join("phase3-clarion.yaml"); + let config_path = project_dir.path().join("phase3-loomweave.yaml"); std::fs::write( &config_path, phase3_config_with_filigree(2, &format!("http://{addr}")), @@ -3349,7 +3349,7 @@ fn analyze_prune_unseen_is_best_effort_on_non_2xx() { let plugin_path = std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap(); - clarion_bin() + loomweave_bin() .args(["analyze", "--config"]) .arg(&config_path) .arg("--prune-unseen") @@ -3361,7 +3361,7 @@ fn analyze_prune_unseen_is_best_effort_on_non_2xx() { server.join().expect("mock server thread"); // A non-2xx clean-stale response must never fail the run. - let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap(); let run_status: String = conn .query_row( "SELECT status FROM runs ORDER BY started_at DESC LIMIT 1", @@ -3381,7 +3381,7 @@ fn analyze_prune_unseen_is_best_effort_on_non_2xx() { ); assert_eq!( stats["filigree_prune"]["rule_id"].as_str(), - Some("CLA-INFRA-FILIGREE-UNREACHABLE"), + Some("LMWV-INFRA-FILIGREE-UNREACHABLE"), ); } @@ -3395,7 +3395,7 @@ fn analyze_prune_unseen_noops_when_filigree_disabled() { let project_dir = tempfile::tempdir().unwrap(); let plugin_dir = tempfile::tempdir().unwrap(); write_phase3_plugin(plugin_dir.path()); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(project_dir.path()) .assert() @@ -3404,12 +3404,12 @@ fn analyze_prune_unseen_noops_when_filigree_disabled() { std::fs::write(project_dir.path().join(format!("{stem}.p3")), b"module\n") .expect("write phase3 fixture file"); } - let config_path = project_dir.path().join("phase3-clarion.yaml"); + let config_path = project_dir.path().join("phase3-loomweave.yaml"); std::fs::write(&config_path, phase3_config(2)).expect("write phase3 config"); let plugin_path = std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap(); - clarion_bin() + loomweave_bin() .args(["analyze", "--config"]) .arg(&config_path) .arg("--prune-unseen") @@ -3430,7 +3430,7 @@ fn analyze_prune_unseen_noops_when_filigree_disabled() { ); } -/// Wave 0 / WS3 (plan T1.4): after a successful `clarion analyze`, the +/// Wave 0 / WS3 (plan T1.4): after a successful `loomweave analyze`, the /// `sei_prior_index` snapshot must equal EXACTLY that run's entity set — stale /// rows from the prior run removed. Two back-to-back runs on the same project /// where the second drops a file prove the full-snapshot replace: the dropped @@ -3443,7 +3443,7 @@ fn analyze_rewrites_prior_index_to_current_run_entity_set() { use std::collections::BTreeSet; fn prior_index_locators(project_root: &std::path::Path) -> BTreeSet { - let conn = Connection::open(project_root.join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(project_root.join(".loomweave/loomweave.db")).unwrap(); conn.prepare("SELECT locator FROM sei_prior_index") .unwrap() .query_map([], |row| row.get::<_, String>(0)) @@ -3456,7 +3456,7 @@ fn analyze_rewrites_prior_index_to_current_run_entity_set() { let plugin_dir = tempfile::tempdir().unwrap(); write_phase3_plugin(plugin_dir.path()); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(project_dir.path()) .assert() @@ -3467,7 +3467,7 @@ fn analyze_rewrites_prior_index_to_current_run_entity_set() { let plugin_path = std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap(); let analyze = |dir: &std::path::Path| { - clarion_bin() + loomweave_bin() .args(["analyze"]) .arg(dir) .env("PATH", &plugin_path) @@ -3508,7 +3508,7 @@ fn analyze_rewrites_prior_index_to_current_run_entity_set() { // Column contract: body_hash populated (NOT NULL), recorded_at stamped, and // signature still NULL in Wave 0 (the WS1 matcher fills it later). - let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap(); let (body_hash, recorded_at, signature): (String, String, Option) = conn .query_row( "SELECT body_hash, recorded_at, signature FROM sei_prior_index WHERE locator = ?1", @@ -3528,7 +3528,7 @@ fn analyze_rewrites_prior_index_to_current_run_entity_set() { fn alive_sei_bindings( project_root: &std::path::Path, ) -> std::collections::BTreeMap { - let conn = Connection::open(project_root.join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(project_root.join(".loomweave/loomweave.db")).unwrap(); conn.prepare( "SELECT current_locator, sei FROM sei_bindings \ WHERE status = 'alive' AND current_locator IS NOT NULL", @@ -3543,7 +3543,7 @@ fn alive_sei_bindings( } fn all_entity_ids(project_root: &std::path::Path) -> std::collections::BTreeSet { - let conn = Connection::open(project_root.join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(project_root.join(".loomweave/loomweave.db")).unwrap(); conn.prepare("SELECT id FROM entities") .unwrap() .query_map([], |row| row.get::<_, String>(0)) @@ -3556,11 +3556,11 @@ fn all_entity_ids(project_root: &std::path::Path) -> std::collections::BTreeSet< #[cfg_attr(not(unix), ignore = "fixture plugin script is a unix shebang")] fn analyze_mints_alive_sei_binding_for_every_entity() { // DoD: every alive entity has an alive `sei_bindings` row after analysis, - // and every SEI carries the reserved `clarion:eid:` prefix (ADR-038). + // and every SEI carries the reserved `loomweave:eid:` prefix (ADR-038). let project_dir = tempfile::tempdir().unwrap(); let plugin_dir = tempfile::tempdir().unwrap(); write_phase3_plugin(plugin_dir.path()); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(project_dir.path()) .assert() @@ -3570,7 +3570,7 @@ fn analyze_mints_alive_sei_binding_for_every_entity() { std::fs::write(project_dir.path().join("sei_alpha.p3"), b"module\n").unwrap(); std::fs::write(project_dir.path().join("sei_beta.p3"), b"module\n").unwrap(); - clarion_bin() + loomweave_bin() .args(["analyze"]) .arg(project_dir.path()) .env("PATH", &plugin_path) @@ -3592,7 +3592,7 @@ fn analyze_mints_alive_sei_binding_for_every_entity() { assert!(!bindings.is_empty(), "expected at least one minted SEI"); for (locator, sei) in &bindings { assert!( - sei.starts_with("clarion:eid:"), + sei.starts_with("loomweave:eid:"), "SEI for {locator} must carry the reserved prefix: {sei}" ); } @@ -3607,7 +3607,7 @@ fn analyze_carries_sei_on_unchanged_rerun() { let project_dir = tempfile::tempdir().unwrap(); let plugin_dir = tempfile::tempdir().unwrap(); write_phase3_plugin(plugin_dir.path()); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(project_dir.path()) .assert() @@ -3615,7 +3615,7 @@ fn analyze_carries_sei_on_unchanged_rerun() { let plugin_path = std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap(); let analyze = || { - clarion_bin() + loomweave_bin() .args(["analyze"]) .arg(project_dir.path()) .env("PATH", &plugin_path) @@ -3644,7 +3644,7 @@ fn analyze_no_sei_flag_skips_minting() { let project_dir = tempfile::tempdir().unwrap(); let plugin_dir = tempfile::tempdir().unwrap(); write_phase3_plugin(plugin_dir.path()); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(project_dir.path()) .assert() @@ -3653,7 +3653,7 @@ fn analyze_no_sei_flag_skips_minting() { std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap(); std::fs::write(project_dir.path().join("sei_delta.p3"), b"module\n").unwrap(); - clarion_bin() + loomweave_bin() .args(["analyze", "--no-sei"]) .arg(project_dir.path()) .env("PATH", &plugin_path) @@ -3678,7 +3678,7 @@ fn analyze_orphans_deleted_entity_bindings_through_the_real_pipeline() { let project_dir = tempfile::tempdir().unwrap(); let plugin_dir = tempfile::tempdir().unwrap(); write_phase3_plugin(plugin_dir.path()); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(project_dir.path()) .assert() @@ -3686,7 +3686,7 @@ fn analyze_orphans_deleted_entity_bindings_through_the_real_pipeline() { let plugin_path = std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap(); let analyze = || { - clarion_bin() + loomweave_bin() .args(["analyze"]) .arg(project_dir.path()) .env("PATH", &plugin_path) @@ -3709,7 +3709,7 @@ fn analyze_orphans_deleted_entity_bindings_through_the_real_pipeline() { std::fs::remove_file(project_dir.path().join("sei_drop.p3")).unwrap(); analyze(); - let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap(); // The dropped entity's binding is now orphaned (by SEI — its row persists). let dropped_status: String = conn .query_row( @@ -3748,15 +3748,15 @@ fn analyze_orphans_deleted_entity_bindings_through_the_real_pipeline() { // ── Wave 2 / T3.1: incremental analysis (skip unchanged files) + orphan guard ── -/// Install Clarion + the phase3 fixture plugin into a fresh project. Returns the +/// Install Loomweave + the phase3 fixture plugin into a fresh project. Returns the /// project dir, the plugin dir (kept alive so the script stays on disk), and the -/// `PATH` value that exposes the plugin to `clarion analyze`. +/// `PATH` value that exposes the plugin to `loomweave analyze`. #[cfg(unix)] fn phase3_env() -> (tempfile::TempDir, tempfile::TempDir, std::ffi::OsString) { let project_dir = tempfile::tempdir().unwrap(); let plugin_dir = tempfile::tempdir().unwrap(); write_phase3_plugin(plugin_dir.path()); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(project_dir.path()) .assert() @@ -3809,14 +3809,14 @@ fn analyze_stamps_entities_with_git_head_commit() { run_git(project_dir.path(), &["commit", "-qm", "initial"]); let head = git_stdout(project_dir.path(), &["rev-parse", "HEAD"]); - clarion_bin() + loomweave_bin() .args(["analyze"]) .arg(project_dir.path()) .env("PATH", &analyze_path) .assert() .success(); - let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap(); for entity_id in ["core:file:demo.p3", "phase3fixture:module:demo"] { let (first_seen, last_seen): (Option, Option) = conn .query_row( @@ -3848,7 +3848,7 @@ fn analyze_incremental_skip_does_not_orphan_unchanged_file_entities() { // entities, every entity in every unchanged file would be falsely orphaned. let (project_dir, _plugin_dir, plugin_path) = phase3_env(); let analyze = || { - clarion_bin() + loomweave_bin() .args(["analyze"]) .arg(project_dir.path()) .env("PATH", &plugin_path) @@ -3903,7 +3903,7 @@ fn analyze_incremental_skip_does_not_orphan_unchanged_file_entities() { ); // And the binding's status is literally alive (belt-and-braces: alive_sei_bindings // already filters status='alive', but assert no orphaned lineage was recorded). - let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap(); let orphaned_for_stable: i64 = conn .query_row( "SELECT COUNT(*) FROM sei_lineage WHERE sei = ?1 AND event = 'orphaned'", @@ -3926,7 +3926,7 @@ fn analyze_incremental_repeated_unchanged_runs_keep_skipping() { // each skip BOTH files after the first. let (project_dir, _plugin_dir, plugin_path) = phase3_env(); let analyze = || { - clarion_bin() + loomweave_bin() .args(["analyze"]) .arg(project_dir.path()) .env("PATH", &plugin_path) @@ -3968,13 +3968,13 @@ fn analyze_no_incremental_forces_full_reanalysis() { std::fs::write(project_dir.path().join("full_a.p3"), b"module\n").unwrap(); std::fs::write(project_dir.path().join("full_b.p3"), b"module\n").unwrap(); - clarion_bin() + loomweave_bin() .args(["analyze"]) .arg(project_dir.path()) .env("PATH", &plugin_path) .assert() .success(); - clarion_bin() + loomweave_bin() .args(["analyze", "--no-incremental"]) .arg(project_dir.path()) .env("PATH", &plugin_path) @@ -4039,7 +4039,7 @@ while True: "jsonrpc": "2.0", "id": ident, "result": { - "name": "clarion-plugin-syn", + "name": "loomweave-plugin-syn", "version": "0.1.0", "ontology_version": "0.6.0", "capabilities": {}, @@ -4069,11 +4069,11 @@ while True: #[cfg(unix)] const SYNTAX_ERROR_PLUGIN_MANIFEST: &str = r#" [plugin] -name = "clarion-plugin-syn" +name = "loomweave-plugin-syn" plugin_id = "synfixture" version = "0.1.0" protocol_version = "1.0" -executable = "clarion-plugin-syn" +executable = "loomweave-plugin-syn" language = "synfixture" extensions = ["syn"] @@ -4086,7 +4086,7 @@ reads_outside_project_root = false [ontology] entity_kinds = ["module"] edge_kinds = [] -rule_id_prefix = "CLA-SYN-" +rule_id_prefix = "LMWV-SYN-" ontology_version = "0.6.0" [ontology.roles] @@ -4098,7 +4098,7 @@ syntax_degraded_module = ["module"] fn write_syntax_error_plugin(plugin_dir: &std::path::Path) { use std::os::unix::fs::PermissionsExt; - let plugin_script = plugin_dir.join("clarion-plugin-syn"); + let plugin_script = plugin_dir.join("loomweave-plugin-syn"); std::fs::write(&plugin_script, SYNTAX_ERROR_PLUGIN_SCRIPT).expect("write syn plugin script"); let mut perms = std::fs::metadata(&plugin_script) .expect("stat syn plugin") @@ -4111,7 +4111,7 @@ fn write_syntax_error_plugin(plugin_dir: &std::path::Path) { } /// REQ-ANALYZE-06 verification (in part): a file that fails to parse produces a -/// `CLA-PY-SYNTAX-ERROR` finding **persisted to the store**, anchored to the +/// `LMWV-PY-SYNTAX-ERROR` finding **persisted to the store**, anchored to the /// degraded module entity — not merely logged. A cleanly-parsed file produces /// no such finding. #[cfg(unix)] @@ -4121,7 +4121,7 @@ fn analyze_persists_syntax_error_finding_for_unparseable_file() { let plugin_dir = tempfile::tempdir().unwrap(); write_syntax_error_plugin(plugin_dir.path()); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(project_dir.path()) .assert() @@ -4131,25 +4131,25 @@ fn analyze_persists_syntax_error_finding_for_unparseable_file() { let plugin_path = std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap(); - clarion_bin() + loomweave_bin() .args(["analyze"]) .arg(project_dir.path()) .env("PATH", &plugin_path) .assert() .success(); - let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap(); let (count, anchor): (i64, String) = conn .query_row( "SELECT COUNT(*), COALESCE(MIN(entity_id), '') FROM findings \ - WHERE rule_id = 'CLA-PY-SYNTAX-ERROR'", + WHERE rule_id = 'LMWV-PY-SYNTAX-ERROR'", [], |row| Ok((row.get(0)?, row.get(1)?)), ) .expect("query syntax-error findings"); assert_eq!( count, 1, - "exactly one CLA-PY-SYNTAX-ERROR finding persisted" + "exactly one LMWV-PY-SYNTAX-ERROR finding persisted" ); assert_eq!( anchor, "synfixture:module:broken_mod", @@ -4207,7 +4207,7 @@ while True: "jsonrpc": "2.0", "id": ident, "result": { - "name": "clarion-plugin-crash", + "name": "loomweave-plugin-crash", "version": "0.1.0", "ontology_version": "0.6.0", "capabilities": {}, @@ -4225,11 +4225,11 @@ while True: #[cfg(unix)] const CRASHING_PLUGIN_MANIFEST: &str = r#" [plugin] -name = "clarion-plugin-crash" +name = "loomweave-plugin-crash" plugin_id = "crashfixture" version = "0.1.0" protocol_version = "1.0" -executable = "clarion-plugin-crash" +executable = "loomweave-plugin-crash" language = "crashfixture" extensions = ["crx"] @@ -4242,7 +4242,7 @@ reads_outside_project_root = false [ontology] entity_kinds = ["module"] edge_kinds = [] -rule_id_prefix = "CLA-CRASH-" +rule_id_prefix = "LMWV-CRASH-" ontology_version = "0.6.0" [ontology.roles] @@ -4253,7 +4253,7 @@ file_scope = ["module"] fn write_crashing_plugin(plugin_dir: &std::path::Path) { use std::os::unix::fs::PermissionsExt; - let plugin_script = plugin_dir.join("clarion-plugin-crash"); + let plugin_script = plugin_dir.join("loomweave-plugin-crash"); std::fs::write(&plugin_script, CRASHING_PLUGIN_SCRIPT).expect("write crash plugin script"); let mut perms = std::fs::metadata(&plugin_script) .expect("stat crash plugin") @@ -4266,7 +4266,7 @@ fn write_crashing_plugin(plugin_dir: &std::path::Path) { } /// REQ-ANALYZE-06 verification (in part): a plugin that crashes mid-run produces -/// a `CLA-INFRA-PLUGIN-CRASH` finding **persisted to the store**, anchored to the +/// a `LMWV-INFRA-PLUGIN-CRASH` finding **persisted to the store**, anchored to the /// synthetic `core:project:{name}` entity — not merely logged. #[cfg(unix)] #[test] @@ -4275,7 +4275,7 @@ fn analyze_persists_crash_finding_anchored_to_project() { let plugin_dir = tempfile::tempdir().unwrap(); write_crashing_plugin(plugin_dir.path()); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(project_dir.path()) .assert() @@ -4286,23 +4286,23 @@ fn analyze_persists_crash_finding_anchored_to_project() { std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap(); // A crashed plugin yields a non-zero exit (SoftFailed → CommitRun(Failed)); // the persisted finding is what we assert on. - clarion_bin() + loomweave_bin() .args(["analyze"]) .arg(project_dir.path()) .env("PATH", &plugin_path) .assert() .failure(); - let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap(); let (count, anchor): (i64, String) = conn .query_row( "SELECT COUNT(*), COALESCE(MIN(entity_id), '') FROM findings \ - WHERE rule_id = 'CLA-INFRA-PLUGIN-CRASH'", + WHERE rule_id = 'LMWV-INFRA-PLUGIN-CRASH'", [], |row| Ok((row.get(0)?, row.get(1)?)), ) .expect("query crash findings"); - assert!(count >= 1, "a CLA-INFRA-PLUGIN-CRASH finding is persisted"); + assert!(count >= 1, "a LMWV-INFRA-PLUGIN-CRASH finding is persisted"); let project_name = project_dir .path() .file_name() @@ -4380,7 +4380,7 @@ while True: "jsonrpc": "2.0", "id": ident, "result": { - "name": "clarion-plugin-hang", + "name": "loomweave-plugin-hang", "version": "0.1.0", "ontology_version": "0.6.0", "capabilities": {}, @@ -4398,11 +4398,11 @@ while True: #[cfg(unix)] const HANGING_PLUGIN_MANIFEST: &str = r#" [plugin] -name = "clarion-plugin-hang" +name = "loomweave-plugin-hang" plugin_id = "hangfixture" version = "0.1.0" protocol_version = "1.0" -executable = "clarion-plugin-hang" +executable = "loomweave-plugin-hang" language = "hangfixture" extensions = ["hng"] @@ -4415,7 +4415,7 @@ reads_outside_project_root = false [ontology] entity_kinds = ["module"] edge_kinds = [] -rule_id_prefix = "CLA-HANG-" +rule_id_prefix = "LMWV-HANG-" ontology_version = "0.6.0" [ontology.roles] @@ -4426,7 +4426,7 @@ file_scope = ["module"] fn write_hanging_plugin(plugin_dir: &std::path::Path) { use std::os::unix::fs::PermissionsExt; - let plugin_script = plugin_dir.join("clarion-plugin-hang"); + let plugin_script = plugin_dir.join("loomweave-plugin-hang"); std::fs::write(&plugin_script, HANGING_PLUGIN_SCRIPT).expect("write hang plugin script"); let mut perms = std::fs::metadata(&plugin_script) .expect("stat hang plugin") @@ -4440,8 +4440,8 @@ fn write_hanging_plugin(plugin_dir: &std::path::Path) { /// REQ-ANALYZE-06 verification (in part): a plugin that hangs on a file is killed /// by the per-file analysis-timeout watchdog and produces a persisted -/// `CLA-PY-TIMEOUT` finding (and not a redundant `CLA-INFRA-PLUGIN-CRASH`). The -/// timeout is set low via `CLARION_PLUGIN_FILE_TIMEOUT_MS` on the spawned process. +/// `LMWV-PY-TIMEOUT` finding (and not a redundant `LMWV-INFRA-PLUGIN-CRASH`). The +/// timeout is set low via `LOOMWEAVE_PLUGIN_FILE_TIMEOUT_MS` on the spawned process. #[cfg(unix)] #[test] fn analyze_persists_timeout_finding_for_hanging_plugin() { @@ -4449,7 +4449,7 @@ fn analyze_persists_timeout_finding_for_hanging_plugin() { let plugin_dir = tempfile::tempdir().unwrap(); write_hanging_plugin(plugin_dir.path()); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(project_dir.path()) .assert() @@ -4458,24 +4458,24 @@ fn analyze_persists_timeout_finding_for_hanging_plugin() { let plugin_path = std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap(); - clarion_bin() + loomweave_bin() .args(["analyze"]) .arg(project_dir.path()) .env("PATH", &plugin_path) - .env("CLARION_PLUGIN_FILE_TIMEOUT_MS", "500") + .env("LOOMWEAVE_PLUGIN_FILE_TIMEOUT_MS", "500") .assert() .failure(); - let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap(); let (timeout_count, anchor): (i64, String) = conn .query_row( "SELECT COUNT(*), COALESCE(MIN(entity_id), '') FROM findings \ - WHERE rule_id = 'CLA-PY-TIMEOUT'", + WHERE rule_id = 'LMWV-PY-TIMEOUT'", [], |row| Ok((row.get(0)?, row.get(1)?)), ) .expect("query timeout findings"); - assert!(timeout_count >= 1, "a CLA-PY-TIMEOUT finding is persisted"); + assert!(timeout_count >= 1, "a LMWV-PY-TIMEOUT finding is persisted"); let project_name = project_dir .path() .file_name() @@ -4490,13 +4490,13 @@ fn analyze_persists_timeout_finding_for_hanging_plugin() { // The generic crash finding is suppressed when a timeout is the root cause. let crash_count: i64 = conn .query_row( - "SELECT COUNT(*) FROM findings WHERE rule_id = 'CLA-INFRA-PLUGIN-CRASH'", + "SELECT COUNT(*) FROM findings WHERE rule_id = 'LMWV-INFRA-PLUGIN-CRASH'", [], |row| row.get(0), ) .unwrap(); assert_eq!( crash_count, 0, - "no redundant CLA-INFRA-PLUGIN-CRASH when the cause is a timeout" + "no redundant LMWV-INFRA-PLUGIN-CRASH when the cause is a timeout" ); } diff --git a/crates/clarion-cli/tests/analyze_failure_modes.rs b/crates/loomweave-cli/tests/analyze_failure_modes.rs similarity index 92% rename from crates/clarion-cli/tests/analyze_failure_modes.rs rename to crates/loomweave-cli/tests/analyze_failure_modes.rs index 3d7229c2..50e09c5c 100644 --- a/crates/clarion-cli/tests/analyze_failure_modes.rs +++ b/crates/loomweave-cli/tests/analyze_failure_modes.rs @@ -1,7 +1,7 @@ -//! Failure-mode integration tests for `clarion analyze`. +//! Failure-mode integration tests for `loomweave analyze`. //! //! These exercise the run-outcome promotion logic in -//! `crates/clarion-cli/src/analyze.rs::run_with_options` — specifically the +//! `crates/loomweave-cli/src/analyze.rs::run_with_options` — specifically the //! branch where the writer-actor rejects an `InsertEdge` mid-run and //! `RunOutcome` must be set to `HardFailed` (→ `FailRun`) rather than //! `SoftFailed` (→ `CommitRun(Failed)` with the full stats blob). @@ -24,12 +24,12 @@ use std::os::unix::fs::PermissionsExt; use assert_cmd::Command; use rusqlite::Connection; -fn clarion_bin() -> Command { - let mut cmd = Command::cargo_bin("clarion").expect("clarion binary"); +fn loomweave_bin() -> Command { + let mut cmd = Command::cargo_bin("loomweave").expect("loomweave binary"); cmd.env( - "CLARION_CODEX_CONFIG", + "LOOMWEAVE_CODEX_CONFIG", std::env::temp_dir().join(format!( - "clarion-test-codex-config-{}.toml", + "loomweave-test-codex-config-{}.toml", std::process::id() )), ); @@ -39,7 +39,7 @@ fn clarion_bin() -> Command { /// Tiny Python fixture plugin that declares an edge kind which the writer /// does not know about (`bogus_edge`). The host accepts it (it appears in /// `[ontology].edge_kinds`); the writer's `enforce_edge_contract` rejects -/// it with `CLA-INFRA-EDGE-UNKNOWN-KIND` — a `StorageError::WriterProtocol` +/// it with `LMWV-INFRA-EDGE-UNKNOWN-KIND` — a `StorageError::WriterProtocol` /// surfaced from `Writer::send_wait` on the first `InsertEdge`. /// /// This is the most realistic deterministic trigger for a mid-run @@ -82,7 +82,7 @@ while True: "jsonrpc": "2.0", "id": ident, "result": { - "name": "clarion-plugin-bogus", + "name": "loomweave-plugin-bogus", "version": "0.1.0", "ontology_version": "0.6.0", "capabilities": {}, @@ -114,7 +114,7 @@ while True: # `bogus_edge` is declared in the manifest's ontology, so # `PluginHost::process_edges` accepts it; the writer's # `enforce_edge_contract` rejects it with - # `CLA-INFRA-EDGE-UNKNOWN-KIND`. That is the mid-run + # `LMWV-INFRA-EDGE-UNKNOWN-KIND`. That is the mid-run # writer-actor failure under test. "edges": [ { @@ -137,11 +137,11 @@ while True: const BOGUS_EDGE_PLUGIN_MANIFEST: &str = r#" [plugin] -name = "clarion-plugin-bogus" +name = "loomweave-plugin-bogus" plugin_id = "bogusfixture" version = "0.1.0" protocol_version = "1.0" -executable = "clarion-plugin-bogus" +executable = "loomweave-plugin-bogus" language = "bogusfixture" extensions = ["bog"] @@ -154,7 +154,7 @@ reads_outside_project_root = false [ontology] entity_kinds = ["module"] edge_kinds = ["bogus_edge"] -rule_id_prefix = "CLA-BOGUS-" +rule_id_prefix = "LMWV-BOGUS-" ontology_version = "0.6.0" [ontology.roles] @@ -206,7 +206,7 @@ while True: "jsonrpc": "2.0", "id": ident, "result": { - "name": "clarion-plugin-partial", + "name": "loomweave-plugin-partial", "version": "0.1.0", "ontology_version": "0.6.0", "capabilities": {}, @@ -243,11 +243,11 @@ while True: const PARTIAL_CRASH_PLUGIN_MANIFEST: &str = r#" [plugin] -name = "clarion-plugin-partial" +name = "loomweave-plugin-partial" plugin_id = "partialfixture" version = "0.1.0" protocol_version = "1.0" -executable = "clarion-plugin-partial" +executable = "loomweave-plugin-partial" language = "partialfixture" extensions = ["part"] @@ -260,7 +260,7 @@ reads_outside_project_root = false [ontology] entity_kinds = ["module"] edge_kinds = [] -rule_id_prefix = "CLA-PARTIAL-" +rule_id_prefix = "LMWV-PARTIAL-" ontology_version = "0.6.0" [ontology.roles] @@ -309,7 +309,7 @@ while True: "jsonrpc": "2.0", "id": ident, "result": { - "name": "clarion-plugin-cross-file", + "name": "loomweave-plugin-cross-file", "version": "0.1.0", "ontology_version": "0.6.0", "capabilities": {}, @@ -380,11 +380,11 @@ while True: const CROSS_FILE_EDGE_PLUGIN_MANIFEST: &str = r#" [plugin] -name = "clarion-plugin-cross-file" +name = "loomweave-plugin-cross-file" plugin_id = "crossfixture" version = "0.1.0" protocol_version = "1.0" -executable = "clarion-plugin-cross-file" +executable = "loomweave-plugin-cross-file" language = "crossfixture" extensions = ["cross"] @@ -397,7 +397,7 @@ reads_outside_project_root = false [ontology] entity_kinds = ["module", "function"] edge_kinds = ["calls"] -rule_id_prefix = "CLA-CROSS-" +rule_id_prefix = "LMWV-CROSS-" ontology_version = "0.6.0" [ontology.roles] @@ -406,7 +406,7 @@ callable = ["function"] "#; fn write_bogus_edge_plugin(plugin_dir: &std::path::Path) { - let plugin_script = plugin_dir.join("clarion-plugin-bogus"); + let plugin_script = plugin_dir.join("loomweave-plugin-bogus"); std::fs::write(&plugin_script, BOGUS_EDGE_PLUGIN_SCRIPT) .expect("write bogus edge plugin script"); let mut perms = std::fs::metadata(&plugin_script) @@ -420,7 +420,7 @@ fn write_bogus_edge_plugin(plugin_dir: &std::path::Path) { } fn write_partial_crash_plugin(plugin_dir: &std::path::Path) { - let plugin_script = plugin_dir.join("clarion-plugin-partial"); + let plugin_script = plugin_dir.join("loomweave-plugin-partial"); std::fs::write(&plugin_script, PARTIAL_CRASH_PLUGIN_SCRIPT) .expect("write partial crash plugin script"); let mut perms = std::fs::metadata(&plugin_script) @@ -437,7 +437,7 @@ fn write_partial_crash_plugin(plugin_dir: &std::path::Path) { } fn write_cross_file_edge_plugin(plugin_dir: &std::path::Path) { - let plugin_script = plugin_dir.join("clarion-plugin-cross-file"); + let plugin_script = plugin_dir.join("loomweave-plugin-cross-file"); std::fs::write(&plugin_script, CROSS_FILE_EDGE_PLUGIN_SCRIPT) .expect("write cross-file edge plugin script"); let mut perms = std::fs::metadata(&plugin_script) @@ -459,7 +459,7 @@ fn analyze_defers_cross_file_edges_until_target_entity_batch_arrives() { let plugin_dir = tempfile::tempdir().unwrap(); write_cross_file_edge_plugin(plugin_dir.path()); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(project_dir.path()) .env("PATH", "") @@ -472,14 +472,14 @@ fn analyze_defers_cross_file_edges_until_target_entity_batch_arrives() { let plugin_path = std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap(); - clarion_bin() + loomweave_bin() .args(["analyze"]) .arg(project_dir.path()) .env("PATH", &plugin_path) .assert() .success(); - let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap(); let run_status: String = conn .query_row( "SELECT status FROM runs ORDER BY started_at DESC LIMIT 1", @@ -513,7 +513,7 @@ fn analyze_defers_cross_file_edges_until_target_entity_batch_arrives() { /// /// 1. End with `status = 'failed'`. /// 2. Carry a `stats.failure_reason` naming the writer-actor failure -/// (`CLA-INFRA-EDGE-UNKNOWN-KIND` from `enforce_edge_contract`), not a +/// (`LMWV-INFRA-EDGE-UNKNOWN-KIND` from `enforce_edge_contract`), not a /// plugin-crash reason. /// 3. Have the minimal `FailRun` stats shape — no `entities_inserted` /// key, distinguishing this from the `SoftFailed` path which writes the @@ -530,7 +530,7 @@ fn analyze_promotes_run_to_hard_failed_when_writer_actor_fails_mid_run() { let plugin_dir = tempfile::tempdir().unwrap(); write_bogus_edge_plugin(plugin_dir.path()); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(project_dir.path()) .env("PATH", "") @@ -545,7 +545,7 @@ fn analyze_promotes_run_to_hard_failed_when_writer_actor_fails_mid_run() { // entities and edges first, changing the stats-shape discriminator. let plugin_path = std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap(); - let out = clarion_bin() + let out = loomweave_bin() .args(["analyze"]) .arg(project_dir.path()) .env("PATH", &plugin_path) @@ -557,7 +557,7 @@ fn analyze_promotes_run_to_hard_failed_when_writer_actor_fails_mid_run() { "stderr should mention failure; got: {stderr}" ); - let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap(); // (1) Run row marked failed. let (run_status, run_stats_raw): (String, String) = conn @@ -580,7 +580,7 @@ fn analyze_promotes_run_to_hard_failed_when_writer_actor_fails_mid_run() { .as_str() .expect("HardFailed stats must contain failure_reason"); assert!( - failure_reason.contains("CLA-INFRA-EDGE-UNKNOWN-KIND"), + failure_reason.contains("LMWV-INFRA-EDGE-UNKNOWN-KIND"), "failure_reason should cite the writer's edge-contract code; \ got: {failure_reason}" ); @@ -623,7 +623,7 @@ fn analyze_promotes_run_to_hard_failed_when_writer_actor_fails_mid_run() { let crash_loop_findings: i64 = conn .query_row( "SELECT COUNT(*) FROM findings \ - WHERE rule_id = 'CLA-INFRA-PLUGIN-DISABLED-CRASH-LOOP'", + WHERE rule_id = 'LMWV-INFRA-PLUGIN-DISABLED-CRASH-LOOP'", [], |row| row.get(0), ) @@ -641,7 +641,7 @@ fn analyze_persists_completed_file_batches_when_plugin_later_crashes() { let plugin_dir = tempfile::tempdir().unwrap(); write_partial_crash_plugin(plugin_dir.path()); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(project_dir.path()) .env("PATH", "") @@ -652,14 +652,14 @@ fn analyze_persists_completed_file_batches_when_plugin_later_crashes() { let plugin_path = std::env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).unwrap(); - clarion_bin() + loomweave_bin() .args(["analyze"]) .arg(project_dir.path()) .env("PATH", &plugin_path) .assert() .failure(); - let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap(); let (run_status, run_stats_raw): (String, String) = conn .query_row( "SELECT status, stats FROM runs ORDER BY started_at DESC LIMIT 1", diff --git a/crates/clarion-cli/tests/db.rs b/crates/loomweave-cli/tests/db.rs similarity index 82% rename from crates/clarion-cli/tests/db.rs rename to crates/loomweave-cli/tests/db.rs index f05858ab..34896081 100644 --- a/crates/clarion-cli/tests/db.rs +++ b/crates/loomweave-cli/tests/db.rs @@ -1,29 +1,29 @@ -//! `clarion db backup` integration tests (clarion-6d433b61ba / STO-04). +//! `loomweave db backup` integration tests (clarion-6d433b61ba / STO-04). use assert_cmd::Command; use rusqlite::{Connection, OpenFlags}; -fn clarion_bin() -> Command { - let mut cmd = Command::cargo_bin("clarion").expect("clarion binary"); +fn loomweave_bin() -> Command { + let mut cmd = Command::cargo_bin("loomweave").expect("loomweave binary"); cmd.env( - "CLARION_CODEX_CONFIG", + "LOOMWEAVE_CODEX_CONFIG", std::env::temp_dir().join(format!( - "clarion-test-codex-config-{}.toml", + "loomweave-test-codex-config-{}.toml", std::process::id() )), ); cmd } -/// Seed a real `.clarion/clarion.db` under `root` with one identifiable row, +/// Seed a real `.loomweave/loomweave.db` under `root` with one identifiable row, /// left in WAL mode (the state a live analyze leaves behind). fn seed_db(root: &std::path::Path) { - let clarion_dir = root.join(".clarion"); - std::fs::create_dir_all(&clarion_dir).expect("mkdir .clarion"); - let db_path = clarion_dir.join("clarion.db"); + let loomweave_dir = root.join(".loomweave"); + std::fs::create_dir_all(&loomweave_dir).expect("mkdir .loomweave"); + let db_path = loomweave_dir.join("loomweave.db"); let mut conn = Connection::open(&db_path).expect("open db"); - clarion_storage::pragma::apply_write_pragmas(&conn).expect("write pragmas"); - clarion_storage::schema::apply_migrations(&mut conn).expect("migrate"); + loomweave_storage::pragma::apply_write_pragmas(&conn).expect("write pragmas"); + loomweave_storage::schema::apply_migrations(&mut conn).expect("migrate"); conn.execute( "INSERT INTO runs (id, started_at, completed_at, config, stats, status) \ VALUES (?1, strftime('%Y-%m-%dT%H:%M:%fZ', 'now'), NULL, '{}', '{}', 'running')", @@ -40,7 +40,7 @@ fn backup_produces_a_readable_standalone_copy() { seed_db(dir.path()); let output = dir.path().join("snapshot.db"); - clarion_bin() + loomweave_bin() .args(["db", "backup"]) .arg(&output) .arg("--path") @@ -86,7 +86,7 @@ fn backup_refuses_to_clobber_without_force() { let output = dir.path().join("snapshot.db"); std::fs::write(&output, b"pre-existing precious file").unwrap(); - clarion_bin() + loomweave_bin() .args(["db", "backup"]) .arg(&output) .arg("--path") @@ -101,7 +101,7 @@ fn backup_refuses_to_clobber_without_force() { ); // --force replaces it with a real backup. - clarion_bin() + loomweave_bin() .args(["db", "backup"]) .arg(&output) .arg("--path") @@ -122,10 +122,10 @@ fn backup_refuses_to_clobber_without_force() { #[test] fn backup_rejects_missing_source_db() { let dir = tempfile::tempdir().unwrap(); - // No seed_db: .clarion/clarion.db does not exist. + // No seed_db: .loomweave/loomweave.db does not exist. let output = dir.path().join("snapshot.db"); - clarion_bin() + loomweave_bin() .args(["db", "backup"]) .arg(&output) .arg("--path") diff --git a/crates/loomweave-cli/tests/discovery_co_located.rs b/crates/loomweave-cli/tests/discovery_co_located.rs new file mode 100644 index 00000000..c8d8246c --- /dev/null +++ b/crates/loomweave-cli/tests/discovery_co_located.rs @@ -0,0 +1,91 @@ +//! Proves the production discovery path finds a plugin co-located in the same +//! directory as the `loomweave` binary even when that directory is NOT on $PATH — +//! the PyPI/venv install scenario (spec 2026-06-05-loomweave-pypi-distribution). +#![cfg(unix)] + +use std::fs; +use std::os::unix::fs::PermissionsExt; +use std::path::Path; + +#[test] +fn co_located_plugin_discovered_off_path() { + let tmp = tempfile::TempDir::new().unwrap(); + let bin = tmp.path().join("bin"); + fs::create_dir_all(&bin).unwrap(); + + // Copy the built loomweave binary into the staged bin/. + let staged = bin.join("loomweave"); + fs::copy(env!("CARGO_BIN_EXE_loomweave"), &staged).unwrap(); + set_exec(&staged); + + // Sibling plugin executable + install-prefix manifest. + let plugin_exe = bin.join("loomweave-plugin-mocktest"); + fs::write(&plugin_exe, b"#!/bin/sh\nexit 0\n").unwrap(); + set_exec(&plugin_exe); + let share = tmp.path().join("share/loomweave/plugins/mocktest"); + fs::create_dir_all(&share).unwrap(); + fs::write(share.join("plugin.toml"), MOCK_MANIFEST).unwrap(); + + // Run the staged binary with an EMPTY PATH so discovery can ONLY succeed via + // the current_exe() level. Use a project dir so doctor has somewhere to look. + let proj = tmp.path().join("proj"); + fs::create_dir_all(&proj).unwrap(); + let output = std::process::Command::new(&staged) + .args(["doctor", "--format", "json", "--path"]) + .arg(&proj) + .current_dir(&proj) + .env("PATH", "") + .env("HOME", tmp.path()) + .output() + .unwrap(); + let stdout = String::from_utf8_lossy(&output.stdout); + + // Parse the doctor JSON and assert the plugin.availability check is "ok" + // and names the discovered mock plugin. + let report: serde_json::Value = serde_json::from_str(&stdout) + .unwrap_or_else(|e| panic!("doctor --format json not parseable: {e}\nstdout:\n{stdout}")); + let checks = report["checks"].as_array().expect("checks array"); + let plugin_check = checks + .iter() + .find(|c| c["id"] == "plugin.availability") + .expect("plugin.availability check present"); + assert_eq!( + plugin_check["status"], "ok", + "co-located plugin should be discovered off-PATH; report:\n{stdout}" + ); + assert!( + plugin_check["message"] + .as_str() + .unwrap_or("") + .contains("mocktest"), + "plugin.availability message should name the discovered plugin; got: {plugin_check}" + ); +} + +fn set_exec(path: &Path) { + let mut perms = fs::metadata(path).unwrap().permissions(); + perms.set_mode(0o755); + fs::set_permissions(path, perms).unwrap(); +} + +const MOCK_MANIFEST: &str = r#"[plugin] +name = "loomweave-plugin-mocktest" +plugin_id = "mocktest" +version = "0.1.0" +protocol_version = "1.0" +executable = "loomweave-plugin-mocktest" +language = "mocktest" +extensions = ["mt"] + +[capabilities.runtime] +expected_max_rss_mb = 256 +expected_entities_per_file = 100 +wardline_aware = false +reads_outside_project_root = false + +[ontology] +entity_kinds = ["function"] +edge_kinds = ["calls"] +rule_id_prefix = "LMWV-MT-" +ontology_version = "0.1.0" +"#; diff --git a/crates/clarion-cli/tests/doctor.rs b/crates/loomweave-cli/tests/doctor.rs similarity index 87% rename from crates/clarion-cli/tests/doctor.rs rename to crates/loomweave-cli/tests/doctor.rs index 55f035da..4fff01d9 100644 --- a/crates/clarion-cli/tests/doctor.rs +++ b/crates/loomweave-cli/tests/doctor.rs @@ -1,4 +1,4 @@ -//! `clarion doctor [--fix]` integration tests. +//! `loomweave doctor [--fix]` integration tests. //! //! Exercises the exit-code contract (healthy -> 0, any problem -> 1) and the //! end-to-end `--fix` wiring across the three orientation surfaces. Per-surface @@ -10,12 +10,12 @@ use std::path::Path; use assert_cmd::Command; -fn clarion_bin() -> Command { - let mut cmd = Command::cargo_bin("clarion").expect("clarion binary"); +fn loomweave_bin() -> Command { + let mut cmd = Command::cargo_bin("loomweave").expect("loomweave binary"); cmd.env( - "CLARION_CODEX_CONFIG", + "LOOMWEAVE_CODEX_CONFIG", std::env::temp_dir().join(format!( - "clarion-test-codex-config-{}.toml", + "loomweave-test-codex-config-{}.toml", std::process::id() )), ); @@ -23,7 +23,7 @@ fn clarion_bin() -> Command { } fn install(args: &[&str], dir: &Path) { - clarion_bin() + loomweave_bin() .args(args) .arg("--path") .arg(dir) @@ -37,7 +37,7 @@ fn read_yaml(path: &Path) -> serde_json::Value { /// Run `doctor` (optionally with `--fix`) and return `(exit_code, stdout)`. fn doctor(dir: &Path, fix: bool) -> (i32, String) { - let mut cmd = clarion_bin(); + let mut cmd = loomweave_bin(); cmd.arg("doctor"); if fix { cmd.arg("--fix"); @@ -50,7 +50,7 @@ fn doctor(dir: &Path, fix: bool) -> (i32, String) { } fn doctor_json(dir: &Path, fix: bool) -> (i32, serde_json::Value) { - let mut cmd = clarion_bin(); + let mut cmd = loomweave_bin(); cmd.arg("doctor"); if fix { cmd.arg("--fix"); @@ -82,13 +82,13 @@ fn doctor_reports_plain_install_healthy() { assert!(out.contains("skill pack up to date"), "stdout:\n{out}"); assert!(out.contains("SessionStart hook present"), "stdout:\n{out}"); assert!( - out.contains(".mcp.json clarion serve entry present"), + out.contains(".mcp.json loomweave serve entry present"), "stdout:\n{out}" ); } /// `doctor --fix` registers the MCP entry; a subsequent plain `doctor` is then -/// fully healthy and exits 0. The `.mcp.json` gains a `clarion` serve entry. +/// fully healthy and exits 0. The `.mcp.json` gains a `loomweave` serve entry. #[test] fn doctor_fix_registers_mcp_then_reports_healthy() { let dir = tempfile::tempdir().unwrap(); @@ -108,13 +108,13 @@ fn doctor_fix_registers_mcp_then_reports_healthy() { let v: serde_json::Value = serde_json::from_str(&fs::read_to_string(dir.path().join(".mcp.json")).unwrap()).unwrap(); assert!( - v["mcpServers"]["clarion"]["command"] + v["mcpServers"]["loomweave"]["command"] .as_str() .unwrap() - .ends_with("clarion") + .ends_with("loomweave") ); assert_eq!( - v["mcpServers"]["clarion"]["args"], + v["mcpServers"]["loomweave"]["args"], serde_json::json!(["serve"]) ); @@ -124,7 +124,7 @@ fn doctor_fix_registers_mcp_then_reports_healthy() { } /// `doctor --fix` preserves a sibling MCP server (e.g. filigree) already in -/// `.mcp.json` while adding the clarion entry. +/// `.mcp.json` while adding the loomweave entry. #[test] fn doctor_fix_preserves_sibling_mcp_server() { let dir = tempfile::tempdir().unwrap(); @@ -148,10 +148,10 @@ fn doctor_fix_preserves_sibling_mcp_server() { "sibling server must be preserved" ); assert!( - v["mcpServers"]["clarion"]["command"] + v["mcpServers"]["loomweave"]["command"] .as_str() .unwrap() - .ends_with("clarion") + .ends_with("loomweave") ); } @@ -177,7 +177,7 @@ fn doctor_fix_repairs_missing_three_way_integration_bindings() { assert_eq!( code, 0, "missing enrich-only integration bindings must NOT fail the gate (federation axiom: \ - Wardline is enrich-only, a Clarion-solo/Filigree-only project is first-class):\n{out}" + Wardline is enrich-only, a Loomweave-solo/Filigree-only project is first-class):\n{out}" ); assert!( out.contains("⚠ three-way integration bindings missing or stale"), @@ -195,20 +195,20 @@ fn doctor_fix_repairs_missing_three_way_integration_bindings() { "stdout:\n{out}" ); - let clarion_yaml = read_yaml(&dir.path().join("clarion.yaml")); + let loomweave_yaml = read_yaml(&dir.path().join("loomweave.yaml")); assert_eq!( - clarion_yaml["integrations"]["filigree"]["base_url"], + loomweave_yaml["integrations"]["filigree"]["base_url"], "http://127.0.0.1:8749" ); assert_eq!( - clarion_yaml["serve"]["http"]["wardline_taint_write"], + loomweave_yaml["serve"]["http"]["wardline_taint_write"], serde_json::json!(true) ); let wardline_yaml = read_yaml(&dir.path().join("wardline.yaml")); assert_eq!( wardline_yaml["filigree"]["url"], - "http://127.0.0.1:8749/api/loom/scan-results" + "http://127.0.0.1:8749/api/weft/scan-results" ); let mcp: serde_json::Value = @@ -219,10 +219,10 @@ fn doctor_fix_repairs_missing_three_way_integration_bindings() { "mcp", "--root", ".", - "--clarion-url", + "--loomweave-url", "http://127.0.0.1:9111", "--filigree-url", - "http://127.0.0.1:8749/api/loom/scan-results" + "http://127.0.0.1:8749/api/weft/scan-results" ]) ); @@ -309,14 +309,14 @@ fn doctor_fix_json_reports_fixed_config_bindings() { #[test] fn doctor_reports_missing_hook_and_mcp_and_prints_index_block() { let dir = tempfile::tempdir().unwrap(); - // Skill flags install ONLY the skill packs (no .clarion/, no hook, no mcp). + // Skill flags install ONLY the skill packs (no .loomweave/, no hook, no mcp). install(&["install", "--skills", "--codex-skills"], dir.path()); let (code, out) = doctor(dir.path(), false); assert_eq!(code, 1, "stdout:\n{out}"); assert!(out.contains("SessionStart hook missing"), "stdout:\n{out}"); assert!( - out.contains(".mcp.json has no clarion serve entry"), + out.contains(".mcp.json has no loomweave serve entry"), "stdout:\n{out}" ); assert!( @@ -328,7 +328,7 @@ fn doctor_reports_missing_hook_and_mcp_and_prints_index_block() { assert!(out.contains("2 problems found"), "stdout:\n{out}"); } -/// A hostile checkout can ship a `.mcp.json` whose `clarion` entry names an +/// A hostile checkout can ship a `.mcp.json` whose `loomweave` entry names an /// attacker-controlled `command` that the MCP client would later launch. /// `doctor` must NOT report that as healthy (the false all-clear bug), but it /// also must not clobber a possibly-deliberate wrapper: it flags the entry @@ -342,7 +342,7 @@ fn doctor_flags_untrusted_mcp_command_without_clobbering_it() { fs::write( dir.path().join(".mcp.json"), format!( - r#"{{"mcpServers":{{"clarion":{{"type":"stdio","command":"./evil-mcp.sh","args":["serve","--path",{canon:?}],"env":{{}}}}}}}}"# + r#"{{"mcpServers":{{"loomweave":{{"type":"stdio","command":"./evil-mcp.sh","args":["serve","--path",{canon:?}],"env":{{}}}}}}}}"# ), ) .unwrap(); @@ -368,7 +368,7 @@ fn doctor_flags_untrusted_mcp_command_without_clobbering_it() { let v: serde_json::Value = serde_json::from_str(&fs::read_to_string(dir.path().join(".mcp.json")).unwrap()).unwrap(); assert_eq!( - v["mcpServers"]["clarion"]["command"], "./evil-mcp.sh", + v["mcpServers"]["loomweave"]["command"], "./evil-mcp.sh", "doctor --fix must not clobber a custom command" ); diff --git a/crates/clarion-cli/tests/guidance.rs b/crates/loomweave-cli/tests/guidance.rs similarity index 93% rename from crates/clarion-cli/tests/guidance.rs rename to crates/loomweave-cli/tests/guidance.rs index 98c69757..aa27a044 100644 --- a/crates/clarion-cli/tests/guidance.rs +++ b/crates/loomweave-cli/tests/guidance.rs @@ -1,6 +1,6 @@ -//! `clarion guidance` authoring CLI integration tests (WS6 / REQ-GUIDANCE-03). +//! `loomweave guidance` authoring CLI integration tests (WS6 / REQ-GUIDANCE-03). //! -//! Drives the real binary end-to-end against a seeded `.clarion/clarion.db`: +//! Drives the real binary end-to-end against a seeded `.loomweave/loomweave.db`: //! create (via `--content`), show, list (incl. `--for-entity`), edit (via a //! fake `$EDITOR`), and delete. Verifies the written `properties` JSON matches //! the shape the MCP read path consumes. @@ -11,30 +11,30 @@ use serde_json::Value; use std::io::{Read, Write}; use std::net::{SocketAddr, TcpListener}; -fn clarion_bin() -> Command { - let mut cmd = Command::cargo_bin("clarion").expect("clarion binary"); +fn loomweave_bin() -> Command { + let mut cmd = Command::cargo_bin("loomweave").expect("loomweave binary"); cmd.env( - "CLARION_CODEX_CONFIG", + "LOOMWEAVE_CODEX_CONFIG", std::env::temp_dir().join(format!( - "clarion-test-codex-config-{}.toml", + "loomweave-test-codex-config-{}.toml", std::process::id() )), ); cmd } -/// Seed a real `.clarion/clarion.db` with the schema and one code entity (so +/// Seed a real `.loomweave/loomweave.db` with the schema and one code entity (so /// `--for-entity` has a target to match). fn seed_db(root: &std::path::Path) { - let clarion_dir = root.join(".clarion"); - std::fs::create_dir_all(&clarion_dir).expect("mkdir .clarion"); - let db_path = clarion_dir.join("clarion.db"); + let loomweave_dir = root.join(".loomweave"); + std::fs::create_dir_all(&loomweave_dir).expect("mkdir .loomweave"); + let db_path = loomweave_dir.join("loomweave.db"); let mut conn = Connection::open(&db_path).expect("open db"); - clarion_storage::pragma::apply_write_pragmas(&conn).expect("write pragmas"); - clarion_storage::schema::apply_migrations(&mut conn).expect("migrate"); + loomweave_storage::pragma::apply_write_pragmas(&conn).expect("write pragmas"); + loomweave_storage::schema::apply_migrations(&mut conn).expect("migrate"); // A function entity under src/auth/ so path + kind rules can match it. // `analyze` stores `source_file_path` as a *canonicalized* absolute path - // (clarion_storage::query::normalize_source_path canonicalizes both root and + // (loomweave_storage::query::normalize_source_path canonicalizes both root and // file), and `serve` / the CLI canonicalize project_root the same way. The // file must exist on disk for canonicalize to resolve symlinks (e.g. macOS // /tmp → /private/tmp), so create it before seeding — this makes the seeded @@ -59,7 +59,7 @@ fn seed_db(root: &std::path::Path) { } fn properties(root: &std::path::Path, id: &str) -> Value { - let db_path = root.join(".clarion").join("clarion.db"); + let db_path = root.join(".loomweave").join("loomweave.db"); let conn = Connection::open(&db_path).expect("reopen db"); let raw: String = conn .query_row( @@ -76,7 +76,7 @@ fn properties(root: &std::path::Path, id: &str) -> Value { /// for the `--expired` / `--stale` filter tests). Bypasses the CLI `create` path /// deliberately — these tests exercise `list`, not authoring. fn seed_sheet(root: &std::path::Path, slug: &str, properties: &Value) { - let db_path = root.join(".clarion").join("clarion.db"); + let db_path = root.join(".loomweave").join("loomweave.db"); let conn = Connection::open(&db_path).expect("open db for seed_sheet"); let id = format!("core:guidance:{slug}"); conn.execute( @@ -91,7 +91,7 @@ fn seed_sheet(root: &std::path::Path, slug: &str, properties: &Value) { /// Run `guidance list` with the given extra args and return stdout. fn list_stdout(root: &std::path::Path, extra: &[&str]) -> String { - let assert = clarion_bin() + let assert = loomweave_bin() .args(["guidance", "list"]) .args(["--path"]) .arg(root) @@ -110,18 +110,18 @@ fn spawn_observations_server(detail: String) -> (SocketAddr, std::thread::JoinHa let read = stream.read(&mut buf).expect("read observations request"); let request = String::from_utf8_lossy(&buf[..read]); assert!( - request.contains("GET /api/loom/observations?limit=100&offset=0 HTTP/1.1"), + request.contains("GET /api/weft/observations?limit=100&offset=0 HTTP/1.1"), "unexpected request: {request}" ); let body = serde_json::json!({ "items": [{ - "observation_id": "clarion-obs-guidance", - "summary": "Clarion guidance proposal for python:function:auth.tokens.refresh", + "observation_id": "loomweave-obs-guidance", + "summary": "Loomweave guidance proposal for python:function:auth.tokens.refresh", "detail": detail, "file_path": "src/auth/tokens.py", "line": 1, "priority": 2, - "actor": "clarion" + "actor": "loomweave" }], "limit": 100, "offset": 0, @@ -281,7 +281,7 @@ fn create_show_list_delete_lifecycle() { seed_db(dir.path()); // create with explicit content + two match rules. - clarion_bin() + loomweave_bin() .args(["guidance", "create"]) .args(["--path"]) .arg(dir.path()) @@ -312,7 +312,7 @@ fn create_show_list_delete_lifecycle() { ); // show prints the id + content. - let show = clarion_bin() + let show = loomweave_bin() .args(["guidance", "show", id]) .args(["--path"]) .arg(dir.path()) @@ -326,7 +326,7 @@ fn create_show_list_delete_lifecycle() { ); // list (no filter) shows the sheet. - let list = clarion_bin() + let list = loomweave_bin() .args(["guidance", "list"]) .args(["--path"]) .arg(dir.path()) @@ -338,7 +338,7 @@ fn create_show_list_delete_lifecycle() { ); // list --for-entity matches via path/kind rule. - let filtered = clarion_bin() + let filtered = loomweave_bin() .args(["guidance", "list"]) .args(["--path"]) .arg(dir.path()) @@ -351,7 +351,7 @@ fn create_show_list_delete_lifecycle() { ); // delete removes it. - clarion_bin() + loomweave_bin() .args(["guidance", "delete", id]) .args(["--path"]) .arg(dir.path()) @@ -359,7 +359,7 @@ fn create_show_list_delete_lifecycle() { .success(); // show now fails (not found). - clarion_bin() + loomweave_bin() .args(["guidance", "show", id]) .args(["--path"]) .arg(dir.path()) @@ -371,7 +371,7 @@ fn create_show_list_delete_lifecycle() { fn promote_observation_creates_guidance_sheet() { let dir = tempfile::tempdir().unwrap(); seed_db(dir.path()); - let proposal = clarion_storage::GuidanceProposal { + let proposal = loomweave_storage::GuidanceProposal { entity_id: "python:function:auth.tokens.refresh".to_owned(), content: "Escalate auth-token risk in summaries.".to_owned(), scope_level: "function".to_owned(), @@ -386,25 +386,27 @@ fn promote_observation_creates_guidance_sheet() { let detail = proposal.to_observation_detail().unwrap(); let (addr, handle) = spawn_observations_server(detail); std::fs::write( - dir.path().join("clarion.yaml"), + dir.path().join("loomweave.yaml"), format!( - "integrations:\n filigree:\n enabled: true\n base_url: http://{addr}\n actor: clarion-test\n" + "integrations:\n filigree:\n enabled: true\n base_url: http://{addr}\n actor: loomweave-test\n" ), ) .unwrap(); - let promoted = clarion_bin() - .args(["guidance", "promote", "clarion-obs-guidance"]) + let promoted = loomweave_bin() + .args(["guidance", "promote", "loomweave-obs-guidance"]) .args(["--path"]) .arg(dir.path()) // Dismissal is best-effort and uses the Filigree MCP subprocess; point it // at a fast no-op so this test only owns the observation-read contract. - .env("CLARION_FILIGREE_MCP_COMMAND", "/bin/true") + .env("LOOMWEAVE_FILIGREE_MCP_COMMAND", "/bin/true") .assert() .success(); let out = String::from_utf8_lossy(&promoted.get_output().stdout); assert!( - out.contains("Promoted observation clarion-obs-guidance to core:guidance:auth-token-risk"), + out.contains( + "Promoted observation loomweave-obs-guidance to core:guidance:auth-token-risk" + ), "unexpected promote output: {out}" ); handle.join().expect("observations server"); @@ -429,7 +431,7 @@ fn list_for_entity_matches_via_path_rule_only() { let dir = tempfile::tempdir().unwrap(); seed_db(dir.path()); - clarion_bin() + loomweave_bin() .args(["guidance", "create"]) .args(["--path"]) .arg(dir.path()) @@ -440,7 +442,7 @@ fn list_for_entity_matches_via_path_rule_only() { .assert() .success(); - let matched = clarion_bin() + let matched = loomweave_bin() .args(["guidance", "list"]) .args(["--path"]) .arg(dir.path()) @@ -453,7 +455,7 @@ fn list_for_entity_matches_via_path_rule_only() { ); // A non-matching path must NOT list for this entity. - clarion_bin() + loomweave_bin() .args(["guidance", "create"]) .args(["--path"]) .arg(dir.path()) @@ -463,7 +465,7 @@ fn list_for_entity_matches_via_path_rule_only() { .args(["--content", "billing guidance"]) .assert() .success(); - let filtered = clarion_bin() + let filtered = loomweave_bin() .args(["guidance", "list"]) .args(["--path"]) .arg(dir.path()) @@ -486,7 +488,7 @@ fn create_rejects_duplicate_id() { let dir = tempfile::tempdir().unwrap(); seed_db(dir.path()); let make = || { - clarion_bin() + loomweave_bin() .args(["guidance", "create"]) .args(["--path"]) .arg(dir.path()) @@ -504,7 +506,7 @@ fn create_rejects_duplicate_id() { fn create_rejects_bad_scope_level_and_bad_match() { let dir = tempfile::tempdir().unwrap(); seed_db(dir.path()); - clarion_bin() + loomweave_bin() .args(["guidance", "create"]) .args(["--path"]) .arg(dir.path()) @@ -514,7 +516,7 @@ fn create_rejects_bad_scope_level_and_bad_match() { .assert() .failure(); - clarion_bin() + loomweave_bin() .args(["guidance", "create"]) .args(["--path"]) .arg(dir.path()) @@ -532,7 +534,7 @@ fn create_normalizes_and_validates_expires() { // A bare date is accepted and normalized to start-of-day UTC in the same // 24-char `…Z` shape the read path's lexical expiry compare expects. - clarion_bin() + loomweave_bin() .args(["guidance", "create"]) .args(["--path"]) .arg(dir.path()) @@ -553,7 +555,7 @@ fn create_normalizes_and_validates_expires() { // Proxy the read path: a future expiry must NOT be lexically < now, i.e. the // sheet is not treated as already expired. - let db_path = dir.path().join(".clarion").join("clarion.db"); + let db_path = dir.path().join(".loomweave").join("loomweave.db"); let conn = Connection::open(&db_path).unwrap(); let now: String = conn .query_row("SELECT strftime('%Y-%m-%dT%H:%M:%fZ','now')", [], |r| { @@ -563,7 +565,7 @@ fn create_normalizes_and_validates_expires() { assert!(stored > now.as_str(), "future expiry must sort after now"); // Garbage `--expires` is rejected up front (no sheet written). - clarion_bin() + loomweave_bin() .args(["guidance", "create"]) .args(["--path"]) .arg(dir.path()) @@ -582,7 +584,7 @@ fn edit_preserves_authored_at_and_provenance_changes_only_content() { seed_db(dir.path()); let id = "core:guidance:edit-me"; - clarion_bin() + loomweave_bin() .args(["guidance", "create"]) .args(["--path"]) .arg(dir.path()) @@ -608,7 +610,7 @@ fn edit_preserves_authored_at_and_provenance_changes_only_content() { std::fs::set_permissions(&editor, perms).unwrap(); } - clarion_bin() + loomweave_bin() .args(["guidance", "edit", id]) .args(["--path"]) .arg(dir.path()) @@ -639,7 +641,7 @@ fn edit_ignores_repo_dotenv_visual_and_uses_inherited_editor() { seed_db(dir.path()); let id = "core:guidance:dotenv-editor"; - clarion_bin() + loomweave_bin() .args(["guidance", "create"]) .args(["--path"]) .arg(dir.path()) @@ -681,7 +683,7 @@ fn edit_ignores_repo_dotenv_visual_and_uses_inherited_editor() { ) .unwrap(); - let bin = assert_cmd::cargo::cargo_bin("clarion"); + let bin = assert_cmd::cargo::cargo_bin("loomweave"); let path = std::env::var("PATH").unwrap_or_default(); let out = std::process::Command::new(&bin) .current_dir(dir.path()) @@ -692,7 +694,7 @@ fn edit_ignores_repo_dotenv_visual_and_uses_inherited_editor() { .args(["--path"]) .arg(dir.path()) .output() - .expect("clarion guidance edit"); + .expect("loomweave guidance edit"); let stdout = String::from_utf8_lossy(&out.stdout); let stderr = String::from_utf8_lossy(&out.stderr); @@ -711,7 +713,7 @@ fn edit_ignores_repo_dotenv_visual_and_uses_inherited_editor() { fn edit_without_editor_set_fails_cleanly() { let dir = tempfile::tempdir().unwrap(); seed_db(dir.path()); - clarion_bin() + loomweave_bin() .args(["guidance", "create"]) .args(["--path"]) .arg(dir.path()) @@ -722,7 +724,7 @@ fn edit_without_editor_set_fails_cleanly() { .assert() .success(); - clarion_bin() + loomweave_bin() .args(["guidance", "edit", "core:guidance:noeditor"]) .args(["--path"]) .arg(dir.path()) @@ -735,7 +737,7 @@ fn edit_without_editor_set_fails_cleanly() { /// Seed one `summary_cache` row for the given entity (the column shape /// `analyze` and the cache writer use). fn seed_summary_cache(root: &std::path::Path, entity_id: &str) { - let db_path = root.join(".clarion").join("clarion.db"); + let db_path = root.join(".loomweave").join("loomweave.db"); let conn = Connection::open(&db_path).expect("open db"); conn.execute( "INSERT INTO summary_cache \ @@ -750,7 +752,7 @@ fn seed_summary_cache(root: &std::path::Path, entity_id: &str) { } fn summary_cache_count(root: &std::path::Path, entity_id: &str) -> i64 { - let db_path = root.join(".clarion").join("clarion.db"); + let db_path = root.join(".loomweave").join("loomweave.db"); let conn = Connection::open(&db_path).expect("open db"); conn.query_row( "SELECT COUNT(*) FROM summary_cache WHERE entity_id = ?1", @@ -772,7 +774,7 @@ fn create_invalidates_cached_summary_for_matched_entity() { 1 ); - let assert = clarion_bin() + let assert = loomweave_bin() .args(["guidance", "create"]) .args(["--path"]) .arg(dir.path()) @@ -800,7 +802,7 @@ fn create_non_matching_sheet_leaves_cache_intact() { seed_db(dir.path()); seed_summary_cache(dir.path(), "python:function:auth.tokens.refresh"); - clarion_bin() + loomweave_bin() .args(["guidance", "create"]) .args(["--path"]) .arg(dir.path()) @@ -823,7 +825,7 @@ fn delete_invalidates_cached_summary_for_matched_entity() { let dir = tempfile::tempdir().unwrap(); seed_db(dir.path()); - clarion_bin() + loomweave_bin() .args(["guidance", "create"]) .args(["--path"]) .arg(dir.path()) @@ -843,7 +845,7 @@ fn delete_invalidates_cached_summary_for_matched_entity() { 1 ); - let assert = clarion_bin() + let assert = loomweave_bin() .args(["guidance", "delete", "core:guidance:auth-sheet"]) .args(["--path"]) .arg(dir.path()) @@ -869,7 +871,7 @@ fn edit_invalidates_cached_summary_for_matched_entity() { seed_db(dir.path()); let id = "core:guidance:edit-cache"; - clarion_bin() + loomweave_bin() .args(["guidance", "create"]) .args(["--path"]) .arg(dir.path()) @@ -893,7 +895,7 @@ fn edit_invalidates_cached_summary_for_matched_entity() { std::fs::set_permissions(&editor, perms).unwrap(); } - let assert = clarion_bin() + let assert = loomweave_bin() .args(["guidance", "edit", id]) .args(["--path"]) .arg(dir.path()) @@ -917,7 +919,7 @@ fn edit_invalidates_cached_summary_for_matched_entity() { /// Run `guidance export --to `. fn export_to(root: &std::path::Path, to_dir: &std::path::Path) { - clarion_bin() + loomweave_bin() .args(["guidance", "export"]) .args(["--path"]) .arg(root) @@ -929,7 +931,7 @@ fn export_to(root: &std::path::Path, to_dir: &std::path::Path) { /// Run `guidance import `. fn import_from(root: &std::path::Path, from_dir: &std::path::Path) { - clarion_bin() + loomweave_bin() .args(["guidance", "import"]) .args(["--path"]) .arg(root) @@ -940,7 +942,7 @@ fn import_from(root: &std::path::Path, from_dir: &std::path::Path) { /// Fetch a guidance sheet's (name, properties) tuple, or None if absent. fn sheet_fields(root: &std::path::Path, id: &str) -> Option<(String, Value)> { - let db_path = root.join(".clarion").join("clarion.db"); + let db_path = root.join(".loomweave").join("loomweave.db"); let conn = Connection::open(&db_path).expect("reopen db"); conn.query_row( "SELECT name, properties FROM entities WHERE id = ?1 AND kind = 'guidance'", @@ -1103,7 +1105,7 @@ fn import_is_idempotent() { assert_eq!(first, second, "re-import is a content no-op"); // Exactly one sheet, not duplicated. - let db_path = dst.path().join(".clarion").join("clarion.db"); + let db_path = dst.path().join(".loomweave").join("loomweave.db"); let conn = Connection::open(&db_path).unwrap(); let count: i64 = conn .query_row( @@ -1161,7 +1163,7 @@ fn import_fails_loudly_on_malformed_file() { // A junk .json file in the import set. std::fs::write(import_dir.path().join("broken.json"), "{ not valid json").unwrap(); - let assert = clarion_bin() + let assert = loomweave_bin() .args(["guidance", "import"]) .args(["--path"]) .arg(dst.path()) @@ -1197,7 +1199,7 @@ fn import_rejects_code_entity_id_and_leaves_entity_intact() { ) .unwrap(); - let assert = clarion_bin() + let assert = loomweave_bin() .args(["guidance", "import"]) .args(["--path"]) .arg(dst.path()) @@ -1220,7 +1222,7 @@ fn import_rejects_code_entity_id_and_leaves_entity_intact() { /// Fetch the raw (name, kind, `plugin_id`, properties) tuple for ANY entity (not /// just guidance), or None. fn sheet_props_raw(root: &std::path::Path, id: &str) -> Option<(String, String, String, String)> { - let db_path = root.join(".clarion").join("clarion.db"); + let db_path = root.join(".loomweave").join("loomweave.db"); let conn = Connection::open(&db_path).expect("reopen db"); conn.query_row( "SELECT name, kind, plugin_id, properties FROM entities WHERE id = ?1", @@ -1242,7 +1244,7 @@ fn import_invalidates_union_of_old_and_new_matches() { // Seed a `class` entity too, so an OLD `kind:class` rule has a target. { - let db_path = dst.path().join(".clarion").join("clarion.db"); + let db_path = dst.path().join(".loomweave").join("loomweave.db"); let conn = Connection::open(&db_path).unwrap(); conn.execute( "INSERT INTO entities (id, plugin_id, kind, name, short_name, properties, \ @@ -1313,7 +1315,7 @@ fn delete_invalidates_guides_edge_target() { seed_db(dir.path()); // seeds python:function:auth.tokens.refresh // Author a sheet with NO match_rules (so only the guides edge can match). - clarion_bin() + loomweave_bin() .args(["guidance", "create"]) .args(["--path"]) .arg(dir.path()) @@ -1326,7 +1328,7 @@ fn delete_invalidates_guides_edge_target() { // Manually wire a `guides` edge (no authoring path creates one today) and a // cache row on the target. { - let db_path = dir.path().join(".clarion").join("clarion.db"); + let db_path = dir.path().join(".loomweave").join("loomweave.db"); let conn = Connection::open(&db_path).unwrap(); conn.execute( "INSERT INTO edges (kind, from_id, to_id, confidence) VALUES \ @@ -1340,7 +1342,7 @@ fn delete_invalidates_guides_edge_target() { } seed_summary_cache(dir.path(), "python:function:auth.tokens.refresh"); - let assert = clarion_bin() + let assert = loomweave_bin() .args(["guidance", "delete", "core:guidance:guides-sheet"]) .args(["--path"]) .arg(dir.path()) @@ -1404,7 +1406,7 @@ fn import_rejects_guidance_id_with_path_separator() { ) .unwrap(); - let assert = clarion_bin() + let assert = loomweave_bin() .args(["guidance", "import"]) .args(["--path"]) .arg(dst.path()) @@ -1430,7 +1432,7 @@ fn import_rejects_guidance_id_with_path_separator() { fn export_flattens_legacy_guidance_id_path_separators() { let src = tempfile::tempdir().unwrap(); seed_db(src.path()); - let db_path = src.path().join(".clarion").join("clarion.db"); + let db_path = src.path().join(".loomweave").join("loomweave.db"); let conn = Connection::open(&db_path).expect("open db"); conn.execute( "INSERT INTO entities (id, plugin_id, kind, name, short_name, properties, \ @@ -1490,7 +1492,7 @@ fn import_is_partial_but_safe_when_a_later_file_is_malformed() { std::fs::write(import_dir.path().join("zzz-bad.json"), "{ not valid json").unwrap(); // The whole import fails loudly naming the bad file... - let assert = clarion_bin() + let assert = loomweave_bin() .args(["guidance", "import"]) .args(["--path"]) .arg(dst.path()) diff --git a/crates/clarion-cli/tests/hook.rs b/crates/loomweave-cli/tests/hook.rs similarity index 69% rename from crates/clarion-cli/tests/hook.rs rename to crates/loomweave-cli/tests/hook.rs index f55bc0b2..a87c43aa 100644 --- a/crates/clarion-cli/tests/hook.rs +++ b/crates/loomweave-cli/tests/hook.rs @@ -1,13 +1,13 @@ -//! `clarion hook session-start` integration tests. +//! `loomweave hook session-start` integration tests. use assert_cmd::Command; -fn clarion_bin() -> Command { - let mut cmd = Command::cargo_bin("clarion").expect("clarion binary"); +fn loomweave_bin() -> Command { + let mut cmd = Command::cargo_bin("loomweave").expect("loomweave binary"); cmd.env( - "CLARION_CODEX_CONFIG", + "LOOMWEAVE_CODEX_CONFIG", std::env::temp_dir().join(format!( - "clarion-test-codex-config-{}.toml", + "loomweave-test-codex-config-{}.toml", std::process::id() )), ); @@ -15,20 +15,20 @@ fn clarion_bin() -> Command { } #[test] -fn hook_session_start_exits_zero_without_clarion_db() { - // Fail-soft: no .clarion/ at all must still exit 0 and nudge. +fn hook_session_start_exits_zero_without_loomweave_db() { + // Fail-soft: no .loomweave/ at all must still exit 0 and nudge. let dir = tempfile::tempdir().unwrap(); - let assert = clarion_bin() + let assert = loomweave_bin() .args(["hook", "session-start", "--path"]) .arg(dir.path()) .assert() .success(); let out = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); assert!( - out.contains("clarion analyze"), + out.contains("loomweave analyze"), "missing analyze nudge in: {out}" ); - // Installing a skill into a project that never had one is `clarion install + // Installing a skill into a project that never had one is `loomweave install // --skills`'s job, NOT the hook's. The hook only re-syncs a skill that is // already present, so a bare project must come away with no skill roots // created (clarion-ac0fc3bd86). @@ -45,29 +45,33 @@ fn hook_session_start_exits_zero_without_clarion_db() { #[test] fn hook_session_start_prints_counts_for_installed_project() { let dir = tempfile::tempdir().unwrap(); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .assert() .success(); - let assert = clarion_bin() + let assert = loomweave_bin() .args(["hook", "session-start", "--path"]) .arg(dir.path()) .assert() .success(); let out = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); assert!(out.contains("entities"), "missing entity count line: {out}"); - assert!(out.contains("clarion analyze"), "missing nudge: {out}"); + assert!(out.contains("loomweave analyze"), "missing nudge: {out}"); } #[test] fn hook_session_start_exits_zero_with_corrupt_db() { let dir = tempfile::tempdir().unwrap(); - std::fs::create_dir_all(dir.path().join(".clarion")).unwrap(); - // Garbage where clarion.db should be — not a valid SQLite file. - std::fs::write(dir.path().join(".clarion/clarion.db"), b"NOT A SQLITE DB").unwrap(); - let assert = clarion_bin() + std::fs::create_dir_all(dir.path().join(".loomweave")).unwrap(); + // Garbage where loomweave.db should be — not a valid SQLite file. + std::fs::write( + dir.path().join(".loomweave/loomweave.db"), + b"NOT A SQLITE DB", + ) + .unwrap(); + let assert = loomweave_bin() .args(["hook", "session-start", "--path"]) .arg(dir.path()) .assert() @@ -82,15 +86,17 @@ fn hook_session_start_exits_zero_with_corrupt_db() { #[test] fn hook_session_start_resyncs_skill_when_present_and_drifted() { let dir = tempfile::tempdir().unwrap(); - clarion_bin() + loomweave_bin() .args(["install", "--skills", "--path"]) .arg(dir.path()) .assert() .success(); - let skill = dir.path().join(".claude/skills/clarion-workflow/SKILL.md"); + let skill = dir + .path() + .join(".claude/skills/loomweave-workflow/SKILL.md"); std::fs::write(&skill, "STALE").unwrap(); - clarion_bin() + loomweave_bin() .args(["hook", "session-start", "--path"]) .arg(dir.path()) .assert() @@ -98,7 +104,7 @@ fn hook_session_start_resyncs_skill_when_present_and_drifted() { let body = std::fs::read_to_string(&skill).unwrap(); assert!( - body.contains("name: clarion-workflow"), + body.contains("name: loomweave-workflow"), "hook did not repair drifted skill: {body}" ); } diff --git a/crates/clarion-cli/tests/install.rs b/crates/loomweave-cli/tests/install.rs similarity index 65% rename from crates/clarion-cli/tests/install.rs rename to crates/loomweave-cli/tests/install.rs index 47b41ff2..dce89f54 100644 --- a/crates/clarion-cli/tests/install.rs +++ b/crates/loomweave-cli/tests/install.rs @@ -1,16 +1,16 @@ -//! `clarion install` integration tests. +//! `loomweave install` integration tests. use std::fs; use assert_cmd::Command; use rusqlite::Connection; -fn clarion_bin() -> Command { - let mut cmd = Command::cargo_bin("clarion").expect("clarion binary"); +fn loomweave_bin() -> Command { + let mut cmd = Command::cargo_bin("loomweave").expect("loomweave binary"); cmd.env( - "CLARION_CODEX_CONFIG", + "LOOMWEAVE_CODEX_CONFIG", std::env::temp_dir().join(format!( - "clarion-test-codex-config-{}.toml", + "loomweave-test-codex-config-{}.toml", std::process::id() )), ); @@ -22,29 +22,35 @@ fn read_yaml(path: &std::path::Path) -> serde_json::Value { } #[test] -fn install_creates_clarion_dir_with_expected_contents() { +fn install_creates_loomweave_dir_with_expected_contents() { let dir = tempfile::tempdir().unwrap(); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .assert() .success(); - let clarion = dir.path().join(".clarion"); - assert!(clarion.join("clarion.db").exists(), "clarion.db missing"); - assert!(clarion.join("config.json").exists(), "config.json missing"); - assert!(clarion.join(".gitignore").exists(), ".gitignore missing"); + let loomweave = dir.path().join(".loomweave"); assert!( - dir.path().join("clarion.yaml").exists(), - "clarion.yaml not at project root" + loomweave.join("loomweave.db").exists(), + "loomweave.db missing" + ); + assert!( + loomweave.join("config.json").exists(), + "config.json missing" + ); + assert!(loomweave.join(".gitignore").exists(), ".gitignore missing"); + assert!( + dir.path().join("loomweave.yaml").exists(), + "loomweave.yaml not at project root" ); - let config = fs::read_to_string(clarion.join("config.json")).unwrap(); + let config = fs::read_to_string(loomweave.join("config.json")).unwrap(); let parsed: serde_json::Value = serde_json::from_str(&config).unwrap(); assert_eq!(parsed["schema_version"], 1); assert!(parsed["last_run_id"].is_null()); - let gitignore = fs::read_to_string(clarion.join(".gitignore")).unwrap(); + let gitignore = fs::read_to_string(loomweave.join(".gitignore")).unwrap(); for rule in &[ "*.shadow.db", "tmp/", @@ -67,36 +73,36 @@ fn install_all_wires_three_way_integration_bindings() { fs::create_dir_all(&filigree_dir).unwrap(); fs::write(filigree_dir.join("ephemeral.port"), "8749\n").unwrap(); - clarion_bin() + loomweave_bin() .args(["install", "--all", "--path"]) .arg(dir.path()) .assert() .success(); - let clarion_yaml = read_yaml(&dir.path().join("clarion.yaml")); + let loomweave_yaml = read_yaml(&dir.path().join("loomweave.yaml")); assert_eq!( - clarion_yaml["integrations"]["filigree"]["enabled"], + loomweave_yaml["integrations"]["filigree"]["enabled"], serde_json::json!(true) ); assert_eq!( - clarion_yaml["integrations"]["filigree"]["base_url"], + loomweave_yaml["integrations"]["filigree"]["base_url"], "http://127.0.0.1:8749" ); assert_eq!( - clarion_yaml["serve"]["http"]["enabled"], + loomweave_yaml["serve"]["http"]["enabled"], serde_json::json!(true) ); - assert_eq!(clarion_yaml["serve"]["http"]["bind"], "127.0.0.1:9111"); + assert_eq!(loomweave_yaml["serve"]["http"]["bind"], "127.0.0.1:9111"); assert_eq!( - clarion_yaml["serve"]["http"]["wardline_taint_write"], + loomweave_yaml["serve"]["http"]["wardline_taint_write"], serde_json::json!(true) ); let wardline_yaml = read_yaml(&dir.path().join("wardline.yaml")); - assert_eq!(wardline_yaml["clarion"]["url"], "http://127.0.0.1:9111"); + assert_eq!(wardline_yaml["loomweave"]["url"], "http://127.0.0.1:9111"); assert_eq!( wardline_yaml["filigree"]["url"], - "http://127.0.0.1:8749/api/loom/scan-results" + "http://127.0.0.1:8749/api/weft/scan-results" ); let mcp: serde_json::Value = @@ -107,10 +113,10 @@ fn install_all_wires_three_way_integration_bindings() { "mcp", "--root", ".", - "--clarion-url", + "--loomweave-url", "http://127.0.0.1:9111", "--filigree-url", - "http://127.0.0.1:8749/api/loom/scan-results" + "http://127.0.0.1:8749/api/weft/scan-results" ]) ); } @@ -118,13 +124,13 @@ fn install_all_wires_three_way_integration_bindings() { #[test] fn install_applies_each_migration_exactly_once() { let dir = tempfile::tempdir().unwrap(); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .assert() .success(); - let conn = Connection::open(dir.path().join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(dir.path().join(".loomweave/loomweave.db")).unwrap(); let count: i64 = conn .query_row("SELECT COUNT(*) FROM schema_migrations", [], |row| { row.get(0) @@ -132,7 +138,7 @@ fn install_applies_each_migration_exactly_once() { .unwrap(); assert_eq!( count, - i64::from(clarion_storage::schema::CURRENT_SCHEMA_VERSION) + i64::from(loomweave_storage::schema::CURRENT_SCHEMA_VERSION) ); let versions: Vec = { let mut stmt = conn @@ -142,21 +148,21 @@ fn install_applies_each_migration_exactly_once() { rows.map(std::result::Result::unwrap).collect() }; let expected: Vec = - (1..=i64::from(clarion_storage::schema::CURRENT_SCHEMA_VERSION)).collect(); + (1..=i64::from(loomweave_storage::schema::CURRENT_SCHEMA_VERSION)).collect(); assert_eq!(versions, expected); } #[test] -fn install_all_rejects_non_directory_clarion() { - // Bug (PR#21 review #6): when `.clarion` already exists as a regular file +fn install_all_rejects_non_directory_loomweave() { + // Bug (PR#21 review #6): when `.loomweave` already exists as a regular file // and `--all` (a non-bare init) is run without `--force`, install treated // it as "already initialised" and skipped DB creation, then proceeded to - // install skills/hooks atop a project with no usable `.clarion/clarion.db`. + // install skills/hooks atop a project with no usable `.loomweave/loomweave.db`. // It must instead refuse with a clear non-directory error. let dir = tempfile::tempdir().unwrap(); - std::fs::write(dir.path().join(".clarion"), "i am a file, not a dir").unwrap(); + std::fs::write(dir.path().join(".loomweave"), "i am a file, not a dir").unwrap(); - let out = clarion_bin() + let out = loomweave_bin() .args(["install", "--all", "--path"]) .arg(dir.path()) .env("PATH", "") @@ -165,19 +171,19 @@ fn install_all_rejects_non_directory_clarion() { let stderr = String::from_utf8(out.get_output().stderr.clone()).unwrap(); assert!( stderr.contains("non-directory"), - "error did not mention the non-directory .clarion: {stderr}" + "error did not mention the non-directory .loomweave: {stderr}" ); } #[test] -fn install_force_rejects_non_directory_clarion() { +fn install_force_rejects_non_directory_loomweave() { // The --force overwrite path has its own non-directory guard (distinct from - // the --all skip-init guard): it can only remove an existing .clarion/ + // the --all skip-init guard): it can only remove an existing .loomweave/ // *directory*, never a regular file masquerading as one. let dir = tempfile::tempdir().unwrap(); - std::fs::write(dir.path().join(".clarion"), "i am a file, not a dir").unwrap(); + std::fs::write(dir.path().join(".loomweave"), "i am a file, not a dir").unwrap(); - let out = clarion_bin() + let out = loomweave_bin() .args(["install", "--force", "--path"]) .arg(dir.path()) .env("PATH", "") @@ -185,23 +191,23 @@ fn install_force_rejects_non_directory_clarion() { .failure(); let stderr = String::from_utf8(out.get_output().stderr.clone()).unwrap(); assert!( - stderr.contains("can only overwrite an existing .clarion/ directory"), + stderr.contains("can only overwrite an existing .loomweave/ directory"), "error did not mention the --force non-directory guard: {stderr}" ); } #[test] -fn install_skips_clarion_init_when_dir_already_exists() { +fn install_skips_loomweave_init_when_dir_already_exists() { let dir = tempfile::tempdir().unwrap(); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .assert() .success(); - // Second bare install must succeed: skip .clarion/ init but still apply + // Second bare install must succeed: skip .loomweave/ init but still apply // skills/hooks idempotently and report "already initialised". - let out = clarion_bin() + let out = loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .assert() @@ -214,34 +220,37 @@ fn install_skips_clarion_init_when_dir_already_exists() { } #[test] -fn install_force_replaces_existing_clarion_dir_without_overwriting_yaml() { +fn install_force_replaces_existing_loomweave_dir_without_overwriting_yaml() { let dir = tempfile::tempdir().unwrap(); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .assert() .success(); - let clarion = dir.path().join(".clarion"); - fs::write(clarion.join("stale.tmp"), "stale").unwrap(); + let loomweave = dir.path().join(".loomweave"); + fs::write(loomweave.join("stale.tmp"), "stale").unwrap(); fs::write( - dir.path().join("clarion.yaml"), + dir.path().join("loomweave.yaml"), "version: 1\ncustom: true\n", ) .unwrap(); - clarion_bin() + loomweave_bin() .args(["install", "--force", "--path"]) .arg(dir.path()) .assert() .success(); assert!( - !clarion.join("stale.tmp").exists(), - "--force should remove stale .clarion/ contents" + !loomweave.join("stale.tmp").exists(), + "--force should remove stale .loomweave/ contents" + ); + assert!( + loomweave.join("loomweave.db").exists(), + "loomweave.db missing" ); - assert!(clarion.join("clarion.db").exists(), "clarion.db missing"); - let yaml = read_yaml(&dir.path().join("clarion.yaml")); + let yaml = read_yaml(&dir.path().join("loomweave.yaml")); assert_eq!(yaml["custom"], serde_json::json!(true)); assert_eq!( yaml["serve"]["http"]["wardline_taint_write"], @@ -251,49 +260,49 @@ fn install_force_replaces_existing_clarion_dir_without_overwriting_yaml() { #[cfg(unix)] #[test] -fn install_cleans_up_clarion_dir_when_post_mkdir_step_fails() { - // Bug clarion-ed5017139f: `clarion install` left .clarion/ partially +fn install_cleans_up_loomweave_dir_when_post_mkdir_step_fails() { + // Bug clarion-ed5017139f: `loomweave install` left .loomweave/ partially // populated on failure, blocking re-install without manual rm -rf. // - // Reproducer: pre-create clarion.yaml as a *broken symlink* whose target + // Reproducer: pre-create loomweave.yaml as a *broken symlink* whose target // sits under a non-existent parent dir. Install's `yaml_path.exists()` // check follows symlinks → returns false → install attempts `fs::write`, // which follows the symlink → tries to open a path under a non-existent - // dir → ENOENT. By that point .clarion/ has been mkdir'd and populated; + // dir → ENOENT. By that point .loomweave/ has been mkdir'd and populated; // the bug was leaving it on disk. use std::os::unix::fs::symlink; let dir = tempfile::tempdir().unwrap(); - let yaml = dir.path().join("clarion.yaml"); + let yaml = dir.path().join("loomweave.yaml"); symlink( - "/clarion-test-nonexistent-by-construction/never/exists/cannot-write", + "/loomweave-test-nonexistent-by-construction/never/exists/cannot-write", &yaml, ) .unwrap(); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .assert() .failure(); - let clarion = dir.path().join(".clarion"); + let loomweave = dir.path().join(".loomweave"); assert!( - !clarion.exists(), - ".clarion/ should have been cleaned up after install failed, \ + !loomweave.exists(), + ".loomweave/ should have been cleaned up after install failed, \ but it still exists at {}", - clarion.display() + loomweave.display() ); } #[test] -fn install_preserves_existing_clarion_yaml_keys_while_wiring_bindings() { +fn install_preserves_existing_loomweave_yaml_keys_while_wiring_bindings() { let dir = tempfile::tempdir().unwrap(); - let yaml_path = dir.path().join("clarion.yaml"); - let user_content = "# user-edited clarion.yaml\nversion: 1\ncustom_key: preserved\n"; + let yaml_path = dir.path().join("loomweave.yaml"); + let user_content = "# user-edited loomweave.yaml\nversion: 1\ncustom_key: preserved\n"; fs::write(&yaml_path, user_content).unwrap(); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .assert() @@ -312,25 +321,25 @@ fn install_preserves_existing_clarion_yaml_keys_while_wiring_bindings() { } #[test] -fn install_claude_code_writes_mcp_json_without_initialising_clarion_dir() { +fn install_claude_code_writes_mcp_json_without_initialising_loomweave_dir() { let dir = tempfile::tempdir().unwrap(); - clarion_bin() + loomweave_bin() .args(["install", "--claude-code", "--path"]) .arg(dir.path()) .assert() .success(); assert!( - !dir.path().join(".clarion").exists(), - "--claude-code should not create .clarion/" + !dir.path().join(".loomweave").exists(), + "--claude-code should not create .loomweave/" ); let raw = fs::read_to_string(dir.path().join(".mcp.json")).unwrap(); let parsed: serde_json::Value = serde_json::from_str(&raw).unwrap(); - let entry = &parsed["mcpServers"]["clarion"]; + let entry = &parsed["mcpServers"]["loomweave"]; assert_eq!(entry["type"], "stdio"); assert!( - entry["command"].as_str().unwrap().ends_with("clarion"), - "command should point at a clarion executable: {entry:?}" + entry["command"].as_str().unwrap().ends_with("loomweave"), + "command should point at a loomweave executable: {entry:?}" ); assert_eq!( entry["args"], @@ -340,11 +349,11 @@ fn install_claude_code_writes_mcp_json_without_initialising_clarion_dir() { } #[test] -fn install_codex_writes_requested_config_without_initialising_clarion_dir() { +fn install_codex_writes_requested_config_without_initialising_loomweave_dir() { let dir = tempfile::tempdir().unwrap(); let codex_config = dir.path().join("codex-config.toml"); - clarion_bin() + loomweave_bin() .args(["install", "--codex", "--codex-config"]) .arg(&codex_config) .args(["--path"]) @@ -353,12 +362,12 @@ fn install_codex_writes_requested_config_without_initialising_clarion_dir() { .success(); assert!( - !dir.path().join(".clarion").exists(), - "--codex should not create .clarion/" + !dir.path().join(".loomweave").exists(), + "--codex should not create .loomweave/" ); let raw = fs::read_to_string(&codex_config).unwrap(); assert!( - raw.contains("[mcp_servers.clarion]"), + raw.contains("[mcp_servers.loomweave]"), "Codex MCP entry missing: {raw}" ); assert!( @@ -371,9 +380,9 @@ fn install_codex_writes_requested_config_without_initialising_clarion_dir() { fn dotenv_in_cwd_is_loaded_before_tracing_setup() { // Proves the dotenvy hook in `main()` runs before `init_tracing()`: a // `.env`-supplied RUST_LOG=debug enables the debug-level log line in - // `install` (the "clarion.yaml already exists; leaving untouched" + // `install` (the "loomweave.yaml already exists; leaving untouched" // branch in install.rs) that the default `info` filter would - // otherwise suppress. Pre-creating `clarion.yaml` puts us on the + // otherwise suppress. Pre-creating `loomweave.yaml` puts us on the // branch that emits debug. // // Uses raw std::process::Command rather than assert_cmd::Command so the @@ -382,22 +391,22 @@ fn dotenv_in_cwd_is_loaded_before_tracing_setup() { // producing an empty stderr regardless of .env content. let dir = tempfile::tempdir().unwrap(); fs::write(dir.path().join(".env"), "RUST_LOG=debug\n").unwrap(); - fs::write(dir.path().join("clarion.yaml"), "version: 1\n").unwrap(); + fs::write(dir.path().join("loomweave.yaml"), "version: 1\n").unwrap(); - let bin = assert_cmd::cargo::cargo_bin("clarion"); + let bin = assert_cmd::cargo::cargo_bin("loomweave"); let path = std::env::var("PATH").unwrap_or_default(); let out = std::process::Command::new(&bin) .current_dir(dir.path()) .env_clear() .env("PATH", path) .env( - "CLARION_CODEX_CONFIG", + "LOOMWEAVE_CODEX_CONFIG", dir.path().join("isolated-codex-config.toml"), ) .args(["install", "--path"]) .arg(dir.path()) .output() - .expect("clarion install"); + .expect("loomweave install"); let stdout = String::from_utf8_lossy(&out.stdout); let stderr = String::from_utf8_lossy(&out.stderr); @@ -421,9 +430,9 @@ fn explicit_env_var_wins_over_dotenv() { // suppress the debug line even when .env tries to bump verbosity. let dir = tempfile::tempdir().unwrap(); fs::write(dir.path().join(".env"), "RUST_LOG=debug\n").unwrap(); - fs::write(dir.path().join("clarion.yaml"), "version: 1\n").unwrap(); + fs::write(dir.path().join("loomweave.yaml"), "version: 1\n").unwrap(); - let bin = assert_cmd::cargo::cargo_bin("clarion"); + let bin = assert_cmd::cargo::cargo_bin("loomweave"); let path = std::env::var("PATH").unwrap_or_default(); let out = std::process::Command::new(&bin) .current_dir(dir.path()) @@ -431,13 +440,13 @@ fn explicit_env_var_wins_over_dotenv() { .env("PATH", path) .env("RUST_LOG", "info") .env( - "CLARION_CODEX_CONFIG", + "LOOMWEAVE_CODEX_CONFIG", dir.path().join("isolated-codex-config.toml"), ) .args(["install", "--path"]) .arg(dir.path()) .output() - .expect("clarion install"); + .expect("loomweave install"); let stdout = String::from_utf8_lossy(&out.stdout); let stderr = String::from_utf8_lossy(&out.stderr); diff --git a/crates/clarion-cli/tests/sarif.rs b/crates/loomweave-cli/tests/sarif.rs similarity index 91% rename from crates/clarion-cli/tests/sarif.rs rename to crates/loomweave-cli/tests/sarif.rs index 764c2821..8d37de43 100644 --- a/crates/clarion-cli/tests/sarif.rs +++ b/crates/loomweave-cli/tests/sarif.rs @@ -1,4 +1,4 @@ -//! `clarion sarif import` integration tests. +//! `loomweave sarif import` integration tests. use std::fs; use std::io::{Read, Write}; @@ -6,12 +6,12 @@ use std::net::TcpListener; use assert_cmd::Command; -fn clarion_bin() -> Command { - let mut cmd = Command::cargo_bin("clarion").expect("clarion binary"); +fn loomweave_bin() -> Command { + let mut cmd = Command::cargo_bin("loomweave").expect("loomweave binary"); cmd.env( - "CLARION_CODEX_CONFIG", + "LOOMWEAVE_CODEX_CONFIG", std::env::temp_dir().join(format!( - "clarion-test-codex-config-{}.toml", + "loomweave-test-codex-config-{}.toml", std::process::id() )), ); @@ -24,7 +24,7 @@ fn sarif_import_posts_findings_to_mock_filigree() { let listener = TcpListener::bind("127.0.0.1:0").expect("bind test server"); let addr = listener.local_addr().expect("local addr"); - // Spawn a thread to receive the HTTP request from clarion sarif import + // Spawn a thread to receive the HTTP request from loomweave sarif import let handle = std::thread::spawn(move || { let (mut stream, _) = listener.accept().expect("accept connection"); let mut request = vec![0_u8; 8192]; @@ -91,7 +91,7 @@ fn sarif_import_posts_findings_to_mock_filigree() { let dir = tempfile::tempdir().unwrap(); - // Write a mock clarion.yaml config + // Write a mock loomweave.yaml config let config_content = format!( r#" integrations: @@ -102,10 +102,10 @@ integrations: token_env: "TEST_FILIGREE_TOKEN" "# ); - fs::write(dir.path().join("clarion.yaml"), config_content).unwrap(); + fs::write(dir.path().join("loomweave.yaml"), config_content).unwrap(); - // Create a dummy .clarion dir so it passes the project layout checks - fs::create_dir_all(dir.path().join(".clarion")).unwrap(); + // Create a dummy .loomweave dir so it passes the project layout checks + fs::create_dir_all(dir.path().join(".loomweave")).unwrap(); // Write a mock SARIF file let sarif_content = r#"{ @@ -154,7 +154,7 @@ integrations: fs::write(&sarif_path, sarif_content).unwrap(); // Run the cli command - clarion_bin() + loomweave_bin() .env("TEST_FILIGREE_TOKEN", "my-mock-token") .args(["sarif", "import"]) .arg(&sarif_path) @@ -204,7 +204,7 @@ fn sarif_import_prefers_wardline_partial_fingerprint() { let dir = tempfile::tempdir().unwrap(); fs::write( - dir.path().join("clarion.yaml"), + dir.path().join("loomweave.yaml"), format!( r#" integrations: @@ -217,7 +217,7 @@ integrations: ), ) .unwrap(); - fs::create_dir_all(dir.path().join(".clarion")).unwrap(); + fs::create_dir_all(dir.path().join(".loomweave")).unwrap(); let sarif_content = r#"{ "version": "2.1.0", "runs": [{ @@ -243,7 +243,7 @@ integrations: let sarif_path = dir.path().join("wardline.sarif"); fs::write(&sarif_path, sarif_content).unwrap(); - clarion_bin() + loomweave_bin() .env("TEST_FILIGREE_TOKEN", "my-mock-token") .args(["sarif", "import"]) .arg(&sarif_path) diff --git a/crates/clarion-cli/tests/secret_scan.rs b/crates/loomweave-cli/tests/secret_scan.rs similarity index 90% rename from crates/clarion-cli/tests/secret_scan.rs rename to crates/loomweave-cli/tests/secret_scan.rs index 6c4e3d5b..6d10eb84 100644 --- a/crates/clarion-cli/tests/secret_scan.rs +++ b/crates/loomweave-cli/tests/secret_scan.rs @@ -4,12 +4,12 @@ use assert_cmd::Command; use rusqlite::Connection; use sha1::{Digest, Sha1}; -fn clarion_bin() -> Command { - let mut cmd = Command::cargo_bin("clarion").expect("clarion binary"); +fn loomweave_bin() -> Command { + let mut cmd = Command::cargo_bin("loomweave").expect("loomweave binary"); cmd.env( - "CLARION_CODEX_CONFIG", + "LOOMWEAVE_CODEX_CONFIG", std::env::temp_dir().join(format!( - "clarion-test-codex-config-{}.toml", + "loomweave-test-codex-config-{}.toml", std::process::id() )), ); @@ -56,7 +56,7 @@ while True: "jsonrpc": "2.0", "id": ident, "result": { - "name": "clarion-plugin-secretfixture", + "name": "loomweave-plugin-secretfixture", "version": "0.1.0", "ontology_version": "0.1.0", "capabilities": {}, @@ -65,7 +65,7 @@ while True: elif method == "analyze_file": if ( os.environ.get("SECRETFIXTURE_ASSERT_ENV_ABSENT") - and os.environ.get("CLARION_DOTENV_SENTINEL") is not None + and os.environ.get("LOOMWEAVE_DOTENV_SENTINEL") is not None ): raise SystemExit(42) path = msg["params"]["file_path"] @@ -94,11 +94,11 @@ while True: const PLUGIN_MANIFEST: &str = r#" [plugin] -name = "clarion-plugin-secretfixture" +name = "loomweave-plugin-secretfixture" plugin_id = "secretfixture" version = "0.1.0" protocol_version = "1.0" -executable = "clarion-plugin-secretfixture" +executable = "loomweave-plugin-secretfixture" language = "secretfixture" extensions = ["sec"] @@ -111,7 +111,7 @@ reads_outside_project_root = false [ontology] entity_kinds = ["module"] edge_kinds = [] -rule_id_prefix = "CLA-SECRET-FIXTURE-" +rule_id_prefix = "LMWV-SECRET-FIXTURE-" ontology_version = "0.1.0" [ontology.roles] @@ -121,7 +121,7 @@ file_scope = ["module"] fn write_secret_fixture_plugin(plugin_dir: &std::path::Path) { use std::os::unix::fs::PermissionsExt; - let plugin_script = plugin_dir.join("clarion-plugin-secretfixture"); + let plugin_script = plugin_dir.join("loomweave-plugin-secretfixture"); std::fs::write(&plugin_script, PLUGIN_SCRIPT).expect("write plugin script"); let mut perms = std::fs::metadata(&plugin_script) .expect("stat plugin") @@ -133,7 +133,7 @@ fn write_secret_fixture_plugin(plugin_dir: &std::path::Path) { } fn install_project(project: &std::path::Path) { - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(project) .assert() @@ -145,7 +145,7 @@ fn plugin_path(plugin_dir: &std::path::Path) -> std::ffi::OsString { } fn conn(project: &std::path::Path) -> Connection { - Connection::open(project.join(".clarion/clarion.db")).expect("open clarion db") + Connection::open(project.join(".loomweave/loomweave.db")).expect("open loomweave db") } fn sha1_hex(bytes: &[u8]) -> String { @@ -169,7 +169,7 @@ fn clean_project_has_no_secret_findings() { install_project(project.path()); std::fs::write(project.path().join("clean.sec"), b"nothing to see\n").unwrap(); - clarion_bin() + loomweave_bin() .arg("analyze") .arg(project.path()) .env("PATH", plugin_path(plugin.path())) @@ -178,7 +178,7 @@ fn clean_project_has_no_secret_findings() { let count: i64 = conn(project.path()) .query_row( - "SELECT COUNT(*) FROM findings WHERE rule_id LIKE 'CLA-SEC-%'", + "SELECT COUNT(*) FROM findings WHERE rule_id LIKE 'LMWV-SEC-%'", [], |row| row.get(0), ) @@ -198,7 +198,7 @@ fn secret_file_persists_finding_and_briefing_block() { ) .unwrap(); - clarion_bin() + loomweave_bin() .arg("analyze") .arg(project.path()) .env("PATH", plugin_path(plugin.path())) @@ -216,7 +216,7 @@ fn secret_file_persists_finding_and_briefing_block() { assert_eq!(blocked, "secret_present"); let count: i64 = db .query_row( - "SELECT COUNT(*) FROM findings WHERE rule_id = 'CLA-SEC-SECRET-DETECTED'", + "SELECT COUNT(*) FROM findings WHERE rule_id = 'LMWV-SEC-SECRET-DETECTED'", [], |row| row.get(0), ) @@ -248,7 +248,7 @@ fn resume_does_not_duplicate_secret_findings() { .unwrap(); // Fresh run. - clarion_bin() + loomweave_bin() .arg("analyze") .arg(project.path()) .env("PATH", plugin_path(plugin.path())) @@ -266,7 +266,7 @@ fn resume_does_not_duplicate_secret_findings() { .unwrap(); let first_id = db .query_row( - "SELECT id FROM findings WHERE rule_id = 'CLA-SEC-SECRET-DETECTED'", + "SELECT id FROM findings WHERE rule_id = 'LMWV-SEC-SECRET-DETECTED'", [], |r| r.get(0), ) @@ -275,7 +275,7 @@ fn resume_does_not_duplicate_secret_findings() { }; // Resume the same run. - clarion_bin() + loomweave_bin() .arg("analyze") .args(["--resume", &run_id]) .arg(project.path()) @@ -286,7 +286,7 @@ fn resume_does_not_duplicate_secret_findings() { let db = conn(project.path()); let count: i64 = db .query_row( - "SELECT COUNT(*) FROM findings WHERE rule_id = 'CLA-SEC-SECRET-DETECTED'", + "SELECT COUNT(*) FROM findings WHERE rule_id = 'LMWV-SEC-SECRET-DETECTED'", [], |row| row.get(0), ) @@ -294,7 +294,7 @@ fn resume_does_not_duplicate_secret_findings() { assert_eq!(count, 1, "resume must not duplicate the secret finding"); let after_id: String = db .query_row( - "SELECT id FROM findings WHERE rule_id = 'CLA-SEC-SECRET-DETECTED'", + "SELECT id FROM findings WHERE rule_id = 'LMWV-SEC-SECRET-DETECTED'", [], |r| r.get(0), ) @@ -322,7 +322,7 @@ fn dotenv_sidecar_persists_finding_with_core_file_anchor() { ) .unwrap(); - clarion_bin() + loomweave_bin() .arg("analyze") .arg(project.path()) .env("PATH", plugin_path(plugin.path())) @@ -332,7 +332,7 @@ fn dotenv_sidecar_persists_finding_with_core_file_anchor() { let db = conn(project.path()); let finding_count: i64 = db .query_row( - "SELECT COUNT(*) FROM findings WHERE rule_id = 'CLA-SEC-SECRET-DETECTED'", + "SELECT COUNT(*) FROM findings WHERE rule_id = 'LMWV-SEC-SECRET-DETECTED'", [], |row| row.get(0), ) @@ -361,11 +361,11 @@ fn analyze_does_not_load_dotenv_into_plugin_environment() { std::fs::write(project.path().join("clean.sec"), b"nothing to see\n").unwrap(); std::fs::write( project.path().join(".env"), - b"CLARION_DOTENV_SENTINEL=ordinaryvalue\n", + b"LOOMWEAVE_DOTENV_SENTINEL=ordinaryvalue\n", ) .unwrap(); - clarion_bin() + loomweave_bin() .arg("analyze") .arg(".") .current_dir(project.path()) @@ -385,7 +385,7 @@ fn plugin_entity_for_unscanned_source_is_briefing_blocked() { let unscanned = project.path().join("notes.txt"); std::fs::write(&unscanned, b"ordinary notes\n").unwrap(); - clarion_bin() + loomweave_bin() .arg("analyze") .arg(project.path()) .env("PATH", plugin_path(plugin.path())) @@ -417,7 +417,7 @@ fn baseline_suppresses_secret_and_emits_audit_match() { .unwrap(); let hashed_secret = sha1_hex(b"AKIAIOSFODNN7EXAMPLE"); std::fs::write( - project.path().join(".clarion/secrets-baseline.yaml"), + project.path().join(".loomweave/secrets-baseline.yaml"), format!( r#" version: "1.0" @@ -433,7 +433,7 @@ results: ) .unwrap(); - clarion_bin() + loomweave_bin() .arg("analyze") .arg(project.path()) .env("PATH", plugin_path(plugin.path())) @@ -443,14 +443,14 @@ results: let db = conn(project.path()); let secret_count: i64 = db .query_row( - "SELECT COUNT(*) FROM findings WHERE rule_id = 'CLA-SEC-SECRET-DETECTED'", + "SELECT COUNT(*) FROM findings WHERE rule_id = 'LMWV-SEC-SECRET-DETECTED'", [], |row| row.get(0), ) .unwrap(); let match_count: i64 = db .query_row( - "SELECT COUNT(*) FROM findings WHERE rule_id = 'CLA-INFRA-SECRET-BASELINE-MATCH'", + "SELECT COUNT(*) FROM findings WHERE rule_id = 'LMWV-INFRA-SECRET-BASELINE-MATCH'", [], |row| row.get(0), ) @@ -480,7 +480,7 @@ fn missing_baseline_justification_degrades_to_finding() { .unwrap(); let hashed_secret = sha1_hex(b"AKIAIOSFODNN7EXAMPLE"); std::fs::write( - project.path().join(".clarion/secrets-baseline.yaml"), + project.path().join(".loomweave/secrets-baseline.yaml"), format!( r#" version: "1.0" @@ -500,7 +500,7 @@ results: ) .unwrap(); - clarion_bin() + loomweave_bin() .arg("analyze") .arg(project.path()) .env("PATH", plugin_path(plugin.path())) @@ -509,7 +509,7 @@ results: let count: i64 = conn(project.path()) .query_row( - "SELECT COUNT(*) FROM findings WHERE rule_id = 'CLA-INFRA-SECRET-BASELINE-NO-JUSTIFICATION'", + "SELECT COUNT(*) FROM findings WHERE rule_id = 'LMWV-INFRA-SECRET-BASELINE-NO-JUSTIFICATION'", [], |row| row.get(0), ) @@ -529,7 +529,7 @@ fn non_tty_override_confirmed_allows_briefing_and_records_stats() { ) .unwrap(); - clarion_bin() + loomweave_bin() .args([ "analyze", "--allow-unredacted-secrets", @@ -550,7 +550,7 @@ fn non_tty_override_confirmed_allows_briefing_and_records_stats() { .unwrap(); let override_count: i64 = db .query_row( - "SELECT COUNT(*) FROM findings WHERE rule_id = 'CLA-SEC-UNREDACTED-SECRETS-ALLOWED'", + "SELECT COUNT(*) FROM findings WHERE rule_id = 'LMWV-SEC-UNREDACTED-SECRETS-ALLOWED'", [], |row| row.get(0), ) @@ -564,7 +564,7 @@ fn non_tty_override_confirmed_allows_briefing_and_records_stats() { .unwrap(); let evidence_json: String = db .query_row( - "SELECT evidence FROM findings WHERE rule_id = 'CLA-SEC-UNREDACTED-SECRETS-ALLOWED'", + "SELECT evidence FROM findings WHERE rule_id = 'LMWV-SEC-UNREDACTED-SECRETS-ALLOWED'", [], |row| row.get(0), ) @@ -572,11 +572,11 @@ fn non_tty_override_confirmed_allows_briefing_and_records_stats() { let evidence: serde_json::Value = serde_json::from_str(&evidence_json).expect("override evidence JSON"); // ADR-013: override is additive — per-(rule,file,line) SECRET_DETECTED - // still lands so `filigree list --rule-id=CLA-SEC-SECRET-DETECTED` + // still lands so `filigree list --rule-id=LMWV-SEC-SECRET-DETECTED` // enumerates the full audited population, not just the unoverridden tail. let secret_detected_count: i64 = db .query_row( - "SELECT COUNT(*) FROM findings WHERE rule_id = 'CLA-SEC-SECRET-DETECTED'", + "SELECT COUNT(*) FROM findings WHERE rule_id = 'LMWV-SEC-SECRET-DETECTED'", [], |row| row.get(0), ) @@ -609,14 +609,14 @@ fn non_tty_override_without_confirmation_exits_78_before_run_start() { ) .unwrap(); - let assert = clarion_bin() + let assert = loomweave_bin() .args(["analyze", "--allow-unredacted-secrets"]) .arg(project.path()) .env("PATH", plugin_path(plugin.path())) .assert() .code(78); let stderr = String::from_utf8_lossy(&assert.get_output().stderr); - assert!(stderr.contains("CLA-INFRA-SECRET-OVERRIDE-UNCONFIRMED")); + assert!(stderr.contains("LMWV-INFRA-SECRET-OVERRIDE-UNCONFIRMED")); assert!(stderr.contains("leaky.sec:1 AwsAccessKeyId")); let run_count: i64 = conn(project.path()) .query_row("SELECT COUNT(*) FROM runs", [], |row| row.get(0)) @@ -636,7 +636,7 @@ fn non_tty_override_with_wrong_confirmation_exits_78_before_run_start() { ) .unwrap(); - let assert = clarion_bin() + let assert = loomweave_bin() .args([ "analyze", "--allow-unredacted-secrets", @@ -647,7 +647,7 @@ fn non_tty_override_with_wrong_confirmation_exits_78_before_run_start() { .assert() .code(78); let stderr = String::from_utf8_lossy(&assert.get_output().stderr); - assert!(stderr.contains("CLA-INFRA-SECRET-OVERRIDE-UNCONFIRMED")); + assert!(stderr.contains("LMWV-INFRA-SECRET-OVERRIDE-UNCONFIRMED")); let run_count: i64 = conn(project.path()) .query_row("SELECT COUNT(*) FROM runs", [], |row| row.get(0)) .unwrap(); @@ -672,7 +672,7 @@ fn baseline_suppression_and_override_admission_are_audited_together() { .unwrap(); let hashed_secret = sha1_hex(b"AKIAIOSFODNN7EXAMPLE"); std::fs::write( - project.path().join(".clarion/secrets-baseline.yaml"), + project.path().join(".loomweave/secrets-baseline.yaml"), format!( r#" version: "1.0" @@ -688,7 +688,7 @@ results: ) .unwrap(); - clarion_bin() + loomweave_bin() .args([ "analyze", "--allow-unredacted-secrets", @@ -702,21 +702,21 @@ results: let db = conn(project.path()); let baseline_count: i64 = db .query_row( - "SELECT COUNT(*) FROM findings WHERE rule_id = 'CLA-INFRA-SECRET-BASELINE-MATCH'", + "SELECT COUNT(*) FROM findings WHERE rule_id = 'LMWV-INFRA-SECRET-BASELINE-MATCH'", [], |row| row.get(0), ) .unwrap(); let override_count: i64 = db .query_row( - "SELECT COUNT(*) FROM findings WHERE rule_id = 'CLA-SEC-UNREDACTED-SECRETS-ALLOWED'", + "SELECT COUNT(*) FROM findings WHERE rule_id = 'LMWV-SEC-UNREDACTED-SECRETS-ALLOWED'", [], |row| row.get(0), ) .unwrap(); let secret_count: i64 = db .query_row( - "SELECT COUNT(*) FROM findings WHERE rule_id = 'CLA-SEC-SECRET-DETECTED'", + "SELECT COUNT(*) FROM findings WHERE rule_id = 'LMWV-SEC-SECRET-DETECTED'", [], |row| row.get(0), ) @@ -748,12 +748,12 @@ fn assert_invalid_baseline_aborts(raw_baseline: &str, expected_stderr: &str) { install_project(project.path()); std::fs::write(project.path().join("leaky.sec"), b"nothing to see\n").unwrap(); std::fs::write( - project.path().join(".clarion/secrets-baseline.yaml"), + project.path().join(".loomweave/secrets-baseline.yaml"), raw_baseline, ) .unwrap(); - let assert = clarion_bin() + let assert = loomweave_bin() .arg("analyze") .arg(project.path()) .env("PATH", plugin_path(plugin.path())) @@ -843,7 +843,7 @@ fn override_flag_is_noop_without_detections() { install_project(project.path()); std::fs::write(project.path().join("clean.sec"), b"nothing to see\n").unwrap(); - clarion_bin() + loomweave_bin() .args(["analyze", "--allow-unredacted-secrets"]) .arg(project.path()) .env("PATH", plugin_path(plugin.path())) @@ -853,7 +853,7 @@ fn override_flag_is_noop_without_detections() { let db = conn(project.path()); let override_count: i64 = db .query_row( - "SELECT COUNT(*) FROM findings WHERE rule_id = 'CLA-SEC-UNREDACTED-SECRETS-ALLOWED'", + "SELECT COUNT(*) FROM findings WHERE rule_id = 'LMWV-SEC-UNREDACTED-SECRETS-ALLOWED'", [], |row| row.get(0), ) @@ -883,7 +883,7 @@ fn only_secret_bearing_file_is_blocked_in_multi_file_project() { .unwrap(); std::fs::write(project.path().join("clean_b.sec"), b"still clean\n").unwrap(); - clarion_bin() + loomweave_bin() .arg("analyze") .arg(project.path()) .env("PATH", plugin_path(plugin.path())) @@ -915,7 +915,7 @@ fn cleared_sidecar_clears_stale_briefing_block() { let dotenv = project.path().join(".env"); std::fs::write(&dotenv, b"aws_access_key_id = 'AKIAIOSFODNN7EXAMPLE'\n").unwrap(); - clarion_bin() + loomweave_bin() .arg("analyze") .arg(project.path()) .env("PATH", plugin_path(plugin.path())) @@ -938,7 +938,7 @@ fn cleared_sidecar_clears_stale_briefing_block() { std::fs::write(&dotenv, b"NO_SECRET_HERE=ordinary_value\n").unwrap(); - clarion_bin() + loomweave_bin() .arg("analyze") .arg(project.path()) .env("PATH", plugin_path(plugin.path())) @@ -984,7 +984,7 @@ fn clean_sidecar_creates_anchor_without_block() { ) .unwrap(); - clarion_bin() + loomweave_bin() .arg("analyze") .arg(project.path()) .env("PATH", plugin_path(plugin.path())) diff --git a/crates/clarion-cli/tests/serve.rs b/crates/loomweave-cli/tests/serve.rs similarity index 93% rename from crates/clarion-cli/tests/serve.rs rename to crates/loomweave-cli/tests/serve.rs index 9e47bfdf..c19c51e0 100644 --- a/crates/clarion-cli/tests/serve.rs +++ b/crates/loomweave-cli/tests/serve.rs @@ -9,11 +9,11 @@ use std::thread; use std::time::{Duration, Instant}; use assert_cmd::Command; -use clarion_core::{ +use hmac::{Hmac, Mac}; +use loomweave_core::{ LEAF_SUMMARY_PROMPT_TEMPLATE_ID, plugin::{ContentLengthCeiling, Frame, read_frame, write_frame}, }; -use hmac::{Hmac, Mac}; use rusqlite::{Connection, params}; use serde::Deserialize; use serde_json::Value; @@ -46,12 +46,12 @@ impl HttpRawResponse { } } -fn clarion_bin() -> Command { - let mut cmd = Command::cargo_bin("clarion").expect("clarion binary"); +fn loomweave_bin() -> Command { + let mut cmd = Command::cargo_bin("loomweave").expect("loomweave binary"); cmd.env( - "CLARION_CODEX_CONFIG", + "LOOMWEAVE_CODEX_CONFIG", std::env::temp_dir().join(format!( - "clarion-test-codex-config-{}.toml", + "loomweave-test-codex-config-{}.toml", std::process::id() )), ); @@ -60,7 +60,7 @@ fn clarion_bin() -> Command { #[test] fn serve_help_advertises_mcp_stdio_server() { - let assert = clarion_bin().args(["serve", "--help"]).assert().success(); + let assert = loomweave_bin().args(["serve", "--help"]).assert().success(); let stdout = String::from_utf8(assert.get_output().stdout.clone()).expect("help is utf8"); assert!(stdout.contains("Run the MCP stdio server")); @@ -70,7 +70,7 @@ fn serve_help_advertises_mcp_stdio_server() { #[test] fn serve_stdio_initialize_round_trip() { let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") @@ -78,14 +78,14 @@ fn serve_stdio_initialize_round_trip() { .success(); write_stdio_config(dir.path()); - let mut child = StdCommand::new(assert_cmd::cargo::cargo_bin("clarion")) + let mut child = StdCommand::new(assert_cmd::cargo::cargo_bin("loomweave")) .args(["serve", "--path"]) .arg(dir.path()) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() - .expect("spawn clarion serve"); + .expect("spawn loomweave serve"); { let mut stdin = child.stdin.take().expect("child stdin"); @@ -109,7 +109,7 @@ fn serve_stdio_initialize_round_trip() { stdin.flush().expect("flush initialize frame"); } - let output = child.wait_with_output().expect("wait for clarion serve"); + let output = child.wait_with_output().expect("wait for loomweave serve"); assert!( output.status.success(), "serve failed: {}", @@ -124,13 +124,13 @@ fn serve_stdio_initialize_round_trip() { assert_eq!(response["id"], 1); assert_eq!(response["result"]["protocolVersion"], "2025-11-25"); - assert_eq!(response["result"]["serverInfo"]["name"], "clarion"); + assert_eq!(response["result"]["serverInfo"]["name"], "loomweave"); } #[test] fn serve_stdio_accepts_mcp_json_line_initialize_without_stdin_eof() { let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") @@ -138,14 +138,14 @@ fn serve_stdio_accepts_mcp_json_line_initialize_without_stdin_eof() { .success(); write_stdio_config(dir.path()); - let mut child = StdCommand::new(assert_cmd::cargo::cargo_bin("clarion")) + let mut child = StdCommand::new(assert_cmd::cargo::cargo_bin("loomweave")) .args(["serve", "--path"]) .arg(dir.path()) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() - .expect("spawn clarion serve"); + .expect("spawn loomweave serve"); let mut stdin = child.stdin.take().expect("child stdin"); let stdout = child.stdout.take().expect("child stdout"); @@ -180,13 +180,13 @@ fn serve_stdio_accepts_mcp_json_line_initialize_without_stdin_eof() { Err(err) => { let _ = child.kill(); let _ = child.wait(); - panic!("clarion serve did not answer newline-delimited MCP initialize: {err}"); + panic!("loomweave serve did not answer newline-delimited MCP initialize: {err}"); } }; let response: Value = serde_json::from_str(line.trim_end()).expect("response line is json"); drop(stdin); - let status = child.wait().expect("wait for clarion serve"); + let status = child.wait().expect("wait for loomweave serve"); reader.join().expect("stdout reader thread"); assert!( @@ -195,7 +195,7 @@ fn serve_stdio_accepts_mcp_json_line_initialize_without_stdin_eof() { read_child_stderr(&mut child) ); assert_eq!(response["id"], 7); - assert_eq!(response["result"]["serverInfo"]["name"], "clarion"); + assert_eq!(response["result"]["serverInfo"]["name"], "loomweave"); } fn read_child_stderr(child: &mut Child) -> String { @@ -225,14 +225,14 @@ fn serve_http_responses_match_federation_fixture_contracts() { include_str!("../../../docs/federation/fixtures/post-api-v1-files-batch.json"), ); let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") .assert() .success(); fs::write( - dir.path().join(".clarion/instance_id"), + dir.path().join(".loomweave/instance_id"), format!("{STABLE_INSTANCE_ID}\n"), ) .expect("seed stable instance ID"); @@ -259,7 +259,7 @@ fn serve_http_responses_match_federation_fixture_contracts() { stop_serve(&mut child); let auth_dir = tempfile::tempdir().expect("temp auth project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(auth_dir.path()) .env("PATH", "") @@ -270,12 +270,12 @@ fn serve_http_responses_match_federation_fixture_contracts() { write_http_config_with_token_env( auth_dir.path(), &auth_bind, - "CLARION_TEST_FIXTURE_BATCH_TOKEN", + "LOOMWEAVE_TEST_FIXTURE_BATCH_TOKEN", ); let mut auth_child = spawn_serve_with_env( auth_dir.path(), - &[("CLARION_TEST_FIXTURE_BATCH_TOKEN", "fixture-secret")], + &[("LOOMWEAVE_TEST_FIXTURE_BATCH_TOKEN", "fixture-secret")], ); validate_fixture_examples_matching( &auth_bind, @@ -289,7 +289,7 @@ fn serve_http_responses_match_federation_fixture_contracts() { #[test] fn serve_http_files_endpoint_returns_briefing_blocked_for_blocked_entity() { let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") @@ -326,7 +326,7 @@ fn serve_http_files_endpoint_returns_briefing_blocked_for_blocked_entity() { #[test] fn serve_http_files_endpoint_resolves_known_file_on_configured_port() { let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") @@ -355,7 +355,7 @@ fn serve_http_files_endpoint_resolves_known_file_on_configured_port() { #[test] fn serve_http_files_endpoint_prefers_stored_manifest_language() { let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") @@ -377,7 +377,7 @@ fn serve_http_files_endpoint_prefers_stored_manifest_language() { #[test] fn serve_http_files_etag_round_trip_and_if_none_match_returns_304() { let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") @@ -413,7 +413,7 @@ fn serve_http_files_etag_round_trip_and_if_none_match_returns_304() { #[test] fn serve_http_files_blank_path_returns_invalid_path_envelope() { let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") @@ -441,7 +441,7 @@ fn serve_http_identity_resolve_rejects_sei_shaped_input_and_resolves_unknown() { // with 400 INVALID_PATH (never silently mis-resolved); a well-formed but // unknown locator resolves to { alive: false }. let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") @@ -451,8 +451,9 @@ fn serve_http_identity_resolve_rejects_sei_shaped_input_and_resolves_unknown() { write_http_config(dir.path(), &bind); let mut child = spawn_serve(dir.path()); - let sei_body = serde_json::json!({ "locator": "clarion:eid:0123456789abcdef0123456789abcdef" }) - .to_string(); + let sei_body = + serde_json::json!({ "locator": "loomweave:eid:0123456789abcdef0123456789abcdef" }) + .to_string(); let rejected = wait_for_http_post_json(&bind, "/api/v1/identity/resolve", &sei_body, &[]); let unknown_body = serde_json::json!({ "locator": "python:function:nope.absent" }).to_string(); @@ -475,7 +476,7 @@ fn serve_http_identity_resolve_rejects_sei_shaped_input_and_resolves_unknown() { /// caller/callee linkages, in a file with a content hash. This is the state the /// Wave 1 matcher produces (proven by the SEI conformance oracle + the /// production orphan test); the test below proves the Wardline dossier assembler -/// can read every slice it needs over Clarion's HTTP surface alone. Returns the +/// can read every slice it needs over Loomweave's HTTP surface alone. Returns the /// carried SEI. fn seed_renamed_function_dossier(project_root: &Path) -> String { let source_path = project_root.join("mod.py"); @@ -485,11 +486,11 @@ fn seed_renamed_function_dossier(project_root: &Path) -> String { .expect("canonical source path") .display() .to_string(); - let sei = "clarion:eid:0123456789abcdef0123456789abcdef".to_owned(); + let sei = "loomweave:eid:0123456789abcdef0123456789abcdef".to_owned(); let new_locator = "python:function:mod.process_v2"; let old_locator = "python:function:mod.process"; let ts = "2026-06-02T00:00:00.000Z"; - let db_path = project_root.join(".clarion/clarion.db"); + let db_path = project_root.join(".loomweave/loomweave.db"); let conn = Connection::open(&db_path).expect("open sqlite"); conn.execute( @@ -543,10 +544,10 @@ fn seed_renamed_function_dossier(project_root: &Path) -> String { fn serve_http_dossier_participation_surface_serves_a_renamed_function() { // WS4 core-paradise e2e (Wave 2 DoD): the Wardline dossier assembler can build // a complete, freshness-stamped, SEI-keyed view of a RENAMED function using - // ONLY Clarion's HTTP surface — SEI carried, facts not orphaned, freshness + // ONLY Loomweave's HTTP surface — SEI carried, facts not orphaned, freshness // stamped. Exercises every slice the participation spec pins. let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") @@ -654,7 +655,7 @@ fn serve_http_dossier_participation_surface_serves_a_renamed_function() { #[test] fn serve_http_files_rejects_unknown_query_fields() { let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") @@ -683,7 +684,7 @@ fn serve_http_files_rejects_unknown_query_fields() { #[test] fn serve_http_rejects_oversized_get_body_before_handler() { let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") @@ -704,7 +705,7 @@ fn serve_http_rejects_oversized_get_body_before_handler() { #[test] fn serve_http_files_path_traversal_returns_outside_project_envelope() { let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") @@ -730,7 +731,7 @@ fn serve_http_files_path_traversal_returns_outside_project_envelope() { #[test] fn serve_http_files_unknown_catalog_file_returns_not_found_envelope() { let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") @@ -755,7 +756,7 @@ fn serve_http_files_unknown_catalog_file_returns_not_found_envelope() { #[test] fn serve_http_files_storage_failure_returns_closed_error_without_raw_detail() { let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") @@ -770,7 +771,7 @@ fn serve_http_files_storage_failure_returns_closed_error_without_raw_detail() { .expect("canonical source path") .display() .to_string(); - let db_path = dir.path().join(".clarion/clarion.db"); + let db_path = dir.path().join(".loomweave/loomweave.db"); let conn = Connection::open(&db_path).expect("open sqlite"); conn.execute( "INSERT INTO entities ( @@ -825,14 +826,14 @@ fn serve_http_files_storage_failure_returns_closed_error_without_raw_detail() { #[test] fn serve_http_capabilities_and_mcp_stdio_coexist() { let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") .assert() .success(); fs::write( - dir.path().join(".clarion/instance_id"), + dir.path().join(".loomweave/instance_id"), format!("{STABLE_INSTANCE_ID}\n"), ) .expect("seed stable instance ID"); @@ -882,7 +883,7 @@ fn serve_http_capabilities_and_mcp_stdio_coexist() { stdin.flush().expect("flush initialize frame"); } drop(child.stdin.take()); - let output = child.wait_with_output().expect("wait for clarion serve"); + let output = child.wait_with_output().expect("wait for loomweave serve"); assert!( output.status.success(), @@ -896,19 +897,19 @@ fn serve_http_capabilities_and_mcp_stdio_coexist() { serde_json::from_slice(&frame.body).expect("response body is json"); assert_eq!(response["id"], 42); - assert_eq!(response["result"]["serverInfo"]["name"], "clarion"); + assert_eq!(response["result"]["serverInfo"]["name"], "loomweave"); } #[test] fn serve_http_capabilities_reuses_persisted_instance_id_across_restarts() { let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") .assert() .success(); - let instance_id_path = dir.path().join(".clarion/instance_id"); + let instance_id_path = dir.path().join(".loomweave/instance_id"); let first_bind = free_loopback_bind(); write_http_config(dir.path(), &first_bind); @@ -954,13 +955,13 @@ fn serve_http_capabilities_reuses_persisted_instance_id_across_restarts() { #[test] fn serve_http_capabilities_returns_new_instance_id_after_rotation() { let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") .assert() .success(); - let instance_id_path = dir.path().join(".clarion/instance_id"); + let instance_id_path = dir.path().join(".loomweave/instance_id"); let first_bind = free_loopback_bind(); write_http_config(dir.path(), &first_bind); @@ -1008,7 +1009,7 @@ fn serve_http_capabilities_returns_new_instance_id_after_rotation() { #[test] fn serve_http_capabilities_creates_instance_id_with_private_unix_mode() { let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") @@ -1022,7 +1023,7 @@ fn serve_http_capabilities_creates_instance_id_with_private_unix_mode() { .expect("HTTP /api/v1/_capabilities response"); stop_serve(&mut child); - let instance_id_path = dir.path().join(".clarion/instance_id"); + let instance_id_path = dir.path().join(".loomweave/instance_id"); assert_eq!( fs::read_to_string(&instance_id_path) .expect("read persisted instance_id") @@ -1040,13 +1041,13 @@ fn serve_http_capabilities_creates_instance_id_with_private_unix_mode() { #[test] fn serve_http_capabilities_repairs_existing_instance_id_mode() { let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") .assert() .success(); - let instance_id_path = dir.path().join(".clarion/instance_id"); + let instance_id_path = dir.path().join(".loomweave/instance_id"); let seeded_id = "9bd7234e-6d44-4a38-9ae4-76f912a10221"; fs::write(&instance_id_path, format!("{seeded_id}\n")).expect("seed instance ID"); fs::set_permissions(&instance_id_path, fs::Permissions::from_mode(0o644)) @@ -1071,13 +1072,13 @@ fn serve_http_capabilities_repairs_existing_instance_id_mode() { #[test] fn serve_rejects_invalid_instance_id_before_serving_http() { let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") .assert() .success(); - fs::write(dir.path().join(".clarion/instance_id"), "not-a-uuid\n") + fs::write(dir.path().join(".loomweave/instance_id"), "not-a-uuid\n") .expect("write invalid instance ID"); let bind = free_loopback_bind(); write_http_config(dir.path(), &bind); @@ -1089,7 +1090,7 @@ fn serve_rejects_invalid_instance_id_before_serving_http() { assert!(!output.status.success()); let stderr = String::from_utf8_lossy(&output.stderr); assert!( - stderr.contains("invalid Clarion instance ID"), + stderr.contains("invalid Loomweave instance ID"), "unexpected stderr: {stderr}" ); } @@ -1097,7 +1098,7 @@ fn serve_rejects_invalid_instance_id_before_serving_http() { #[test] fn serve_http_batch_endpoint_resolves_mixed_paths() { let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") @@ -1161,7 +1162,7 @@ fn serve_http_batch_endpoint_resolves_mixed_paths() { #[test] fn serve_http_files_resolve_endpoint_returns_per_path_results() { let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") @@ -1227,7 +1228,7 @@ fn serve_http_files_resolve_endpoint_returns_per_path_results() { #[test] fn serve_http_files_resolve_rejects_over_1000_paths() { let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") @@ -1262,7 +1263,7 @@ fn serve_http_files_resolve_rejects_over_1000_paths() { #[test] fn serve_http_batch_rejects_over_256_queries() { let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") @@ -1299,7 +1300,7 @@ fn serve_http_batch_rejects_over_256_queries() { #[test] fn serve_http_batch_requires_auth_when_configured() { let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") @@ -1307,12 +1308,9 @@ fn serve_http_batch_requires_auth_when_configured() { .success(); seed_file_entity(dir.path()); let bind = free_loopback_bind(); - write_http_config_with_token_env(dir.path(), &bind, "CLARION_TEST_LOOM_TOKEN_BATCH"); + write_http_config_with_token_env(dir.path(), &bind, "WEFT_TEST_TOKEN_BATCH"); - let mut child = spawn_serve_with_env( - dir.path(), - &[("CLARION_TEST_LOOM_TOKEN_BATCH", "batch-secret")], - ); + let mut child = spawn_serve_with_env(dir.path(), &[("WEFT_TEST_TOKEN_BATCH", "batch-secret")]); let body = serde_json::json!({ "queries": [{"path": "demo.py", "language": "python"}] }) @@ -1341,7 +1339,7 @@ fn serve_http_batch_requires_auth_when_configured() { #[test] fn serve_http_files_endpoint_requires_bearer_token_when_configured() { let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") @@ -1349,12 +1347,10 @@ fn serve_http_files_endpoint_requires_bearer_token_when_configured() { .success(); seed_file_entity(dir.path()); let bind = free_loopback_bind(); - write_http_config_with_token_env(dir.path(), &bind, "CLARION_TEST_LOOM_TOKEN_REQ"); + write_http_config_with_token_env(dir.path(), &bind, "WEFT_TEST_TOKEN_REQ"); - let mut child = spawn_serve_with_env( - dir.path(), - &[("CLARION_TEST_LOOM_TOKEN_REQ", "shh-its-a-secret")], - ); + let mut child = + spawn_serve_with_env(dir.path(), &[("WEFT_TEST_TOKEN_REQ", "shh-its-a-secret")]); let unauthenticated = wait_for_http_response(&bind, "/api/v1/files?path=demo.py&language=python"); let authenticated = wait_for_http_raw_response( @@ -1377,7 +1373,7 @@ fn serve_http_files_endpoint_requires_bearer_token_when_configured() { #[test] fn serve_http_files_endpoint_rejects_wrong_token() { let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") @@ -1385,12 +1381,9 @@ fn serve_http_files_endpoint_rejects_wrong_token() { .success(); seed_file_entity(dir.path()); let bind = free_loopback_bind(); - write_http_config_with_token_env(dir.path(), &bind, "CLARION_TEST_LOOM_TOKEN_WRONG"); + write_http_config_with_token_env(dir.path(), &bind, "WEFT_TEST_TOKEN_WRONG"); - let mut child = spawn_serve_with_env( - dir.path(), - &[("CLARION_TEST_LOOM_TOKEN_WRONG", "correct-horse")], - ); + let mut child = spawn_serve_with_env(dir.path(), &[("WEFT_TEST_TOKEN_WRONG", "correct-horse")]); let wrong = wait_for_http_raw_response( &bind, "/api/v1/files?path=demo.py&language=python", @@ -1426,7 +1419,7 @@ fn serve_http_files_endpoint_rejects_wrong_token() { #[test] fn serve_http_files_endpoint_requires_hmac_identity_when_configured() { let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") @@ -1434,12 +1427,10 @@ fn serve_http_files_endpoint_requires_hmac_identity_when_configured() { .success(); seed_file_entity(dir.path()); let bind = free_loopback_bind(); - write_http_config_with_identity_token_env(dir.path(), &bind, "CLARION_TEST_LOOM_IDENTITY_REQ"); + write_http_config_with_identity_token_env(dir.path(), &bind, "WEFT_TEST_IDENTITY_REQ"); - let mut child = spawn_serve_with_env( - dir.path(), - &[("CLARION_TEST_LOOM_IDENTITY_REQ", "shared-secret")], - ); + let mut child = + spawn_serve_with_env(dir.path(), &[("WEFT_TEST_IDENTITY_REQ", "shared-secret")]); let path = "/api/v1/files?path=demo.py&language=python"; let missing = wait_for_http_raw_response(&bind, path, &[]); let (signed_header, signed_timestamp, signed_nonce) = @@ -1448,9 +1439,9 @@ fn serve_http_files_endpoint_requires_hmac_identity_when_configured() { &bind, path, &[ - ("X-Loom-Component", &signed_header), - ("X-Loom-Timestamp", &signed_timestamp), - ("X-Loom-Nonce", &signed_nonce), + ("X-Weft-Component", &signed_header), + ("X-Weft-Timestamp", &signed_timestamp), + ("X-Weft-Nonce", &signed_nonce), ], ); stop_serve(&mut child); @@ -1469,7 +1460,7 @@ fn serve_http_files_endpoint_requires_hmac_identity_when_configured() { #[test] fn serve_http_files_endpoint_rejects_wrong_hmac_identity() { let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") @@ -1477,16 +1468,10 @@ fn serve_http_files_endpoint_rejects_wrong_hmac_identity() { .success(); seed_file_entity(dir.path()); let bind = free_loopback_bind(); - write_http_config_with_identity_token_env( - dir.path(), - &bind, - "CLARION_TEST_LOOM_IDENTITY_WRONG", - ); + write_http_config_with_identity_token_env(dir.path(), &bind, "WEFT_TEST_IDENTITY_WRONG"); - let mut child = spawn_serve_with_env( - dir.path(), - &[("CLARION_TEST_LOOM_IDENTITY_WRONG", "shared-secret")], - ); + let mut child = + spawn_serve_with_env(dir.path(), &[("WEFT_TEST_IDENTITY_WRONG", "shared-secret")]); let path = "/api/v1/files?path=demo.py&language=python"; let (wrong_header, wrong_timestamp, wrong_nonce) = hmac_component_headers("other-secret", "GET", path, b""); @@ -1494,9 +1479,9 @@ fn serve_http_files_endpoint_rejects_wrong_hmac_identity() { &bind, path, &[ - ("X-Loom-Component", &wrong_header), - ("X-Loom-Timestamp", &wrong_timestamp), - ("X-Loom-Nonce", &wrong_nonce), + ("X-Weft-Component", &wrong_header), + ("X-Weft-Timestamp", &wrong_timestamp), + ("X-Weft-Nonce", &wrong_nonce), ], ); stop_serve(&mut child); @@ -1510,19 +1495,17 @@ fn serve_http_files_endpoint_rejects_wrong_hmac_identity() { #[test] fn serve_http_capabilities_does_not_require_token() { let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") .assert() .success(); let bind = free_loopback_bind(); - write_http_config_with_token_env(dir.path(), &bind, "CLARION_TEST_LOOM_TOKEN_CAPS"); + write_http_config_with_token_env(dir.path(), &bind, "WEFT_TEST_TOKEN_CAPS"); - let mut child = spawn_serve_with_env( - dir.path(), - &[("CLARION_TEST_LOOM_TOKEN_CAPS", "any-token-value")], - ); + let mut child = + spawn_serve_with_env(dir.path(), &[("WEFT_TEST_TOKEN_CAPS", "any-token-value")]); let response = wait_for_http_response(&bind, "/api/v1/_capabilities"); stop_serve(&mut child); let response = response.expect("capabilities probe response"); @@ -1535,18 +1518,18 @@ fn serve_http_capabilities_does_not_require_token() { #[test] fn serve_http_refuses_startup_on_non_loopback_without_token() { let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") .assert() .success(); // Non-loopback bind + allow_non_loopback opt-in + token_env unset - // should refuse to start with CLA-CONFIG-HTTP-NO-AUTH. + // should refuse to start with LMWV-CONFIG-HTTP-NO-AUTH. fs::write( - dir.path().join("clarion.yaml"), + dir.path().join("loomweave.yaml"), "version: 1\nserve:\n http:\n enabled: true\n bind: \"0.0.0.0:0\"\n \ - allow_non_loopback: true\n token_env: \"CLARION_TEST_LOOM_TOKEN_REFUSE\"\n", + allow_non_loopback: true\n token_env: \"WEFT_TEST_TOKEN_REFUSE\"\n", ) .expect("write non-loopback HTTP config without token env"); @@ -1557,11 +1540,11 @@ fn serve_http_refuses_startup_on_non_loopback_without_token() { assert!(!output.status.success()); let stderr = String::from_utf8_lossy(&output.stderr); assert!( - stderr.contains("CLA-CONFIG-HTTP-NO-AUTH"), - "error should cite CLA-CONFIG-HTTP-NO-AUTH: {stderr}" + stderr.contains("LMWV-CONFIG-HTTP-NO-AUTH"), + "error should cite LMWV-CONFIG-HTTP-NO-AUTH: {stderr}" ); assert!( - stderr.contains("CLARION_TEST_LOOM_TOKEN_REFUSE"), + stderr.contains("WEFT_TEST_TOKEN_REFUSE"), "error should name the configured token_env: {stderr}" ); } @@ -1569,18 +1552,14 @@ fn serve_http_refuses_startup_on_non_loopback_without_token() { #[test] fn serve_http_refuses_startup_when_identity_env_is_missing() { let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") .assert() .success(); let bind = free_loopback_bind(); - write_http_config_with_identity_token_env( - dir.path(), - &bind, - "CLARION_TEST_LOOM_IDENTITY_MISSING", - ); + write_http_config_with_identity_token_env(dir.path(), &bind, "WEFT_TEST_IDENTITY_MISSING"); let child = spawn_serve_with_env(dir.path(), &[]); let output = wait_for_child_exit(child, Duration::from_secs(2)) @@ -1589,11 +1568,11 @@ fn serve_http_refuses_startup_when_identity_env_is_missing() { assert!(!output.status.success()); let stderr = String::from_utf8_lossy(&output.stderr); assert!( - stderr.contains("CLA-CONFIG-HTTP-IDENTITY-MISSING"), - "error should cite CLA-CONFIG-HTTP-IDENTITY-MISSING: {stderr}" + stderr.contains("LMWV-CONFIG-HTTP-IDENTITY-MISSING"), + "error should cite LMWV-CONFIG-HTTP-IDENTITY-MISSING: {stderr}" ); assert!( - stderr.contains("CLARION_TEST_LOOM_IDENTITY_MISSING"), + stderr.contains("WEFT_TEST_IDENTITY_MISSING"), "error should name the configured identity_token_env: {stderr}" ); } @@ -1601,14 +1580,14 @@ fn serve_http_refuses_startup_when_identity_env_is_missing() { #[test] fn serve_rejects_non_loopback_http_bind_before_binding_without_opt_in() { let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") .assert() .success(); fs::write( - dir.path().join("clarion.yaml"), + dir.path().join("loomweave.yaml"), "version: 1\nserve:\n http:\n enabled: true\n bind: \"0.0.0.0:0\"\n", ) .expect("write non-loopback HTTP config"); @@ -1632,19 +1611,19 @@ fn serve_rejects_non_loopback_http_bind_before_binding_without_opt_in() { #[test] fn serve_rejects_invalid_project_config() { let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") .assert() .success(); fs::write( - dir.path().join("clarion.yaml"), + dir.path().join("loomweave.yaml"), "llm: [not valid for this schema]\n", ) .expect("write invalid config"); - let assert = clarion_bin() + let assert = loomweave_bin() .args(["serve", "--path"]) .arg(dir.path()) .assert() @@ -1657,7 +1636,7 @@ fn serve_rejects_invalid_project_config() { #[test] fn serve_wires_recording_llm_provider_and_writer_for_cached_summary_touches() { let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") @@ -1665,7 +1644,7 @@ fn serve_wires_recording_llm_provider_and_writer_for_cached_summary_touches() { .success(); let source_path = dir.path().join("demo.py"); fs::write(&source_path, "def entry():\n return 1\n").expect("write source"); - let db_path = dir.path().join(".clarion/clarion.db"); + let db_path = dir.path().join(".loomweave/loomweave.db"); let conn = Connection::open(&db_path).expect("open sqlite"); conn.execute( "INSERT INTO entities ( @@ -1696,19 +1675,19 @@ fn serve_wires_recording_llm_provider_and_writer_for_cached_summary_touches() { .expect("insert summary cache"); drop(conn); fs::write( - dir.path().join("clarion.yaml"), + dir.path().join("loomweave.yaml"), "llm:\n enabled: true\n provider: recording\nserve:\n mcp:\n enable_write_tools: true\n", ) .expect("write config"); - let mut child = StdCommand::new(assert_cmd::cargo::cargo_bin("clarion")) + let mut child = StdCommand::new(assert_cmd::cargo::cargo_bin("loomweave")) .args(["serve", "--path"]) .arg(dir.path()) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() - .expect("spawn clarion serve"); + .expect("spawn loomweave serve"); { let mut stdin = child.stdin.take().expect("child stdin"); write_frame( @@ -1730,7 +1709,7 @@ fn serve_wires_recording_llm_provider_and_writer_for_cached_summary_touches() { stdin.flush().expect("flush summary frame"); } - let output = child.wait_with_output().expect("wait for clarion serve"); + let output = child.wait_with_output().expect("wait for loomweave serve"); assert!( output.status.success(), "serve failed: {}", @@ -1763,7 +1742,7 @@ fn serve_wires_recording_llm_provider_and_writer_for_cached_summary_touches() { #[test] fn serve_routes_summary_miss_through_codex_cli_provider() { let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") @@ -1804,8 +1783,8 @@ done stdin_prompt="$(cat)" printf '%s' "$stdin_prompt" > "{prompt_log}" case "$stdin_prompt" in - *"Prompt contract: clarion-agent-provider-v1"*"Do not inspect additional files"*"Source excerpt:"*) ;; - *) echo "missing Clarion agent prompt contract" >&2; exit 32 ;; + *"Prompt contract: loomweave-agent-provider-v1"*"Do not inspect additional files"*"Source excerpt:"*) ;; + *) echo "missing Loomweave agent prompt contract" >&2; exit 32 ;; esac grep -q '"purpose"' "$schema" printf '%s\n' '{{"usage":{{"input_tokens":31,"cached_input_tokens":9,"output_tokens":7,"total_tokens":38}}}}' @@ -1840,14 +1819,14 @@ printf '%s' '{{"purpose":"via codex serve","behavior":"served through fake Codex assert!( fs::read_to_string(prompt_log) .expect("read Codex prompt log") - .contains("Prompt contract: clarion-agent-provider-v1") + .contains("Prompt contract: loomweave-agent-provider-v1") ); } #[test] fn serve_routes_summary_miss_through_claude_cli_provider() { let dir = tempfile::tempdir().expect("temp project"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") @@ -1888,12 +1867,12 @@ done stdin_prompt="$(cat)" printf '%s\n%s' "$print_prompt" "$stdin_prompt" > "{prompt_log}" case "$print_prompt" in - *"Clarion's local Claude Code LLM provider"*) ;; + *"Loomweave's local Claude Code LLM provider"*) ;; *) echo "missing Claude print prompt" >&2; exit 41 ;; esac case "$stdin_prompt" in - *"Prompt contract: clarion-agent-provider-v1"*"Do not inspect additional files"*"Source excerpt:"*) ;; - *) echo "missing Clarion agent prompt contract" >&2; exit 42 ;; + *"Prompt contract: loomweave-agent-provider-v1"*"Do not inspect additional files"*"Source excerpt:"*) ;; + *) echo "missing Loomweave agent prompt contract" >&2; exit 42 ;; esac case "$schema" in *'"purpose"'*'"behavior"'*) ;; @@ -1933,7 +1912,7 @@ printf '%s\n' '{{"type":"result","subtype":"success","structured_output":{{"purp assert!( fs::read_to_string(prompt_log) .expect("read Claude prompt log") - .contains("Clarion's local Claude Code LLM provider") + .contains("Loomweave's local Claude Code LLM provider") ); } @@ -1948,7 +1927,7 @@ fn seed_summary_entity(project_root: &Path) { let source_path = project_root.join("demo.py"); fs::write(&source_path, source).expect("write source"); let content_hash = line_range_content_hash(source, 1, 2); - let db_path = project_root.join(".clarion/clarion.db"); + let db_path = project_root.join(".loomweave/loomweave.db"); let conn = Connection::open(&db_path).expect("open sqlite"); conn.execute( "INSERT INTO entities ( @@ -1986,7 +1965,7 @@ fn write_provider_config( .trim_start_matches('\n') .replace("\"__EXECUTABLE__\"", &executable_yaml); fs::write( - project_root.join("clarion.yaml"), + project_root.join("loomweave.yaml"), format!( concat!( "version: 1\n", @@ -2011,14 +1990,14 @@ fn write_provider_config( } fn call_summary_through_serve(project_root: &Path) -> Value { - let mut child = StdCommand::new(assert_cmd::cargo::cargo_bin("clarion")) + let mut child = StdCommand::new(assert_cmd::cargo::cargo_bin("loomweave")) .args(["serve", "--path"]) .arg(project_root) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() - .expect("spawn clarion serve"); + .expect("spawn loomweave serve"); { let mut stdin = child.stdin.take().expect("child stdin"); write_frame( @@ -2040,12 +2019,12 @@ fn call_summary_through_serve(project_root: &Path) -> Value { stdin.flush().expect("flush summary frame"); } - let output = child.wait_with_output().expect("wait for clarion serve"); - let config_debug = fs::read_to_string(project_root.join("clarion.yaml")) - .unwrap_or_else(|err| format!("failed to read clarion.yaml: {err}")); + let output = child.wait_with_output().expect("wait for loomweave serve"); + let config_debug = fs::read_to_string(project_root.join("loomweave.yaml")) + .unwrap_or_else(|err| format!("failed to read loomweave.yaml: {err}")); assert!( output.status.success(), - "serve failed: {}\nclarion.yaml:\n{}", + "serve failed: {}\nloomweave.yaml:\n{}", String::from_utf8_lossy(&output.stderr), config_debug ); @@ -2485,7 +2464,7 @@ fn seed_file_entity(project_root: &Path) -> (String, String, String) { .to_string(); let content_hash = "hash-demo-file".to_owned(); let file_id = "core:file:demo.py".to_owned(); - let db_path = project_root.join(".clarion/clarion.db"); + let db_path = project_root.join(".loomweave/loomweave.db"); let conn = Connection::open(&db_path).expect("open sqlite"); conn.execute( "INSERT INTO entities ( @@ -2510,7 +2489,7 @@ fn seed_custom_language_file_entity(project_root: &Path) { .expect("canonical source path") .display() .to_string(); - let db_path = project_root.join(".clarion/clarion.db"); + let db_path = project_root.join(".loomweave/loomweave.db"); let conn = Connection::open(&db_path).expect("open sqlite"); conn.execute( "INSERT INTO entities ( @@ -2534,7 +2513,7 @@ fn seed_briefing_blocked_file_entity(project_root: &Path) { .expect("canonical blocked path") .display() .to_string(); - let db_path = project_root.join(".clarion/clarion.db"); + let db_path = project_root.join(".loomweave/loomweave.db"); let conn = Connection::open(&db_path).expect("open sqlite"); conn.execute( "INSERT INTO entities ( @@ -2561,7 +2540,7 @@ fn seed_storage_failure_file_entity(project_root: &Path) { .expect("canonical source path") .display() .to_string(); - let db_path = project_root.join(".clarion/clarion.db"); + let db_path = project_root.join(".loomweave/loomweave.db"); let conn = Connection::open(&db_path).expect("open sqlite"); conn.execute( "INSERT INTO entities ( @@ -2624,14 +2603,14 @@ fn release_reserved_loopback_bind(project_root: &Path) { } fn configured_http_bind(project_root: &Path) -> Option { - let raw = fs::read_to_string(project_root.join("clarion.yaml")).ok()?; + let raw = fs::read_to_string(project_root.join("loomweave.yaml")).ok()?; let parsed: ServeTestConfig = serde_norway::from_str(&raw).ok()?; parsed.serve.http.bind } fn write_stdio_config(project_root: &Path) { fs::write( - project_root.join("clarion.yaml"), + project_root.join("loomweave.yaml"), "version: 1\nserve:\n http:\n enabled: false\n", ) .expect("write MCP stdio-only serve config"); @@ -2639,7 +2618,7 @@ fn write_stdio_config(project_root: &Path) { fn write_http_config(project_root: &Path, bind: &str) { fs::write( - project_root.join("clarion.yaml"), + project_root.join("loomweave.yaml"), format!("version: 1\nserve:\n http:\n enabled: true\n bind: \"{bind}\"\n"), ) .expect("write HTTP serve config"); @@ -2647,7 +2626,7 @@ fn write_http_config(project_root: &Path, bind: &str) { fn write_http_config_with_token_env(project_root: &Path, bind: &str, token_env: &str) { fs::write( - project_root.join("clarion.yaml"), + project_root.join("loomweave.yaml"), format!( "version: 1\nserve:\n http:\n enabled: true\n bind: \"{bind}\"\n token_env: \"{token_env}\"\n" ), @@ -2657,7 +2636,7 @@ fn write_http_config_with_token_env(project_root: &Path, bind: &str, token_env: fn write_http_config_with_identity_token_env(project_root: &Path, bind: &str, token_env: &str) { fs::write( - project_root.join("clarion.yaml"), + project_root.join("loomweave.yaml"), format!( "version: 1\nserve:\n http:\n enabled: true\n bind: \"{bind}\"\n identity_token_env: \"{token_env}\"\n" ), @@ -2693,7 +2672,7 @@ fn hmac_component_header_with_freshness( nonce: &str, ) -> String { format!( - "clarion:{}", + "loomweave:{}", hmac_sha256_hex( secret.as_bytes(), canonical_hmac_message(method, path_and_query, body, timestamp, nonce).as_bytes() @@ -2783,7 +2762,7 @@ fn spawn_serve(project_root: &Path) -> ServeChild { fn spawn_serve_with_env(project_root: &Path, env: &[(&str, &str)]) -> ServeChild { release_reserved_loopback_bind(project_root); - let mut command = StdCommand::new(assert_cmd::cargo::cargo_bin("clarion")); + let mut command = StdCommand::new(assert_cmd::cargo::cargo_bin("loomweave")); command .args(["serve", "--path"]) .arg(project_root) @@ -2793,7 +2772,7 @@ fn spawn_serve_with_env(project_root: &Path, env: &[(&str, &str)]) -> ServeChild for (key, value) in env { command.env(key, value); } - ServeChild::new(command.spawn().expect("spawn clarion serve")) + ServeChild::new(command.spawn().expect("spawn loomweave serve")) } fn stop_serve(child: &mut ServeChild) { diff --git a/crates/clarion-cli/tests/skills.rs b/crates/loomweave-cli/tests/skills.rs similarity index 69% rename from crates/clarion-cli/tests/skills.rs rename to crates/loomweave-cli/tests/skills.rs index f09505f9..e01fc549 100644 --- a/crates/clarion-cli/tests/skills.rs +++ b/crates/loomweave-cli/tests/skills.rs @@ -1,15 +1,15 @@ -//! `clarion install --skills/--codex-skills/--hooks/--all` integration tests. +//! `loomweave install --skills/--codex-skills/--hooks/--all` integration tests. use std::fs; use assert_cmd::Command; -fn clarion_bin() -> Command { - let mut cmd = Command::cargo_bin("clarion").expect("clarion binary"); +fn loomweave_bin() -> Command { + let mut cmd = Command::cargo_bin("loomweave").expect("loomweave binary"); cmd.env( - "CLARION_CODEX_CONFIG", + "LOOMWEAVE_CODEX_CONFIG", std::env::temp_dir().join(format!( - "clarion-test-codex-config-{}.toml", + "loomweave-test-codex-config-{}.toml", std::process::id() )), ); @@ -17,9 +17,9 @@ fn clarion_bin() -> Command { } #[test] -fn install_skills_writes_claude_pack_without_initialising_clarion_dir() { +fn install_skills_writes_claude_pack_without_initialising_loomweave_dir() { let dir = tempfile::tempdir().unwrap(); - clarion_bin() + loomweave_bin() .args(["install", "--skills", "--path"]) .arg(dir.path()) .assert() @@ -27,27 +27,27 @@ fn install_skills_writes_claude_pack_without_initialising_clarion_dir() { assert!( dir.path() - .join(".claude/skills/clarion-workflow/SKILL.md") + .join(".claude/skills/loomweave-workflow/SKILL.md") .exists(), "skill not installed under .claude" ); assert!( !dir.path() - .join(".agents/skills/clarion-workflow/SKILL.md") + .join(".agents/skills/loomweave-workflow/SKILL.md") .exists(), "--skills should not install Codex skills under .agents" ); - // --skills MUST NOT init .clarion/. + // --skills MUST NOT init .loomweave/. assert!( - !dir.path().join(".clarion").exists(), - "--skills should not create .clarion/" + !dir.path().join(".loomweave").exists(), + "--skills should not create .loomweave/" ); } #[test] -fn install_codex_skills_writes_agents_pack_without_initialising_clarion_dir() { +fn install_codex_skills_writes_agents_pack_without_initialising_loomweave_dir() { let dir = tempfile::tempdir().unwrap(); - clarion_bin() + loomweave_bin() .args(["install", "--codex-skills", "--path"]) .arg(dir.path()) .assert() @@ -55,19 +55,19 @@ fn install_codex_skills_writes_agents_pack_without_initialising_clarion_dir() { assert!( !dir.path() - .join(".claude/skills/clarion-workflow/SKILL.md") + .join(".claude/skills/loomweave-workflow/SKILL.md") .exists(), "--codex-skills should not install Claude skills under .claude" ); assert!( dir.path() - .join(".agents/skills/clarion-workflow/SKILL.md") + .join(".agents/skills/loomweave-workflow/SKILL.md") .exists(), "Codex skill not installed under .agents" ); assert!( - !dir.path().join(".clarion").exists(), - "--codex-skills should not create .clarion/" + !dir.path().join(".loomweave").exists(), + "--codex-skills should not create .loomweave/" ); } @@ -75,15 +75,18 @@ fn install_codex_skills_writes_agents_pack_without_initialising_clarion_dir() { fn install_skills_is_idempotent() { let dir = tempfile::tempdir().unwrap(); for _ in 0..2 { - clarion_bin() + loomweave_bin() .args(["install", "--skills", "--path"]) .arg(dir.path()) .assert() .success(); } - let body = - fs::read_to_string(dir.path().join(".claude/skills/clarion-workflow/SKILL.md")).unwrap(); - assert!(body.contains("name: clarion-workflow")); + let body = fs::read_to_string( + dir.path() + .join(".claude/skills/loomweave-workflow/SKILL.md"), + ) + .unwrap(); + assert!(body.contains("name: loomweave-workflow")); } #[test] @@ -97,7 +100,7 @@ fn install_hooks_merges_session_start_without_clobbering() { ) .unwrap(); - clarion_bin() + loomweave_bin() .args(["install", "--hooks", "--path"]) .arg(dir.path()) .assert() @@ -119,38 +122,41 @@ fn install_hooks_merges_session_start_without_clobbering() { .collect(); assert!( cmds.iter() - .any(|c| c.contains("clarion hook session-start")) + .any(|c| c.contains("loomweave hook session-start")) ); - assert!(!dir.path().join(".clarion").exists()); + assert!(!dir.path().join(".loomweave").exists()); } #[test] fn install_all_does_init_skills_and_hooks() { let dir = tempfile::tempdir().unwrap(); - clarion_bin() + loomweave_bin() .args(["install", "--all", "--path"]) .arg(dir.path()) .assert() .success(); - assert!(dir.path().join(".clarion/clarion.db").exists(), "no db"); + assert!(dir.path().join(".loomweave/loomweave.db").exists(), "no db"); assert!( dir.path() - .join(".claude/skills/clarion-workflow/SKILL.md") + .join(".claude/skills/loomweave-workflow/SKILL.md") .exists(), "no Claude skill" ); assert!( dir.path() - .join(".agents/skills/clarion-workflow/SKILL.md") + .join(".agents/skills/loomweave-workflow/SKILL.md") .exists(), "no Codex skill" ); let raw = fs::read_to_string(dir.path().join(".claude/settings.json")).unwrap(); - assert!(raw.contains("clarion hook session-start"), "no hook: {raw}"); + assert!( + raw.contains("loomweave hook session-start"), + "no hook: {raw}" + ); let mcp_raw = fs::read_to_string(dir.path().join(".mcp.json")).unwrap(); assert!( - mcp_raw.contains("\"clarion\""), + mcp_raw.contains("\"loomweave\""), "no Claude Code MCP entry: {mcp_raw}" ); } @@ -159,18 +165,18 @@ fn install_all_does_init_skills_and_hooks() { fn install_all_is_rerunnable_and_preserves_index() { let dir = tempfile::tempdir().unwrap(); // First --all: full setup. - clarion_bin() + loomweave_bin() .args(["install", "--all", "--path"]) .arg(dir.path()) .assert() .success(); - let db = dir.path().join(".clarion/clarion.db"); + let db = dir.path().join(".loomweave/loomweave.db"); assert!(db.exists(), "first --all did not create db"); // Mark the db so we can prove the second run did NOT recreate it. let before = std::fs::metadata(&db).unwrap().modified().unwrap(); // Second --all: must succeed (not bail), keep the index, re-apply skills/hooks. - clarion_bin() + loomweave_bin() .args(["install", "--all", "--path"]) .arg(dir.path()) .assert() @@ -183,13 +189,13 @@ fn install_all_is_rerunnable_and_preserves_index() { ); assert!( dir.path() - .join(".claude/skills/clarion-workflow/SKILL.md") + .join(".claude/skills/loomweave-workflow/SKILL.md") .exists(), "skill missing after rerun" ); let raw = std::fs::read_to_string(dir.path().join(".claude/settings.json")).unwrap(); assert!( - raw.contains("clarion hook session-start"), + raw.contains("loomweave hook session-start"), "hook missing after rerun" ); } diff --git a/crates/clarion-cli/tests/wp1_e2e.rs b/crates/loomweave-cli/tests/wp1_e2e.rs similarity index 71% rename from crates/clarion-cli/tests/wp1_e2e.rs rename to crates/loomweave-cli/tests/wp1_e2e.rs index 238aaf1d..e1e52624 100644 --- a/crates/clarion-cli/tests/wp1_e2e.rs +++ b/crates/loomweave-cli/tests/wp1_e2e.rs @@ -6,12 +6,12 @@ use assert_cmd::Command; use rusqlite::Connection; -fn clarion_bin() -> Command { - let mut cmd = Command::cargo_bin("clarion").expect("clarion binary"); +fn loomweave_bin() -> Command { + let mut cmd = Command::cargo_bin("loomweave").expect("loomweave binary"); cmd.env( - "CLARION_CODEX_CONFIG", + "LOOMWEAVE_CODEX_CONFIG", std::env::temp_dir().join(format!( - "clarion-test-codex-config-{}.toml", + "loomweave-test-codex-config-{}.toml", std::process::id() )), ); @@ -22,7 +22,7 @@ fn clarion_bin() -> Command { fn wp1_walking_skeleton_end_to_end() { let dir = tempfile::tempdir().unwrap(); - // Scrub PATH on every clarion invocation. The runner's PATH almost + // Scrub PATH on every loomweave invocation. The runner's PATH almost // always contains world-writable directories (`/usr/local/bin`, // `/opt/pipx_bin`, …) which trip WP2 scrub commit `7c0e396`'s // refusal during plugin discovery; an empty PATH guarantees the @@ -30,22 +30,22 @@ fn wp1_walking_skeleton_end_to_end() { // `tests/analyze.rs::analyze_without_plugins_writes_skipped_run_row` // (scrub commit `ad054bd`). - // Step 1: clarion install - clarion_bin() + // Step 1: loomweave install + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .env("PATH", "") .assert() .success(); - let clarion_dir = dir.path().join(".clarion"); - assert!(clarion_dir.join("clarion.db").exists()); - assert!(clarion_dir.join("config.json").exists()); - assert!(clarion_dir.join(".gitignore").exists()); - assert!(dir.path().join("clarion.yaml").exists()); + let loomweave_dir = dir.path().join(".loomweave"); + assert!(loomweave_dir.join("loomweave.db").exists()); + assert!(loomweave_dir.join("config.json").exists()); + assert!(loomweave_dir.join(".gitignore").exists()); + assert!(dir.path().join("loomweave.yaml").exists()); - // Step 2: clarion analyze (no plugins yet — WP2 wires them) - clarion_bin() + // Step 2: loomweave analyze (no plugins yet — WP2 wires them) + loomweave_bin() .args(["analyze"]) .arg(dir.path()) .env("PATH", "") @@ -53,7 +53,7 @@ fn wp1_walking_skeleton_end_to_end() { .success(); // Step 3: verify expected shape in the DB. - let conn = Connection::open(clarion_dir.join("clarion.db")).unwrap(); + let conn = Connection::open(loomweave_dir.join("loomweave.db")).unwrap(); let migration_version: i64 = conn .query_row("SELECT MAX(version) FROM schema_migrations", [], |row| { @@ -62,7 +62,7 @@ fn wp1_walking_skeleton_end_to_end() { .unwrap(); assert_eq!( migration_version, - i64::from(clarion_storage::schema::CURRENT_SCHEMA_VERSION), + i64::from(loomweave_storage::schema::CURRENT_SCHEMA_VERSION), "schema not on the latest migration" ); diff --git a/crates/clarion-cli/tests/wp2_e2e.rs b/crates/loomweave-cli/tests/wp2_e2e.rs similarity index 87% rename from crates/clarion-cli/tests/wp2_e2e.rs rename to crates/loomweave-cli/tests/wp2_e2e.rs index 886c1008..596fd2a1 100644 --- a/crates/clarion-cli/tests/wp2_e2e.rs +++ b/crates/loomweave-cli/tests/wp2_e2e.rs @@ -3,11 +3,11 @@ //! Proves signoff A.2.8: the full Sprint 1 walking-skeleton pipeline works. //! //! Scenario: -//! 1. `clarion install` initialises `.clarion/clarion.db`. -//! 2. A `clarion-plugin-fixture` binary is placed on a synthetic `$PATH` +//! 1. `loomweave install` initialises `.loomweave/loomweave.db`. +//! 2. A `loomweave-plugin-fixture` binary is placed on a synthetic `$PATH` //! alongside its `plugin.toml` (neighbour-discovery convention, L9). //! 3. A single source file `demo.mt` is created in the project root. -//! 4. `clarion analyze` discovers the fixture plugin, spawns it, +//! 4. `loomweave analyze` discovers the fixture plugin, spawns it, //! handshakes, calls `analyze_file` once, receives one entity, and //! persists it to the `entities` table. //! @@ -24,35 +24,40 @@ use assert_cmd::Command; // it from tripping `-D warnings` as unused on non-Linux targets // (clarion-12667da9f5). #[cfg(target_os = "linux")] -use clarion_core::plugin::limits::FINDING_OOM_KILLED; +use loomweave_core::plugin::limits::FINDING_OOM_KILLED; use rusqlite::Connection; use tempfile::TempDir; -fn clarion_bin() -> Command { - let mut cmd = Command::cargo_bin("clarion").expect("clarion binary"); +fn loomweave_bin() -> Command { + let mut cmd = Command::cargo_bin("loomweave").expect("loomweave binary"); cmd.env( - "CLARION_CODEX_CONFIG", + "LOOMWEAVE_CODEX_CONFIG", std::env::temp_dir().join(format!( - "clarion-test-codex-config-{}.toml", + "loomweave-test-codex-config-{}.toml", std::process::id() )), ); cmd } -/// Locate the `clarion-plugin-fixture` binary. +/// Locate the `loomweave-fixture-plugin` binary. /// -/// Tries `CARGO_BIN_EXE_clarion-plugin-fixture` first (set by cargo nextest -/// when `clarion-plugin-fixture` appears in `[dev-dependencies]`). Falls back -/// to the standard `target/{debug,release}/` search. +/// The cargo artifact is named `loomweave-fixture-plugin` (off the +/// `loomweave-plugin-*` discovery glob — see the fixture crate's Cargo.toml); +/// `setup_plugin_dir` symlinks it back under the `loomweave-plugin-fixture` +/// glob name so discovery finds it on the synthetic `$PATH`. +/// +/// Tries `CARGO_BIN_EXE_loomweave-fixture-plugin` first (set by cargo nextest +/// when the fixture appears in `[dev-dependencies]`). Falls back to the +/// standard `target/{debug,release}/` search. fn fixture_binary_path() -> PathBuf { - if let Ok(path) = env::var("CARGO_BIN_EXE_clarion-plugin-fixture") { + if let Ok(path) = env::var("CARGO_BIN_EXE_loomweave-fixture-plugin") { return PathBuf::from(path); } // Fallback: search target/ relative to the workspace root. let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - // clarion-cli is at crates/clarion-cli; workspace root is ../../ + // loomweave-cli is at crates/loomweave-cli; workspace root is ../../ let workspace_root = manifest_dir .parent() // crates/ .and_then(|p| p.parent()) // workspace root @@ -62,14 +67,14 @@ fn fixture_binary_path() -> PathBuf { env::var("CARGO_TARGET_DIR").map_or_else(|_| workspace_root.join("target"), PathBuf::from); for profile in &["debug", "release"] { - let candidate = target_dir.join(profile).join("clarion-plugin-fixture"); + let candidate = target_dir.join(profile).join("loomweave-fixture-plugin"); if candidate.exists() { return candidate; } } panic!( - "clarion-plugin-fixture binary not found. \ + "loomweave-fixture-plugin binary not found. \ Run `cargo build --workspace` before running this test. \ Searched: {}", target_dir.display() @@ -77,7 +82,7 @@ fn fixture_binary_path() -> PathBuf { } /// Set up a synthetic `$PATH` directory containing: -/// - `clarion-plugin-fixture` executable (symlink to the real binary). +/// - `loomweave-plugin-fixture` executable (symlink to the real binary). /// - `plugin.toml` manifest (copied from the core test fixtures). /// /// Returns the temp dir (must stay alive for the duration of the test). @@ -85,8 +90,8 @@ fn setup_plugin_dir(fixture_bin: &PathBuf) -> TempDir { let plugin_dir = TempDir::new().expect("create plugin tempdir"); // Symlink the fixture binary into the dir under its expected name. - let dest = plugin_dir.path().join("clarion-plugin-fixture"); - std::os::unix::fs::symlink(fixture_bin, &dest).expect("symlink clarion-plugin-fixture"); + let dest = plugin_dir.path().join("loomweave-plugin-fixture"); + std::os::unix::fs::symlink(fixture_bin, &dest).expect("symlink loomweave-plugin-fixture"); // Verify the target is executable. let meta = fs::metadata(fixture_bin).expect("stat fixture binary"); @@ -99,7 +104,7 @@ fn setup_plugin_dir(fixture_bin: &PathBuf) -> TempDir { let toml_src = PathBuf::from(env!("CARGO_MANIFEST_DIR")) .parent() // crates/ .unwrap() - .join("clarion-core") + .join("loomweave-core") .join("tests") .join("fixtures") .join("plugin.toml"); @@ -113,16 +118,16 @@ fn setup_plugin_dir(fixture_bin: &PathBuf) -> TempDir { fn setup_oom_plugin_dir(fixture_bin: &PathBuf) -> TempDir { let plugin_dir = TempDir::new().expect("create oom plugin tempdir"); - let dest = plugin_dir.path().join("clarion-plugin-oom"); - std::os::unix::fs::symlink(fixture_bin, &dest).expect("symlink clarion-plugin-oom"); + let dest = plugin_dir.path().join("loomweave-plugin-oom"); + std::os::unix::fs::symlink(fixture_bin, &dest).expect("symlink loomweave-plugin-oom"); let manifest = r#" [plugin] -name = "clarion-plugin-oom" +name = "loomweave-plugin-oom" plugin_id = "oom" version = "0.1.0" protocol_version = "1.0" -executable = "clarion-plugin-oom" +executable = "loomweave-plugin-oom" language = "fixture" extensions = ["oom"] @@ -135,7 +140,7 @@ reads_outside_project_root = false [ontology] entity_kinds = ["widget"] edge_kinds = [] -rule_id_prefix = "CLA-OOM-" +rule_id_prefix = "LMWV-OOM-" ontology_version = "0.1.0" "#; fs::write(plugin_dir.path().join("plugin.toml"), manifest).expect("write oom plugin.toml"); @@ -154,8 +159,8 @@ fn wp2_e2e_smoke_fixture_plugin_round_trip() { // 3. Set up the project directory. let project_dir = TempDir::new().expect("create project tempdir"); - // 4. `clarion install` to initialise `.clarion/`. - clarion_bin() + // 4. `loomweave install` to initialise `.loomweave/`. + loomweave_bin() .args(["install", "--path"]) .arg(project_dir.path()) .assert() @@ -176,8 +181,8 @@ fn wp2_e2e_smoke_fixture_plugin_round_trip() { let new_path = env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).expect("join_paths"); - // 7. Run `clarion analyze` with the synthetic PATH. - clarion_bin() + // 7. Run `loomweave analyze` with the synthetic PATH. + loomweave_bin() .args(["analyze"]) .arg(project_dir.path()) .env("PATH", &new_path) @@ -185,7 +190,7 @@ fn wp2_e2e_smoke_fixture_plugin_round_trip() { .success(); // 8. Verify the database — full round-trip identity assertions. - let db_path = project_dir.path().join(".clarion/clarion.db"); + let db_path = project_dir.path().join(".loomweave/loomweave.db"); let conn = Connection::open(&db_path).expect("open db"); // Assert 1 + 2: exactly one run row with status "completed". @@ -273,7 +278,7 @@ fn wp2_rlimit_as_oom_kill_is_reported_as_host_finding() { let plugin_dir = setup_oom_plugin_dir(&fixture_bin); let project_dir = TempDir::new().expect("create project tempdir"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(project_dir.path()) .assert() @@ -283,11 +288,11 @@ fn wp2_rlimit_as_oom_kill_is_reported_as_host_finding() { let new_path = env::join_paths(std::iter::once(plugin_dir.path().to_path_buf())).expect("join_paths"); - let out = clarion_bin() + let out = loomweave_bin() .args(["analyze"]) .arg(project_dir.path()) .env("PATH", &new_path) - .env("CLARION_FIXTURE_EXCEED_RLIMIT_AS", "1") + .env("LOOMWEAVE_FIXTURE_EXCEED_RLIMIT_AS", "1") .assert() .failure(); let stdout = String::from_utf8(out.get_output().stdout.clone()).unwrap(); @@ -297,7 +302,7 @@ fn wp2_rlimit_as_oom_kill_is_reported_as_host_finding() { "OOM finding missing from analyze diagnostics.\nstdout: {stdout}\nstderr: {stderr}" ); - let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap(); let (run_status, stats_raw): (String, String) = conn .query_row("SELECT status, stats FROM runs LIMIT 1", [], |row| { Ok((row.get(0)?, row.get(1)?)) @@ -320,8 +325,8 @@ fn wp2_rlimit_as_oom_kill_is_reported_as_host_finding() { /// longer tanks the whole run. /// /// Scenario: -/// - `clarion-plugin-fixture` + its manifest in `plugin_dir_a` (extensions = mt) -/// - `clarion-plugin-broken` (symlink to /bin/true) + a manifest declaring +/// - `loomweave-plugin-fixture` + its manifest in `plugin_dir_a` (extensions = mt) +/// - `loomweave-plugin-broken` (symlink to /bin/true) + a manifest declaring /// `plugin_id` "broken" and extensions = "bk" in `plugin_dir_b` /// - Project root has `demo.mt` (fixture input) and `demo.bk` (broken input) /// - Both plugin dirs prepended to PATH @@ -341,15 +346,15 @@ fn wp2_crash_in_one_plugin_does_not_prevent_other_plugins_from_running() { // 3. plugin_dir_b: broken plugin pointing at /bin/true. let plugin_dir_b = TempDir::new().expect("create broken plugin dir"); - let broken_bin = plugin_dir_b.path().join("clarion-plugin-broken"); + let broken_bin = plugin_dir_b.path().join("loomweave-plugin-broken"); std::os::unix::fs::symlink("/bin/true", &broken_bin).expect("symlink /bin/true"); let broken_manifest = r#" [plugin] -name = "clarion-plugin-broken" +name = "loomweave-plugin-broken" plugin_id = "broken" version = "0.1.0" protocol_version = "1.0" -executable = "clarion-plugin-broken" +executable = "loomweave-plugin-broken" language = "broken" extensions = ["bk"] @@ -362,7 +367,7 @@ reads_outside_project_root = false [ontology] entity_kinds = ["widget"] edge_kinds = [] -rule_id_prefix = "CLA-BROKEN-" +rule_id_prefix = "LMWV-BROKEN-" ontology_version = "0.1.0" "#; fs::write(plugin_dir_b.path().join("plugin.toml"), broken_manifest) @@ -370,7 +375,7 @@ ontology_version = "0.1.0" // 4. Set up project directory with one file per plugin extension. let project_dir = TempDir::new().expect("create project tempdir"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(project_dir.path()) .assert() @@ -393,7 +398,7 @@ ontology_version = "0.1.0" // 6. analyze must exit non-zero (a plugin crashed) but the run still // processes the other plugin's files. - let out = clarion_bin() + let out = loomweave_bin() .args(["analyze"]) .arg(project_dir.path()) .env("PATH", &new_path) @@ -407,7 +412,7 @@ ontology_version = "0.1.0" // 7. Verify the DB: run = 'failed', entity from fixture IS persisted. // `fail_run` writes the reason into stats.failure_reason (JSON). - let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap(); let (row_count, run_status, stats_raw): (i64, String, String) = conn .query_row( "SELECT COUNT(*), COALESCE(MAX(status), ''), COALESCE(MAX(stats), '') FROM runs", @@ -490,16 +495,16 @@ fn wp2_crash_loop_breaker_trips_and_skips_remaining_plugins() { for i in 0..4u8 { let dir = TempDir::new().expect("create broken plugin dir"); let suffix = format!("broken{i}"); - let binary = dir.path().join(format!("clarion-plugin-{suffix}")); + let binary = dir.path().join(format!("loomweave-plugin-{suffix}")); std::os::unix::fs::symlink("/bin/true", &binary).expect("symlink /bin/true"); let manifest = format!( r#"[plugin] -name = "clarion-plugin-{suffix}" +name = "loomweave-plugin-{suffix}" plugin_id = "{suffix}" version = "0.1.0" protocol_version = "1.0" -executable = "clarion-plugin-{suffix}" +executable = "loomweave-plugin-{suffix}" language = "{suffix}" extensions = ["b{i}"] @@ -512,7 +517,7 @@ reads_outside_project_root = false [ontology] entity_kinds = ["widget"] edge_kinds = [] -rule_id_prefix = "CLA-{PREFIX}-" +rule_id_prefix = "LMWV-{PREFIX}-" ontology_version = "0.1.0" "#, PREFIX = suffix.to_uppercase(), @@ -533,7 +538,7 @@ ontology_version = "0.1.0" // via the "no files match" path at analyze.rs ~208, confounding the // "skipped by breaker" assertion. let project_dir = TempDir::new().expect("create project tempdir"); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(project_dir.path()) .assert() @@ -556,7 +561,7 @@ ontology_version = "0.1.0" let new_path = env::join_paths(parts).expect("join_paths"); // 5. analyze must fail (exit 1) — plugins crashed. - let out = clarion_bin() + let out = loomweave_bin() .args(["analyze"]) .arg(project_dir.path()) .env("PATH", &new_path) @@ -576,7 +581,7 @@ ontology_version = "0.1.0" "breaker-tripped log line missing from stderr.\nstdout: {stdout}\nstderr: {stderr}" ); assert!( - stderr.contains("CLA-INFRA-PLUGIN-DISABLED-CRASH-LOOP"), + stderr.contains("LMWV-INFRA-PLUGIN-DISABLED-CRASH-LOOP"), "FINDING_DISABLED_CRASH_LOOP subcode missing from stderr.\nstdout: {stdout}\nstderr: {stderr}" ); @@ -585,7 +590,7 @@ ontology_version = "0.1.0" // synthetic `core:project:*` finding anchor (REQ-ANALYZE-06, minted to // hold the persisted crash findings) is excluded — it is not a // plugin-produced entity. - let conn = Connection::open(project_dir.path().join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(project_dir.path().join(".loomweave/loomweave.db")).unwrap(); let entity_count: i64 = conn .query_row( "SELECT COUNT(*) FROM entities WHERE NOT (plugin_id = 'core' AND kind = 'project')", diff --git a/crates/clarion-core/Cargo.toml b/crates/loomweave-core/Cargo.toml similarity index 95% rename from crates/clarion-core/Cargo.toml rename to crates/loomweave-core/Cargo.toml index 1fd3da7d..e31077ce 100644 --- a/crates/clarion-core/Cargo.toml +++ b/crates/loomweave-core/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "clarion-core" +name = "loomweave-core" version.workspace = true edition.workspace = true license.workspace = true diff --git a/crates/clarion-core/src/embedding_provider.rs b/crates/loomweave-core/src/embedding_provider.rs similarity index 99% rename from crates/clarion-core/src/embedding_provider.rs rename to crates/loomweave-core/src/embedding_provider.rs index ee4aea35..498709ca 100644 --- a/crates/clarion-core/src/embedding_provider.rs +++ b/crates/loomweave-core/src/embedding_provider.rs @@ -2,7 +2,7 @@ //! //! Mirrors [`crate::llm_provider`]: a small trait with a deterministic recording //! double for tests and one live API-endpoint implementation. Embeddings are -//! **opt-in** (off by default, like the LLM policy) — Loom is local-first, so +//! **opt-in** (off by default, like the LLM policy) — Weft is local-first, so //! nothing here makes a hosted service *required*. When semantic search is off //! the MCP tool degrades honestly; it never fabricates an empty-as-complete //! result. diff --git a/crates/clarion-core/src/entity_id.rs b/crates/loomweave-core/src/entity_id.rs similarity index 99% rename from crates/clarion-core/src/entity_id.rs rename to crates/loomweave-core/src/entity_id.rs index 3f47964f..a335f20e 100644 --- a/crates/clarion-core/src/entity_id.rs +++ b/crates/loomweave-core/src/entity_id.rs @@ -1,6 +1,6 @@ //! Entity-ID assembler. //! -//! Per ADR-003 + ADR-022, every Clarion entity has a stable 3-segment ID: +//! Per ADR-003 + ADR-022, every Loomweave entity has a stable 3-segment ID: //! `{plugin_id}:{kind}:{canonical_qualified_name}`. //! //! - `plugin_id` and `kind` must match the grammar `[a-z][a-z0-9_]*`. diff --git a/crates/clarion-core/src/errors.rs b/crates/loomweave-core/src/errors.rs similarity index 96% rename from crates/clarion-core/src/errors.rs rename to crates/loomweave-core/src/errors.rs index cc83ece3..9ac8f37c 100644 --- a/crates/clarion-core/src/errors.rs +++ b/crates/loomweave-core/src/errors.rs @@ -1,20 +1,20 @@ -//! Shared error-code vocabularies for Clarion's two structured wire surfaces. +//! Shared error-code vocabularies for Loomweave's two structured wire surfaces. //! -//! Clarion emits machine-routable error codes on two **independent** surfaces. +//! Loomweave emits machine-routable error codes on two **independent** surfaces. //! This module is the single typed source of truth for both, so they cannot //! silently drift and so the MCP side gets compiler-checked codes instead of //! bare string literals. The two vocabularies are deliberately **not merged**: //! they serve different transports, different consumers, and different //! granularities (see the narrowing table below). HTTP status is intentionally //! chosen per endpoint and is therefore *not* derivable from the code — see -//! `clarion-cli`'s `classify_read_error` and ADR-037. +//! `loomweave-cli`'s `classify_read_error` and ADR-037. //! //! # Surfaces //! -//! * [`HttpErrorCode`] — the federation HTTP read API (`crates/clarion-cli`). +//! * [`HttpErrorCode`] — the federation HTTP read API (`crates/loomweave-cli`). //! `SCREAMING_SNAKE` on the wire; frozen contract in `docs/federation/contracts.md` //! and ADR-034; switched on by Filigree / Wardline clients. -//! * [`McpErrorCode`] — the MCP tool-error envelope (`crates/clarion-mcp`). +//! * [`McpErrorCode`] — the MCP tool-error envelope (`crates/loomweave-mcp`). //! kebab-case on the wire; consumed by consult-mode agents; pinned by tests. //! //! # MCP → HTTP narrowing relationship (documentation only) @@ -180,7 +180,7 @@ mod tests { #[test] fn mcp_error_code_wire_strings_are_pinned() { // These kebab spellings are stable on the MCP wire (pinned by - // clarion-mcp/tests/storage_tools.rs and relied on by consult agents). + // loomweave-mcp/tests/storage_tools.rs and relied on by consult agents). assert_eq!(McpErrorCode::InvalidPath.as_str(), "invalid-path"); assert_eq!(McpErrorCode::EntityNotFound.as_str(), "entity-not-found"); assert_eq!(McpErrorCode::StorageError.as_str(), "storage-error"); diff --git a/crates/clarion-core/src/hardened_git.rs b/crates/loomweave-core/src/hardened_git.rs similarity index 98% rename from crates/clarion-core/src/hardened_git.rs rename to crates/loomweave-core/src/hardened_git.rs index 91ef0019..729d437e 100644 --- a/crates/clarion-core/src/hardened_git.rs +++ b/crates/loomweave-core/src/hardened_git.rs @@ -1,7 +1,7 @@ //! Hardened `git` invocation for read-only probes against an **untrusted** //! corpus. //! -//! Clarion analyzes and serves repositories whose contents are not trusted (the +//! Loomweave analyzes and serves repositories whose contents are not trusted (the //! same posture that motivates the plugin jail, ADR-021, and the pre-ingest //! secret scanner). Running `git` inside such a repo is a command-execution //! hazard: repo-local configuration and Git *attributes* can name programs that @@ -115,7 +115,7 @@ const NULL_DEVICE: &str = "/dev/null"; /// /// ```no_run /// # use std::path::Path; -/// # use clarion_core::hardened_git_command; +/// # use loomweave_core::hardened_git_command; /// let out = hardened_git_command(Path::new("/corpus")) /// .args(["rev-parse", "HEAD"]) /// .output(); diff --git a/crates/clarion-core/src/lib.rs b/crates/loomweave-core/src/lib.rs similarity index 92% rename from crates/clarion-core/src/lib.rs rename to crates/loomweave-core/src/lib.rs index 35b858a6..d30418b1 100644 --- a/crates/clarion-core/src/lib.rs +++ b/crates/loomweave-core/src/lib.rs @@ -1,10 +1,10 @@ -//! clarion-core — domain types, identifiers, and provider traits. +//! loomweave-core — domain types, identifiers, and provider traits. //! //! # Re-export policy (ticket clarion-29acbcd042) //! //! Only facade types that external callers need are re-exported at the crate //! root. Implementation types (`Frame`, `TransportError`, `RequestEnvelope`, etc.) -//! remain accessible via `clarion_core::plugin::transport::*` and siblings. +//! remain accessible via `loomweave_core::plugin::transport::*` and siblings. pub mod embedding_provider; pub mod entity_id; diff --git a/crates/clarion-core/src/llm_provider.rs b/crates/loomweave-core/src/llm_provider.rs similarity index 96% rename from crates/clarion-core/src/llm_provider.rs rename to crates/loomweave-core/src/llm_provider.rs index 0287f717..dcc81c09 100644 --- a/crates/clarion-core/src/llm_provider.rs +++ b/crates/loomweave-core/src/llm_provider.rs @@ -15,8 +15,8 @@ use thiserror::Error; pub const LEAF_SUMMARY_PROMPT_TEMPLATE_ID: &str = "leaf-v1"; pub const INFERRED_CALLS_PROMPT_VERSION: &str = "inferred-calls-v1"; -const AGENT_PROVIDER_PROMPT_VERSION: &str = "clarion-agent-provider-v1"; -const CLAUDE_CLI_PRINT_PROMPT: &str = "You are Clarion's local Claude Code LLM provider. Read the Clarion provider prompt from stdin, complete that exact task, and return only the validated JSON object."; +const AGENT_PROVIDER_PROMPT_VERSION: &str = "loomweave-agent-provider-v1"; +const CLAUDE_CLI_PRINT_PROMPT: &str = "You are Loomweave's local Claude Code LLM provider. Read the Loomweave provider prompt from stdin, complete that exact task, and return only the validated JSON object."; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum LlmPurpose { @@ -115,21 +115,21 @@ pub trait LlmProvider: Send + Sync { pub fn build_coding_agent_provider_prompt(request: &LlmRequest) -> String { format!( "Prompt contract: {prompt_version}\n\ - You are Clarion's coding-agent LLM provider for repository graph enrichment.\n\ - Clarion has already selected the source excerpt, entity metadata, unresolved call sites, and candidate graph context needed for this task.\n\ + You are Loomweave's coding-agent LLM provider for repository graph enrichment.\n\ + Loomweave has already selected the source excerpt, entity metadata, unresolved call sites, and candidate graph context needed for this task.\n\ Follow these rules exactly:\n\ - 1. Use only the evidence inside . Do not inspect additional files, browse, run commands, edit files, or ask follow-up questions.\n\ + 1. Use only the evidence inside . Do not inspect additional files, browse, run commands, edit files, or ask follow-up questions.\n\ 2. Return exactly one JSON object matching the structured-output schema supplied by the caller. Do not wrap it in Markdown or prose.\n\ 3. Reason privately if needed, but do not expose hidden reasoning. Put only concise evidence summaries in output fields that ask for rationale or relationships.\n\ 4. When evidence is absent, prefer empty strings for optional prose fields and empty arrays for collection fields instead of guessing.\n\ - 5. Keep stable field names and JSON types; downstream Clarion storage parses the response mechanically.\n\ + 5. Keep stable field names and JSON types; downstream Loomweave storage parses the response mechanically.\n\ Task type: {task_type}\n\ Prompt template: {prompt_id}\n\ Task guidance:\n\ {task_guidance}\n\ - \n\ + \n\ {prompt}\n\ - \n", + \n", prompt_version = AGENT_PROVIDER_PROMPT_VERSION, task_type = agent_task_type(&request.purpose), prompt_id = request.prompt_id, @@ -363,8 +363,8 @@ impl LlmProvider for OpenRouterProvider { /// Resolve `executable` via `which::which` and return a typed CLI error if /// it is missing on PATH or at the configured absolute path. Called from each -/// CLI provider's `from_config` so a typo in `clarion.yaml` aborts at -/// `clarion serve` startup rather than exploding on the first MCP request. +/// CLI provider's `from_config` so a typo in `loomweave.yaml` aborts at +/// `loomweave serve` startup rather than exploding on the first MCP request. fn validate_cli_executable(label: &str, executable: &str) -> Result<(), LlmProviderError> { which::which(executable).map_err(|err| LlmProviderError::Cli { message: format!("{label} executable {executable:?} not resolvable: {err}"), @@ -563,8 +563,8 @@ impl LlmProvider for CodexCliProvider { async fn invoke(&self, request: LlmRequest) -> Result { let this = self.clone(); tokio::task::spawn_blocking(move || { - let output_file = codex_temp_file("clarion-codex-output", ".json")?; - let schema_file = codex_temp_file("clarion-codex-schema", ".json")?; + let output_file = codex_temp_file("loomweave-codex-output", ".json")?; + let schema_file = codex_temp_file("loomweave-codex-schema", ".json")?; this.invoke_with_temp_files(request, output_file.path(), schema_file.path()) }) .await @@ -803,7 +803,7 @@ fn response_format_for_purpose(purpose: &LlmPurpose) -> Value { LlmPurpose::Summary => serde_json::json!({ "type": "json_schema", "json_schema": { - "name": "clarion_summary", + "name": "loomweave_summary", "strict": true, "schema": { "type": "object", @@ -833,7 +833,7 @@ fn response_format_for_purpose(purpose: &LlmPurpose) -> Value { LlmPurpose::InferredEdges => serde_json::json!({ "type": "json_schema", "json_schema": { - "name": "clarion_inferred_calls", + "name": "loomweave_inferred_calls", "strict": true, "schema": { "type": "object", @@ -849,7 +849,7 @@ fn response_format_for_purpose(purpose: &LlmPurpose) -> Value { }, "target_id": { "type": "string", - "description": "The Clarion entity id for the inferred callee." + "description": "The Loomweave entity id for the inferred callee." }, "confidence": { "type": "number", @@ -1414,7 +1414,7 @@ pub fn build_leaf_summary_prompt(input: &LeafSummaryPromptInput) -> PromptTempla PromptTemplate { id: LEAF_SUMMARY_PROMPT_TEMPLATE_ID, body: format!( - "You are summarising one Clarion entity at leaf scope only.\n\ + "You are summarising one Loomweave entity at leaf scope only.\n\ Entity id: {entity_id}\n\ Kind: {kind}\n\ Name: {name}\n\ @@ -1434,7 +1434,7 @@ pub fn build_inferred_calls_prompt(input: &InferredCallsPromptInput) -> PromptTe PromptTemplate { id: INFERRED_CALLS_PROMPT_VERSION, body: format!( - "You are resolving unresolved Clarion call sites for one caller.\n\ + "You are resolving unresolved Loomweave call sites for one caller.\n\ Caller entity id: {caller}\n\ Caller source excerpt:\n{source}\n\ Unresolved call sites JSON:\n{sites}\n\ @@ -1528,12 +1528,12 @@ mod tests { let prompt = build_coding_agent_provider_prompt(&request); - assert!(prompt.contains("Prompt contract: clarion-agent-provider-v1")); + assert!(prompt.contains("Prompt contract: loomweave-agent-provider-v1")); assert!(prompt.contains("Task type: inferred_edges")); assert!(prompt.contains("Do not inspect additional files")); assert!(prompt.contains("Return exactly one JSON object")); assert!(prompt.contains("Choose targets only from the supplied candidate entities JSON")); - assert!(prompt.contains("")); + assert!(prompt.contains("")); assert!(prompt.contains("Resolve call-site a from the supplied candidates")); } @@ -1544,8 +1544,8 @@ mod tests { allow_live_provider: false, model_id: "anthropic/claude-sonnet-4.6".to_owned(), endpoint_url: "https://openrouter.ai/api/v1".to_owned(), - referer: "https://github.com/tachyon-beep/clarion".to_owned(), - title: "Clarion".to_owned(), + referer: "https://github.com/foundryside-dev/loomweave".to_owned(), + title: "Loomweave".to_owned(), timeout_seconds: 30, }) .expect_err("api key alone must not enable live calls"); @@ -1556,8 +1556,8 @@ mod tests { allow_live_provider: true, model_id: "anthropic/claude-sonnet-4.6".to_owned(), endpoint_url: "https://openrouter.ai/api/v1".to_owned(), - referer: "https://github.com/tachyon-beep/clarion".to_owned(), - title: "Clarion".to_owned(), + referer: "https://github.com/foundryside-dev/loomweave".to_owned(), + title: "Loomweave".to_owned(), timeout_seconds: 30, }) .expect_err("live opt-in without key should fail"); @@ -1568,8 +1568,8 @@ mod tests { allow_live_provider: true, model_id: "anthropic/claude-sonnet-4.6".to_owned(), endpoint_url: "https://openrouter.ai/api/v1".to_owned(), - referer: "https://github.com/tachyon-beep/clarion".to_owned(), - title: "Clarion".to_owned(), + referer: "https://github.com/foundryside-dev/loomweave".to_owned(), + title: "Loomweave".to_owned(), timeout_seconds: 30, }) .expect("live opt-in and key should construct provider"); @@ -1593,8 +1593,8 @@ mod tests { allow_live_provider: true, model_id: "anthropic/claude-sonnet-4.6".to_owned(), endpoint_url: "https://openrouter.ai/api/v1".to_owned(), - referer: "https://github.com/tachyon-beep/clarion".to_owned(), - title: "Clarion".to_owned(), + referer: "https://github.com/foundryside-dev/loomweave".to_owned(), + title: "Loomweave".to_owned(), timeout_seconds: 0, }); assert!( @@ -1617,13 +1617,13 @@ mod tests { let request = String::from_utf8_lossy(&request[..read]); assert!(request.contains("POST /api/v1/chat/completions HTTP/1.1")); assert!(request.contains("authorization: Bearer secret")); - assert!(request.contains("http-referer: https://github.com/tachyon-beep/clarion")); - assert!(request.contains("x-openrouter-title: Clarion")); + assert!(request.contains("http-referer: https://github.com/foundryside-dev/loomweave")); + assert!(request.contains("x-openrouter-title: Loomweave")); assert!(request.contains(r#""model":"anthropic/claude-sonnet-4.6""#)); assert!(request.contains(r#""max_tokens":512"#)); assert!(request.contains("Summarize this function")); assert!( - request.contains(r#""response_format":{"json_schema":{"name":"clarion_summary""#) + request.contains(r#""response_format":{"json_schema":{"name":"loomweave_summary""#) ); assert!(request.contains(r#""strict":true"#)); assert!( @@ -1657,8 +1657,8 @@ mod tests { allow_live_provider: true, model_id: "anthropic/claude-sonnet-4.6".to_owned(), endpoint_url: format!("http://{addr}/api/v1"), - referer: "https://github.com/tachyon-beep/clarion".to_owned(), - title: "Clarion".to_owned(), + referer: "https://github.com/foundryside-dev/loomweave".to_owned(), + title: "Loomweave".to_owned(), timeout_seconds: 30, }) .expect("test provider"); @@ -1746,11 +1746,9 @@ mod tests { let mut request = [0_u8; 8192]; let read = stream.read(&mut request).expect("read request"); let request = String::from_utf8_lossy(&request[..read]); - assert!( - request.contains( - r#""response_format":{"json_schema":{"name":"clarion_inferred_calls""# - ) - ); + assert!(request.contains( + r#""response_format":{"json_schema":{"name":"loomweave_inferred_calls""# + )); assert!(request.contains(r#""required":["edges"]"#)); assert!( request.contains(r#""required":["site_key","target_id","confidence","rationale"]"#) @@ -1783,8 +1781,8 @@ mod tests { allow_live_provider: true, model_id: "anthropic/claude-sonnet-4.6".to_owned(), endpoint_url: format!("http://{addr}/api/v1"), - referer: "https://github.com/tachyon-beep/clarion".to_owned(), - title: "Clarion".to_owned(), + referer: "https://github.com/foundryside-dev/loomweave".to_owned(), + title: "Loomweave".to_owned(), timeout_seconds: 30, }) .expect("test provider"); @@ -1816,8 +1814,8 @@ mod tests { allow_live_provider: true, model_id: "anthropic/claude-sonnet-4.6".to_owned(), endpoint_url: format!("http://{addr}/api/v1"), - referer: "https://github.com/tachyon-beep/clarion".to_owned(), - title: "Clarion".to_owned(), + referer: "https://github.com/foundryside-dev/loomweave".to_owned(), + title: "Loomweave".to_owned(), timeout_seconds: 30, }) .expect("test provider"); @@ -1917,8 +1915,8 @@ case "$stdin_prompt" in *) echo "missing prompt" >&2; exit 31 ;; esac case "$stdin_prompt" in - *"Prompt contract: clarion-agent-provider-v1"*"Do not inspect additional files"*) ;; - *) echo "missing Clarion agent prompt contract" >&2; exit 32 ;; + *"Prompt contract: loomweave-agent-provider-v1"*"Do not inspect additional files"*) ;; + *) echo "missing Loomweave agent prompt contract" >&2; exit 32 ;; esac echo "sandbox=$sandbox" >> "$log" @@ -1940,7 +1938,7 @@ printf '%s' '{{"purpose":"via codex","behavior":"ran fake CLI","relationships":" project_root: project_root.clone(), model_id: "codex-cli-default".to_owned(), model: Some("gpt-5.5".to_owned()), - profile: Some("clarion".to_owned()), + profile: Some("loomweave".to_owned()), sandbox: "read-only".to_owned(), timeout_seconds: 5, }) @@ -1976,7 +1974,7 @@ printf '%s' '{{"purpose":"via codex","behavior":"ran fake CLI","relationships":" assert!(log.contains("sandbox=read-only")); assert!(log.contains(&format!("cd={}", project_root.display()))); assert!(log.contains("model=gpt-5.5")); - assert!(log.contains("profile=clarion")); + assert!(log.contains("profile=loomweave")); } #[tokio::test] @@ -2140,8 +2138,8 @@ case "$stdin_prompt" in *) echo "missing prompt" >&2; exit 42 ;; esac case "$stdin_prompt" in - *"Prompt contract: clarion-agent-provider-v1"*"Do not inspect additional files"*) ;; - *) echo "missing Clarion agent prompt contract" >&2; exit 43 ;; + *"Prompt contract: loomweave-agent-provider-v1"*"Do not inspect additional files"*) ;; + *) echo "missing Loomweave agent prompt contract" >&2; exit 43 ;; esac echo "print_prompt=$print_prompt" >> "$log" @@ -2210,7 +2208,7 @@ printf '%s\n' '{{"type":"result","subtype":"success","structured_output":{{"purp ); let log = fs::read_to_string(log_path).expect("read fake claude log"); - assert!(log.contains("print_prompt=You are Clarion's local Claude Code LLM provider")); + assert!(log.contains("print_prompt=You are Loomweave's local Claude Code LLM provider")); assert!(log.contains("model=claude-sonnet-4-6")); assert!(log.contains("permission_mode=plan")); assert!(log.contains("tools=Read,Grep")); @@ -2542,8 +2540,8 @@ printf '%s\n' '{{"type":"result","subtype":"success","structured_output":{{"purp allow_live_provider: true, model_id: "anthropic/claude-sonnet-4.6".to_owned(), endpoint_url: format!("http://{addr}/api/v1"), - referer: "https://github.com/tachyon-beep/clarion".to_owned(), - title: "Clarion".to_owned(), + referer: "https://github.com/foundryside-dev/loomweave".to_owned(), + title: "Loomweave".to_owned(), timeout_seconds: 30, }) .expect("test provider"); diff --git a/crates/clarion-core/src/plugin/breaker.rs b/crates/loomweave-core/src/plugin/breaker.rs similarity index 97% rename from crates/clarion-core/src/plugin/breaker.rs rename to crates/loomweave-core/src/plugin/breaker.rs index b9ca1a74..32aba0b3 100644 --- a/crates/clarion-core/src/plugin/breaker.rs +++ b/crates/loomweave-core/src/plugin/breaker.rs @@ -5,7 +5,7 @@ //! spawn attempts for the rolling-window duration. //! //! Sprint 1 hard-codes the threshold and window per UQ-WP2-10; the config -//! surface (`clarion.yaml:plugin_limits.crash_*`) lands in WP6. +//! surface (`loomweave.yaml:plugin_limits.crash_*`) lands in WP6. use std::collections::VecDeque; use std::time::{Duration, Instant}; @@ -13,7 +13,7 @@ use std::time::{Duration, Instant}; // ── Finding subcode constants ───────────────────────────────────────────────── /// Subcode emitted when the breaker trips. -pub const FINDING_DISABLED_CRASH_LOOP: &str = "CLA-INFRA-PLUGIN-DISABLED-CRASH-LOOP"; +pub const FINDING_DISABLED_CRASH_LOOP: &str = "LMWV-INFRA-PLUGIN-DISABLED-CRASH-LOOP"; // ── CrashLoopState ──────────────────────────────────────────────────────────── @@ -266,7 +266,7 @@ mod tests { "initialize", &InitializeParams { protocol_version: "1.0".to_owned(), - project_root: "/tmp/clarion-breaker-test".to_owned(), + project_root: "/tmp/loomweave-breaker-test".to_owned(), }, 1, ); @@ -303,7 +303,7 @@ mod tests { let af_req = make_request( "analyze_file", &AnalyzeFileParams { - file_path: "/tmp/clarion-breaker-test/stub.mock".to_owned(), + file_path: "/tmp/loomweave-breaker-test/stub.mock".to_owned(), }, 2, ); diff --git a/crates/clarion-core/src/plugin/discovery.rs b/crates/loomweave-core/src/plugin/discovery.rs similarity index 64% rename from crates/clarion-core/src/plugin/discovery.rs rename to crates/loomweave-core/src/plugin/discovery.rs index 3a7829dc..b19bd98d 100644 --- a/crates/clarion-core/src/plugin/discovery.rs +++ b/crates/loomweave-core/src/plugin/discovery.rs @@ -1,42 +1,51 @@ -//! Plugin discovery via `$PATH` scanning (ADR-021 §L9). +//! Plugin discovery via `$PATH` scanning and the running binary's own +//! directory (ADR-021 §L9). +//! +//! Directories are scanned in order: every `$PATH` entry first, then the +//! directory of the running `loomweave` binary (`std::env::current_exe()`'s +//! parent). The exe-dir level makes a PyPI/venv install work — the plugin's +//! console script is co-located in the same `bin/` as `loomweave` but is not on +//! the user's `$PATH`. //! //! # Matching rule //! -//! A file is a Clarion plugin candidate if its name matches -//! `clarion-plugin-` where `` is at least one character -//! consisting solely of `[A-Za-z0-9_-]`. Names such as `clarion-plugin-` -//! (empty suffix) or `clarion-plugin` (no second hyphen) are rejected. +//! A file is a Loomweave plugin candidate if its name matches +//! `loomweave-plugin-` where `` is at least one character +//! consisting solely of `[A-Za-z0-9_-]`. Names such as `loomweave-plugin-` +//! (empty suffix) or `loomweave-plugin` (no second hyphen) are rejected. //! //! Additionally the file must exist, be a regular file, and — on Unix — have //! at least one executable bit set (`mode & 0o111 != 0`). //! //! # Manifest lookup order //! -//! For an executable at `/clarion-plugin-`: +//! For an executable at `/loomweave-plugin-`: //! //! 1. **Neighbor first**: `/plugin.toml`. //! 2. **Install-prefix fallback** (only when `` has basename `bin`): -//! `/../share/clarion/plugins//plugin.toml`. +//! `/../share/loomweave/plugins//plugin.toml`. //! 3. **Symlink-resolved install-prefix fallback** (only when `` has //! basename `bin` and the executable is a symlink, e.g. pipx layout): //! canonicalise the executable, then try -//! `/../share/clarion/plugins//plugin.toml`. +//! `/../share/loomweave/plugins//plugin.toml`. //! This catches `pipx install` (which puts a symlink in `~/.local/bin/` //! pointing into `~/.local/share/pipx/venvs//bin/`, with the //! manifest under that venv's `share/`). //! 4. None found → [`DiscoveryError::ManifestNotFound`]. //! -//! **Limitation**: when multiple `clarion-plugin-*` binaries share the same +//! **Limitation**: when multiple `loomweave-plugin-*` binaries share the same //! directory (e.g. `/usr/local/bin`), they all resolve to the *same* //! neighbor `plugin.toml`. This is a known constraint of the neighbor //! convention; real installs should use the install-prefix layout so each -//! plugin has its own `share/clarion/plugins//plugin.toml`. +//! plugin has its own `share/loomweave/plugins//plugin.toml`. //! //! # Deduplication //! //! Duplicate `$PATH` directories are skipped. If the same binary name //! appears in multiple directories the first occurrence wins (matching -//! POSIX shell / `which` semantics). +//! POSIX shell / `which` semantics). Because `$PATH` is scanned before the +//! exe directory, a PATH-installed plugin shadows a same-named sibling +//! co-located next to the binary. use std::collections::HashSet; use std::ffi::OsStr; @@ -49,20 +58,21 @@ use crate::plugin::{Manifest, ManifestError, parse_manifest}; // ── Public types ────────────────────────────────────────────────────────────── -/// A plugin discovered via a `clarion-plugin-*` executable on `$PATH`. +/// A plugin discovered via a `loomweave-plugin-*` executable on `$PATH`. #[derive(Debug)] pub struct DiscoveredPlugin { - /// Path to the plugin executable **as found on `$PATH`**. + /// Path to the plugin executable **as found during discovery** (on + /// `$PATH`, or co-located in the running binary's directory). /// /// Intentionally NOT canonicalised. The neighbour-manifest lookup /// joins `plugin.toml` with this path's parent directory; /// canonicalising here would follow symlinks (e.g. - /// `~/bin/clarion-plugin-python` → `~/.local/pipx/venvs/*/bin/...`) + /// `~/bin/loomweave-plugin-python` → `~/.local/pipx/venvs/*/bin/...`) /// and the manifest lookup would then miss the neighbour that lives /// next to the symlink. /// /// Deduplication uses a separate canonicalised key - /// (`seen_dirs` inside [`discover_on_path`]), so the raw-path retained + /// (`seen_dirs` inside `scan_dir`), so the raw-path retained /// here does not defeat shadowing. /// /// If you need the real binary location for an operator message (e.g. @@ -78,11 +88,11 @@ pub struct DiscoveredPlugin { /// Errors produced during plugin discovery. /// -/// Each variant corresponds to a single `clarion-plugin-*` binary; a +/// Each variant corresponds to a single `loomweave-plugin-*` binary; a /// failure for one plugin does **not** suppress results for others. #[derive(Debug, Error)] pub enum DiscoveryError { - /// A `clarion-plugin-*` binary was found on `$PATH` but no `plugin.toml` + /// A `loomweave-plugin-*` binary was found on `$PATH` but no `plugin.toml` /// was found at either the neighbor location or the install-prefix /// location. #[error( @@ -114,7 +124,7 @@ pub enum DiscoveryError { ManifestTooLarge { path: PathBuf }, /// A `$PATH` directory is world-writable. Any user with write - /// access could drop a `clarion-plugin-*` binary into it. Refused + /// access could drop a `loomweave-plugin-*` binary into it. Refused /// to preserve the ADR-021 "semi-trusted plugin" model — operator /// must deliberately install plugins. #[error( @@ -134,12 +144,15 @@ pub const MAX_MANIFEST_BYTES: u64 = 64 * 1024; /// Discover plugins on the user's `$PATH`. /// /// Reads `$PATH` from the process environment and delegates to -/// [`discover_on_path`]. Returns one `Result` per `clarion-plugin-*` +/// [`discover_on_path`]. Returns one `Result` per `loomweave-plugin-*` /// binary found. #[cfg(unix)] pub fn discover() -> Vec> { let path_val = std::env::var_os("PATH").unwrap_or_default(); - discover_on_path(&path_val) + let exe_dir = std::env::current_exe() + .ok() + .and_then(|p| p.parent().map(std::path::Path::to_path_buf)); + discover_on_path_and_exe_dir(&path_val, exe_dir.as_deref()) } #[cfg(not(unix))] @@ -150,10 +163,10 @@ pub fn discover() -> Vec> { /// Discover plugins on the given explicit `PATH` value (useful in tests). /// /// Parses `path_env` using [`std::env::split_paths`], then scans each -/// directory for `clarion-plugin-*` executables. Returns one `Result` per +/// directory for `loomweave-plugin-*` executables. Returns one `Result` per /// candidate found; a broken plugin does not suppress its siblings. /// -/// **Note**: if two `clarion-plugin-*` binaries sharing a directory both +/// **Note**: if two `loomweave-plugin-*` binaries sharing a directory both /// try to use the neighbor `plugin.toml`, they will resolve to the *same* /// file. This is expected behaviour given the neighbor convention; see the /// module-level docs for the recommended install-prefix layout. @@ -161,88 +174,120 @@ pub fn discover() -> Vec> { /// Primarily useful for testing; production callers should use [`discover`]. #[cfg(unix)] pub fn discover_on_path(path_env: &OsStr) -> Vec> { + discover_on_path_and_exe_dir(path_env, None) +} + +/// Like [`discover_on_path`], but additionally scans `exe_dir` (the directory of +/// the running `loomweave` binary) **after** the `$PATH` entries. `$PATH` entries +/// are scanned first, so a plugin found on `$PATH` shadows a same-named sibling +/// next to the binary (first-match-wins, consistent with PATH shadowing). +/// +/// This is the discovery source that makes a PyPI/venv install work: the plugin +/// console script is co-located in the same `bin/` as `loomweave` but is not on +/// the user's `$PATH`. See ADR-021. +#[cfg(unix)] +pub fn discover_on_path_and_exe_dir( + path_env: &OsStr, + exe_dir: Option<&std::path::Path>, +) -> Vec> { let mut results = Vec::new(); let mut seen_dirs: HashSet = HashSet::new(); let mut seen_names: HashSet = HashSet::new(); - for dir in std::env::split_paths(path_env) { - // Skip empty entries (POSIX: empty means cwd — we don't support that). - if dir.as_os_str().is_empty() { - continue; - } + let exe_dirs = exe_dir.map(std::path::Path::to_path_buf).into_iter(); + for dir in std::env::split_paths(path_env).chain(exe_dirs) { + scan_dir(&dir, &mut seen_dirs, &mut seen_names, &mut results); + } - // Deduplicate directories. - let canonical_dir = match dir.canonicalize() { - Ok(c) => c, - // If the dir doesn't exist or can't be canonicalised, still use the - // raw path for dedup so we don't skip a later entry that resolves - // differently. - Err(_) => dir.clone(), - }; - if !seen_dirs.insert(canonical_dir.clone()) { - continue; - } + results +} + +/// Scan a single directory for `loomweave-plugin-*` executables, appending results. +/// Shared by every discovery source; honours dir/name de-duplication and the +/// world-writable refusal (ADR-021). +#[cfg(unix)] +fn scan_dir( + dir: &std::path::Path, + seen_dirs: &mut HashSet, + seen_names: &mut HashSet, + results: &mut Vec>, +) { + // Skip empty entries (POSIX: empty means cwd — we don't support that). + if dir.as_os_str().is_empty() { + return; + } + + // Deduplicate directories. + let canonical_dir = match dir.canonicalize() { + Ok(c) => c, + // If the dir doesn't exist or can't be canonicalised, still use the + // raw path for dedup so we don't skip a later entry that resolves + // differently. + Err(_) => dir.to_path_buf(), + }; + if !seen_dirs.insert(canonical_dir.clone()) { + return; + } + + // Refuse to load plugins from world-writable directories. On a + // multi-user machine, any user with write access to a $PATH dir + // becomes a plugin installer — a threat model the hybrid- + // authority framing (ADR-021) rules out. Production installs + // should use `~/.local/bin` (0o755) or `/usr/local/bin` (0o755); + // only pathologically misconfigured dirs fail this check. + if is_world_writable(dir) { + results.push(Err(DiscoveryError::WorldWritableDir { + path: dir.to_path_buf(), + })); + return; + } + + // Read directory entries; skip silently on I/O error (non-existent + // dirs are common in $PATH). + let Ok(entries) = std::fs::read_dir(dir) else { + return; + }; - // Refuse to load plugins from world-writable directories. On a - // multi-user machine, any user with write access to a $PATH dir - // becomes a plugin installer — a threat model the hybrid- - // authority framing (ADR-021) rules out. Production installs - // should use `~/.local/bin` (0o755) or `/usr/local/bin` (0o755); - // only pathologically misconfigured dirs fail this check. - if is_world_writable(&dir) { - results.push(Err(DiscoveryError::WorldWritableDir { path: dir.clone() })); + for entry_result in entries { + let Ok(entry) = entry_result else { continue; - } + }; - // Read directory entries; skip silently on I/O error (non-existent - // dirs are common in $PATH). - let Ok(entries) = std::fs::read_dir(&dir) else { + // non-UTF-8 names can't match our prefix. + let Ok(file_name) = entry.file_name().into_string() else { continue; }; - for entry_result in entries { - let Ok(entry) = entry_result else { - continue; - }; - - // non-UTF-8 names can't match our prefix. - let Ok(file_name) = entry.file_name().into_string() else { - continue; - }; - - // ── Name filter ─────────────────────────────────────────────────── - let suffix = match extract_plugin_suffix(&file_name) { - Some(s) => s.to_owned(), - None => continue, - }; - - // ── Shadowing: first match wins ─────────────────────────────────── - // Safe to key on String: non-UTF-8 names were filtered above. - if !seen_names.insert(file_name.clone()) { - continue; - } + // ── Name filter ─────────────────────────────────────────────────── + let suffix = match extract_plugin_suffix(&file_name) { + Some(s) => s.to_owned(), + None => continue, + }; - // `exec_path` is the raw PATH-relative path (not canonicalised). - // Do not canonicalise here — the neighbour-manifest convention - // at `load_plugin` / `find_manifest` looks up `plugin.toml` next - // to this path, and a symlink install pattern (e.g. `~/bin/` full - // of symlinks into `~/.local/pipx/venvs/*/bin/`) expects the - // manifest to live next to the symlink, not next to the resolved - // binary in the venv. See the `executable` field doc-comment on - // `DiscoveredPlugin` for the full consistency story. - let exec_path = dir.join(&file_name); - - // ── Exec-bit check ──────────────────────────────────────────────── - if !is_executable(&exec_path) { - continue; - } + // ── Shadowing: first match wins ─────────────────────────────────── + // Safe to key on String: non-UTF-8 names were filtered above. + if !seen_names.insert(file_name.clone()) { + continue; + } - // ── Manifest lookup ─────────────────────────────────────────────── - results.push(load_plugin(exec_path, &suffix)); + // `exec_path` is the raw PATH-relative path (not canonicalised). + // Do not canonicalise here — the neighbour-manifest convention + // at `load_plugin` / `find_manifest` looks up `plugin.toml` next + // to this path, and a symlink install pattern (e.g. `~/bin/` full + // of symlinks into `~/.local/pipx/venvs/*/bin/`) expects the + // manifest to live next to the symlink, not next to the resolved + // binary in the venv. See the `executable` field doc-comment on + // `DiscoveredPlugin` for the full consistency story. + let exec_path = dir.join(&file_name); + + // ── Exec-bit check ──────────────────────────────────────────────── + if !is_executable(&exec_path) { + continue; } - } - results + // ── Manifest lookup ─────────────────────────────────────────────── + results.push(load_plugin(exec_path, &suffix)); + } } #[cfg(not(unix))] @@ -250,13 +295,21 @@ pub fn discover_on_path(_path_env: &OsStr) -> Vec, +) -> Vec> { + vec![] +} + // ── Internal helpers ────────────────────────────────────────────────────────── -/// Extract the `` from a `clarion-plugin-` name, or `None`. +/// Extract the `` from a `loomweave-plugin-` name, or `None`. /// /// Suffix must be at least one character and consist only of `[A-Za-z0-9_-]`. fn extract_plugin_suffix(name: &str) -> Option<&str> { - let suffix = name.strip_prefix("clarion-plugin-")?; + let suffix = name.strip_prefix("loomweave-plugin-")?; if suffix.is_empty() { return None; } @@ -373,8 +426,8 @@ fn find_manifest(exec_path: &std::path::Path, suffix: &str) -> Result is a symlink into - // ~/.local/share/pipx/venvs//bin/clarion-plugin-, and + // layouts: ~/.local/bin/loomweave-plugin- is a symlink into + // ~/.local/share/pipx/venvs//bin/loomweave-plugin-, and // the manifest lives under that venv's share/. Canonicalise // the executable and re-try the install-prefix layout from // the resolved location. @@ -401,7 +454,7 @@ fn probe_install_prefix_manifest( ) -> Result, DiscoveryError> { let share_path = prefix .join("share") - .join("clarion") + .join("loomweave") .join("plugins") .join(suffix) .join("plugin.toml"); @@ -424,11 +477,11 @@ mod tests { fn minimal_manifest_toml(plugin_id: &str) -> String { format!( r#"[plugin] -name = "clarion-plugin-{plugin_id}" +name = "loomweave-plugin-{plugin_id}" plugin_id = "{plugin_id}" version = "0.1.0" protocol_version = "1.0" -executable = "clarion-plugin-{plugin_id}" +executable = "loomweave-plugin-{plugin_id}" language = "{plugin_id}" extensions = ["mt"] @@ -441,7 +494,7 @@ reads_outside_project_root = false [ontology] entity_kinds = ["function"] edge_kinds = ["calls"] -rule_id_prefix = "CLA-MT-" +rule_id_prefix = "LMWV-MT-" ontology_version = "0.1.0" "# ) @@ -476,7 +529,7 @@ ontology_version = "0.1.0" let bin = tmp.path().join("bin"); fs::create_dir_all(&bin).unwrap(); - make_executable(&bin.join("clarion-plugin-mocktest")); + make_executable(&bin.join("loomweave-plugin-mocktest")); fs::write(bin.join("plugin.toml"), minimal_manifest_toml("mocktest")).unwrap(); let results = discover_on_path(&path_os(&[&bin])); @@ -484,7 +537,7 @@ ontology_version = "0.1.0" let plugin = results.into_iter().next().unwrap().unwrap(); assert_eq!(plugin.manifest.plugin.plugin_id, "mocktest"); - assert_eq!(plugin.executable, bin.join("clarion-plugin-mocktest")); + assert_eq!(plugin.executable, bin.join("loomweave-plugin-mocktest")); assert_eq!(plugin.manifest_path, bin.join("plugin.toml")); } @@ -496,12 +549,12 @@ ontology_version = "0.1.0" let bin = tmp.path().join("bin"); fs::create_dir_all(&bin).unwrap(); - make_executable(&bin.join("clarion-plugin-mocktest")); + make_executable(&bin.join("loomweave-plugin-mocktest")); // No neighbor plugin.toml — only the share/ location. let share = tmp .path() .join("share") - .join("clarion") + .join("loomweave") .join("plugins") .join("mocktest"); fs::create_dir_all(&share).unwrap(); @@ -515,8 +568,87 @@ ontology_version = "0.1.0" assert_eq!( plugin.manifest_path, tmp.path() - .join("share/clarion/plugins/mocktest/plugin.toml") + .join("share/loomweave/plugins/mocktest/plugin.toml") + ); + } + + // ── T2b: current_exe() sibling level (install-prefix, NOT on $PATH) ──────── + + #[test] + fn t2b_exe_dir_install_prefix_found_when_not_on_path() { + let tmp = TempDir::new().unwrap(); + let bin = tmp.path().join("bin"); + fs::create_dir_all(&bin).unwrap(); + + make_executable(&bin.join("loomweave-plugin-mocktest")); + let share = tmp.path().join("share/loomweave/plugins/mocktest"); + fs::create_dir_all(&share).unwrap(); + fs::write(share.join("plugin.toml"), minimal_manifest_toml("mocktest")).unwrap(); + + // $PATH is EMPTY — the plugin is only reachable via the exe dir. + let results = discover_on_path_and_exe_dir(std::ffi::OsStr::new(""), Some(bin.as_path())); + assert_eq!(results.len(), 1, "exe-dir plugin should be discovered"); + + let plugin = results.into_iter().next().unwrap().unwrap(); + assert_eq!(plugin.manifest.plugin.plugin_id, "mocktest"); + assert_eq!( + plugin.manifest_path, + tmp.path() + .join("share/loomweave/plugins/mocktest/plugin.toml") + ); + } + + #[test] + fn t2c_path_entry_shadows_same_named_exe_dir_sibling() { + // A plugin on $PATH wins over a same-named sibling next to the binary. + let tmp = TempDir::new().unwrap(); + let path_bin = tmp.path().join("pathbin"); + let exe_bin = tmp.path().join("exebin"); + fs::create_dir_all(&path_bin).unwrap(); + fs::create_dir_all(&exe_bin).unwrap(); + + make_executable(&path_bin.join("loomweave-plugin-mocktest")); + fs::write( + path_bin.join("plugin.toml"), + minimal_manifest_toml("mocktest"), + ) + .unwrap(); + make_executable(&exe_bin.join("loomweave-plugin-mocktest")); + fs::write( + exe_bin.join("plugin.toml"), + minimal_manifest_toml("mocktest"), + ) + .unwrap(); + + let results = discover_on_path_and_exe_dir(&path_os(&[&path_bin]), Some(exe_bin.as_path())); + assert_eq!(results.len(), 1, "duplicate name must be de-duplicated"); + let plugin = results.into_iter().next().unwrap().unwrap(); + assert_eq!( + plugin.executable, + path_bin.join("loomweave-plugin-mocktest"), + "$PATH entry must shadow the exe-dir sibling" + ); + } + + #[test] + fn t2d_exe_dir_equal_to_path_dir_is_deduped() { + // The exe dir resolving to a dir already on $PATH must not be scanned + // twice (seen_dirs gate), so the plugin is reported exactly once. + let tmp = TempDir::new().unwrap(); + let bin = tmp.path().join("bin"); + fs::create_dir_all(&bin).unwrap(); + make_executable(&bin.join("loomweave-plugin-mocktest")); + let share = tmp.path().join("share/loomweave/plugins/mocktest"); + fs::create_dir_all(&share).unwrap(); + fs::write(share.join("plugin.toml"), minimal_manifest_toml("mocktest")).unwrap(); + + let results = discover_on_path_and_exe_dir(&path_os(&[&bin]), Some(bin.as_path())); + assert_eq!( + results.len(), + 1, + "exe dir == a $PATH dir must be deduped, not double-scanned" ); + assert!(results.into_iter().next().unwrap().is_ok()); } // ── T3: no manifest anywhere → ManifestNotFound ─────────────────────────── @@ -527,7 +659,7 @@ ontology_version = "0.1.0" let bin = tmp.path().join("bin"); fs::create_dir_all(&bin).unwrap(); - make_executable(&bin.join("clarion-plugin-orphan")); + make_executable(&bin.join("loomweave-plugin-orphan")); let results = discover_on_path(&path_os(&[&bin])); assert_eq!(results.len(), 1); @@ -547,7 +679,7 @@ ontology_version = "0.1.0" let bin = tmp.path().join("bin"); fs::create_dir_all(&bin).unwrap(); - make_executable(&bin.join("clarion-plugin-broken")); + make_executable(&bin.join("loomweave-plugin-broken")); fs::write(bin.join("plugin.toml"), b"this is not valid toml ][[[").unwrap(); let results = discover_on_path(&path_os(&[&bin])); @@ -569,12 +701,12 @@ ontology_version = "0.1.0" fs::create_dir_all(&bin).unwrap(); // Should NOT match: - make_executable(&bin.join("not-clarion-plugin")); - make_executable(&bin.join("clarion-plugin-")); // empty suffix - make_executable(&bin.join("clarion-plugin")); // no second hyphen + make_executable(&bin.join("not-loomweave-plugin")); + make_executable(&bin.join("loomweave-plugin-")); // empty suffix + make_executable(&bin.join("loomweave-plugin")); // no second hyphen // Should match: - make_executable(&bin.join("clarion-plugin-valid")); + make_executable(&bin.join("loomweave-plugin-valid")); fs::write(bin.join("plugin.toml"), minimal_manifest_toml("valid")).unwrap(); let results = discover_on_path(&path_os(&[&bin])); @@ -593,7 +725,7 @@ ontology_version = "0.1.0" fs::create_dir_all(&bin).unwrap(); // File exists but has no exec bit. - make_plain_file(&bin.join("clarion-plugin-noexec"), b"#!/bin/sh\n"); + make_plain_file(&bin.join("loomweave-plugin-noexec"), b"#!/bin/sh\n"); fs::write(bin.join("plugin.toml"), minimal_manifest_toml("noexec")).unwrap(); let results = discover_on_path(&path_os(&[&bin])); @@ -611,10 +743,10 @@ ontology_version = "0.1.0" fs::create_dir_all(&dir_b).unwrap(); // Both dirs have the same binary name with valid manifests. - make_executable(&dir_a.join("clarion-plugin-dup")); + make_executable(&dir_a.join("loomweave-plugin-dup")); fs::write(dir_a.join("plugin.toml"), minimal_manifest_toml("dup")).unwrap(); - make_executable(&dir_b.join("clarion-plugin-dup")); + make_executable(&dir_b.join("loomweave-plugin-dup")); fs::write(dir_b.join("plugin.toml"), minimal_manifest_toml("dup")).unwrap(); let results = discover_on_path(&path_os(&[dir_a.as_path(), dir_b.as_path()])); @@ -626,7 +758,7 @@ ontology_version = "0.1.0" let plugin = results.into_iter().next().unwrap().unwrap(); // Executable must come from dir_a, not dir_b. - assert_eq!(plugin.executable, dir_a.join("clarion-plugin-dup")); + assert_eq!(plugin.executable, dir_a.join("loomweave-plugin-dup")); } // ── T8: world-writable directory refused ────────────────────────────────── @@ -638,7 +770,7 @@ ontology_version = "0.1.0" #[test] fn t8_world_writable_dir_is_refused() { let dir = TempDir::new().unwrap(); - make_executable(&dir.path().join("clarion-plugin-evil")); + make_executable(&dir.path().join("loomweave-plugin-evil")); fs::write( dir.path().join("plugin.toml"), minimal_manifest_toml("evil"), diff --git a/crates/clarion-core/src/plugin/host.rs b/crates/loomweave-core/src/plugin/host.rs similarity index 99% rename from crates/clarion-core/src/plugin/host.rs rename to crates/loomweave-core/src/plugin/host.rs index 4525c2b4..90c8df35 100644 --- a/crates/clarion-core/src/plugin/host.rs +++ b/crates/loomweave-core/src/plugin/host.rs @@ -94,7 +94,7 @@ pub const MAX_FINDING_SEVERITY_BYTES: usize = 32; /// maps [`RawEntity::extra`] and [`RawSource::extra`]. /// /// These flow into `properties_json` downstream (via -/// `clarion-cli::analyze::map_entity_to_record`) as `serde_json::to_string` +/// `loomweave-cli::analyze::map_entity_to_record`) as `serde_json::to_string` /// output. Without a cap, a plugin could return 8 MiB frames consisting of /// one tiny `qualified_name` plus a multi-MiB `extra` map that lives in the /// database row and in every host-side clone until the run ends. 64 KiB is @@ -263,7 +263,7 @@ fn oversize_field(raw: &RawEntity) -> Option<(&'static str, usize)> { // check is by serialised byte length rather than entry count — a single // entry with a multi-MiB Value is as toxic as many entries each small. // Serialisation is the next-downstream step anyway (via - // clarion-cli::analyze::map_entity_to_record), so the to_vec here is not + // loomweave-cli::analyze::map_entity_to_record), so the to_vec here is not // an additional allocation beyond what we were already going to pay. for (name, map) in [("extra", &raw.extra), ("source.extra", &raw.source.extra)] { if map.is_empty() { @@ -648,7 +648,7 @@ impl // 1. Absolute / relative paths in the manifest (`executable = "/bin/sh"`, // `executable = "../../evil"`) that would run a binary the // operator did not install. - // 2. Mismatch between discovered name (`clarion-plugin-python`) and + // 2. Mismatch between discovered name (`loomweave-plugin-python`) and // declared name — which would silently run the wrong binary if // a plugin directory contained multiple. let declared = &manifest.plugin.executable; @@ -750,7 +750,7 @@ impl let stderr_tail_for_thread = std::sync::Arc::clone(&stderr_tail); let stderr_thread = std::thread::Builder::new() .name(format!( - "clarion-plugin-stderr-drain:{}", + "loomweave-plugin-stderr-drain:{}", manifest.plugin.plugin_id )) .spawn(move || drain_stderr_into_ring(stderr, &stderr_tail_for_thread)) @@ -1473,7 +1473,7 @@ reads_outside_project_root = false [ontology] entity_kinds = ["function"] edge_kinds = [] -rule_id_prefix = "CLA-MOCK-" +rule_id_prefix = "LMWV-MOCK-" ontology_version = "0.1.0" "#; crate::plugin::parse_manifest(toml.as_bytes()).expect("valid compliant manifest") @@ -1499,7 +1499,7 @@ reads_outside_project_root = false [ontology] entity_kinds = ["module", "function"] edge_kinds = ["contains", "calls"] -rule_id_prefix = "CLA-MOCK-" +rule_id_prefix = "LMWV-MOCK-" ontology_version = "0.4.0" "#; crate::plugin::parse_manifest(toml.as_bytes()).expect("valid calls manifest") @@ -1528,7 +1528,7 @@ pin = "1.1.409" [ontology] entity_kinds = ["module", "function"] edge_kinds = ["contains", "calls"] -rule_id_prefix = "CLA-MOCK-" +rule_id_prefix = "LMWV-MOCK-" ontology_version = "0.4.0" "#; crate::plugin::parse_manifest(toml.as_bytes()).expect("valid pyright manifest") @@ -1554,7 +1554,7 @@ reads_outside_project_root = true [ontology] entity_kinds = ["function"] edge_kinds = [] -rule_id_prefix = "CLA-MOCK-" +rule_id_prefix = "LMWV-MOCK-" ontology_version = "0.1.0" "#; crate::plugin::parse_manifest(toml.as_bytes()).expect("valid reads-outside manifest") @@ -1666,7 +1666,7 @@ ontology_version = "0.1.0" // ── T2: reads_outside_project_root refusal ──────────────────────────────── /// T2: manifest with `reads_outside_project_root = true` is refused at - /// handshake. Host emits `CLA-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY`, + /// handshake. Host emits `LMWV-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY`, /// sends `shutdown` + `exit`, and no `analyze_file` is dispatched. #[test] fn t2_reads_outside_project_root_refused_at_handshake() { @@ -1738,7 +1738,7 @@ ontology_version = "0.1.0" findings .iter() .any(|f| f.subcode == FINDING_UNSUPPORTED_CAPABILITY), - "must have CLA-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY; got: {findings:?}" + "must have LMWV-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY; got: {findings:?}" ); // Verify that neither analyze_file NOR initialized was sent. The @@ -1765,7 +1765,7 @@ ontology_version = "0.1.0" // ── T3: ontology-boundary enforcement ──────────────────────────────────── /// T3: plugin emits entity with `kind: "unknown"` not in manifest ontology. - /// Host drops it and emits `CLA-INFRA-PLUGIN-UNDECLARED-KIND`. + /// Host drops it and emits `LMWV-INFRA-PLUGIN-UNDECLARED-KIND`. #[test] fn t3_undeclared_kind_is_dropped_with_finding() { let manifest = compliant_manifest(); // entity_kinds = ["function"] @@ -1835,7 +1835,7 @@ ontology_version = "0.1.0" /// T4: plugin emits entity whose `id` doesn't match /// `entity_id(plugin_id, kind, qualified_name)`. Host drops it and emits - /// `CLA-INFRA-PLUGIN-ENTITY-ID-MISMATCH`. + /// `LMWV-INFRA-PLUGIN-ENTITY-ID-MISMATCH`. #[test] fn t4_identity_mismatch_drops_entity_with_finding() { let manifest = compliant_manifest(); @@ -1872,14 +1872,14 @@ ontology_version = "0.1.0" findings .iter() .any(|f| f.subcode == FINDING_ENTITY_ID_MISMATCH), - "must have CLA-INFRA-PLUGIN-ENTITY-ID-MISMATCH; got: {findings:?}" + "must have LMWV-INFRA-PLUGIN-ENTITY-ID-MISMATCH; got: {findings:?}" ); } // ── T5: path-jail drop-not-kill ─────────────────────────────────────────── /// T5: plugin emits one entity with a source path that escapes the jail. - /// Host drops the entity, emits `CLA-INFRA-PLUGIN-PATH-ESCAPE`, plugin + /// Host drops the entity, emits `LMWV-INFRA-PLUGIN-PATH-ESCAPE`, plugin /// stays alive (no kill error returned). #[test] fn t5_single_path_escape_drops_entity_plugin_survives() { @@ -1914,7 +1914,7 @@ ontology_version = "0.1.0" let findings = host.take_findings(); assert!( findings.iter().any(|f| f.subcode == FINDING_PATH_ESCAPE), - "must have CLA-INFRA-PLUGIN-PATH-ESCAPE; got: {findings:?}" + "must have LMWV-INFRA-PLUGIN-PATH-ESCAPE; got: {findings:?}" ); assert!( !findings @@ -1928,7 +1928,7 @@ ontology_version = "0.1.0" /// T6: plugin emits 11 entities each with an escaping path. On the 11th /// the breaker trips; host kills the plugin and emits - /// `CLA-INFRA-PLUGIN-DISABLED-PATH-ESCAPE`. + /// `LMWV-INFRA-PLUGIN-DISABLED-PATH-ESCAPE`. #[test] fn t6_eleven_path_escapes_trip_breaker() { let manifest = compliant_manifest(); @@ -2072,7 +2072,7 @@ ontology_version = "0.1.0" /// T8 — oversize-field enforcement: a plugin emits one entity whose /// `qualified_name` exceeds [`MAX_ENTITY_FIELD_BYTES`]. The host drops it /// before the identity check's `format!()` would allocate a duplicate, - /// emits `CLA-INFRA-PLUGIN-ENTITY-FIELD-OVERSIZE`, and the plugin stays + /// emits `LMWV-INFRA-PLUGIN-ENTITY-FIELD-OVERSIZE`, and the plugin stays /// alive (the cap is per-entity; one offender is not a kill trigger). /// /// Verifies the `DoS` amplification fix from review-2. Builds the @@ -2137,7 +2137,7 @@ ontology_version = "0.1.0" .iter() .find(|f| f.subcode == FINDING_ENTITY_FIELD_OVERSIZE) .unwrap_or_else(|| { - panic!("must have CLA-INFRA-PLUGIN-ENTITY-FIELD-OVERSIZE; got: {findings:?}") + panic!("must have LMWV-INFRA-PLUGIN-ENTITY-FIELD-OVERSIZE; got: {findings:?}") }); assert_eq!( offense.metadata.get("field").map(String::as_str), @@ -3044,7 +3044,7 @@ ontology_version = "0.1.0" "entities": [], "edges": [], "findings": [{ - "subcode": "CLA-MOCK-PYRIGHT-RESTART", + "subcode": "LMWV-MOCK-PYRIGHT-RESTART", "severity": "warning", "message": "pyright subprocess died and was restarted", "metadata": { "restart_count": 1 } @@ -3070,7 +3070,7 @@ ontology_version = "0.1.0" let findings = host.take_findings(); let finding = findings .iter() - .find(|f| f.subcode == "CLA-MOCK-PYRIGHT-RESTART") + .find(|f| f.subcode == "LMWV-MOCK-PYRIGHT-RESTART") .unwrap_or_else(|| panic!("plugin finding must survive host path; got: {findings:?}")); assert_eq!(finding.message, "pyright subprocess died and was restarted"); assert_eq!( diff --git a/crates/clarion-core/src/plugin/host_findings.rs b/crates/loomweave-core/src/plugin/host_findings.rs similarity index 92% rename from crates/clarion-core/src/plugin/host_findings.rs rename to crates/loomweave-core/src/plugin/host_findings.rs index fa0c15e6..4fc21e2c 100644 --- a/crates/clarion-core/src/plugin/host_findings.rs +++ b/crates/loomweave-core/src/plugin/host_findings.rs @@ -14,15 +14,15 @@ use crate::plugin::protocol::UnresolvedCallSite; /// Emitted when a plugin emits an entity whose `kind` is not in the manifest's /// `entity_kinds` list (ADR-022 ontology boundary). -pub const FINDING_UNDECLARED_KIND: &str = "CLA-INFRA-PLUGIN-UNDECLARED-KIND"; +pub const FINDING_UNDECLARED_KIND: &str = "LMWV-INFRA-PLUGIN-UNDECLARED-KIND"; /// Emitted when a plugin emits an entity whose `id` string does not match the /// expected `entity_id(plugin_id, kind, qualified_name)` (UQ-WP2-11). -pub const FINDING_ENTITY_ID_MISMATCH: &str = "CLA-INFRA-PLUGIN-ENTITY-ID-MISMATCH"; +pub const FINDING_ENTITY_ID_MISMATCH: &str = "LMWV-INFRA-PLUGIN-ENTITY-ID-MISMATCH"; /// Emitted when the manifest contains a capability not supported in v0.1 /// (ADR-021 §Layer 1). -pub const FINDING_UNSUPPORTED_CAPABILITY: &str = "CLA-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY"; +pub const FINDING_UNSUPPORTED_CAPABILITY: &str = "LMWV-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY"; /// Emitted when a plugin returns an entity whose JSON shape fails to /// deserialise into `RawEntity` (missing required field, wrong type, etc.). @@ -31,7 +31,7 @@ pub const FINDING_UNSUPPORTED_CAPABILITY: &str = "CLA-INFRA-MANIFEST-UNSUPPORTED /// the finding is the only signal the operator gets that the plugin emitted /// malformed output. Without this, a plugin bug that silently produces garbage /// for a subset of entities looks identical to "no entities found". -pub const FINDING_MALFORMED_ENTITY: &str = "CLA-INFRA-PLUGIN-MALFORMED-ENTITY"; +pub const FINDING_MALFORMED_ENTITY: &str = "LMWV-INFRA-PLUGIN-MALFORMED-ENTITY"; /// Emitted when the host is asked to analyze a file whose path is not /// representable as UTF-8. The wire protocol is JSON (UTF-8 only), so the host @@ -42,7 +42,7 @@ pub const FINDING_MALFORMED_ENTITY: &str = "CLA-INFRA-PLUGIN-MALFORMED-ENTITY"; /// the wire boundary would replace invalid bytes with U+FFFD, yielding a path /// the plugin cannot open and an obscure "plugin returned no entities" symptom. /// Failing loudly with this finding keeps the diagnostic at the host layer. -pub const FINDING_NON_UTF8_PATH: &str = "CLA-INFRA-HOST-NON-UTF8-PATH"; +pub const FINDING_NON_UTF8_PATH: &str = "LMWV-INFRA-HOST-NON-UTF8-PATH"; /// Emitted when a plugin returns an entity with a string field longer than /// `MAX_ENTITY_FIELD_BYTES`. Entity is dropped; plugin is not killed. @@ -52,32 +52,32 @@ pub const FINDING_NON_UTF8_PATH: &str = "CLA-INFRA-HOST-NON-UTF8-PATH"; /// identity check duplicates `qualified_name` through `format!()`, so the memory /// cost is at least 2x the incoming string per offending entity, making this a /// RAM-amplification vector even under the 8 MiB Content-Length ceiling. -pub const FINDING_ENTITY_FIELD_OVERSIZE: &str = "CLA-INFRA-PLUGIN-ENTITY-FIELD-OVERSIZE"; +pub const FINDING_ENTITY_FIELD_OVERSIZE: &str = "LMWV-INFRA-PLUGIN-ENTITY-FIELD-OVERSIZE"; /// Emitted when a plugin returns an edge whose JSON shape fails to deserialise /// into `RawEdge` (missing required field, wrong type, etc.). Symmetric with /// [`FINDING_MALFORMED_ENTITY`]; edge is dropped, run continues. -pub const FINDING_MALFORMED_EDGE: &str = "CLA-INFRA-PLUGIN-MALFORMED-EDGE"; +pub const FINDING_MALFORMED_EDGE: &str = "LMWV-INFRA-PLUGIN-MALFORMED-EDGE"; /// Emitted when a plugin emits an edge whose `kind` is not in the manifest's /// `edge_kinds` list (ADR-022 ontology boundary, edge variant). Drop + finding; /// no kill. -pub const FINDING_UNDECLARED_EDGE_KIND: &str = "CLA-INFRA-PLUGIN-UNDECLARED-EDGE-KIND"; +pub const FINDING_UNDECLARED_EDGE_KIND: &str = "LMWV-INFRA-PLUGIN-UNDECLARED-EDGE-KIND"; /// Emitted when a plugin returns an edge with a string field longer than /// `MAX_ENTITY_FIELD_BYTES`. Edge is dropped; plugin is not killed. Same /// rationale as [`FINDING_ENTITY_FIELD_OVERSIZE`] (RAM amplification). -pub const FINDING_EDGE_FIELD_OVERSIZE: &str = "CLA-INFRA-PLUGIN-EDGE-FIELD-OVERSIZE"; +pub const FINDING_EDGE_FIELD_OVERSIZE: &str = "LMWV-INFRA-PLUGIN-EDGE-FIELD-OVERSIZE"; /// Emitted when `stats.unresolved_call_sites` contains a row that cannot be /// tied back to the accepted entities and source bytes for this `analyze_file` /// response. The row is dropped; aggregate counters are retained. pub const FINDING_MALFORMED_UNRESOLVED_CALL_SITE: &str = - "CLA-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE"; + "LMWV-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE"; /// Emitted when a plugin-reported `analyze_file.findings[]` row fails host /// validation. The row is dropped and this infrastructure finding is retained. -pub const FINDING_MALFORMED_FINDING: &str = "CLA-INFRA-PLUGIN-MALFORMED-FINDING"; +pub const FINDING_MALFORMED_FINDING: &str = "LMWV-INFRA-PLUGIN-MALFORMED-FINDING"; /// Informational diagnostic accumulated during a host's lifetime. /// @@ -86,7 +86,7 @@ pub const FINDING_MALFORMED_FINDING: &str = "CLA-INFRA-PLUGIN-MALFORMED-FINDING" /// Findings; for Sprint 1 they are collected only. #[derive(Debug, Clone)] pub struct HostFinding { - /// Finding subcode, e.g. `"CLA-INFRA-PLUGIN-PATH-ESCAPE"`. + /// Finding subcode, e.g. `"LMWV-INFRA-PLUGIN-PATH-ESCAPE"`. pub subcode: String, /// Human-readable message. pub message: String, diff --git a/crates/clarion-core/src/plugin/jail.rs b/crates/loomweave-core/src/plugin/jail.rs similarity index 99% rename from crates/clarion-core/src/plugin/jail.rs rename to crates/loomweave-core/src/plugin/jail.rs index a5babeaa..705093a2 100644 --- a/crates/clarion-core/src/plugin/jail.rs +++ b/crates/loomweave-core/src/plugin/jail.rs @@ -1,4 +1,4 @@ -//! Path-jail enforcement for the Clarion plugin host. +//! Path-jail enforcement for the Loomweave plugin host. //! //! Implements ADR-021 §2a: every file path that a plugin names — whether in a //! request parameter or in a returned entity — must lie *inside* the project diff --git a/crates/clarion-core/src/plugin/limits.rs b/crates/loomweave-core/src/plugin/limits.rs similarity index 96% rename from crates/clarion-core/src/plugin/limits.rs rename to crates/loomweave-core/src/plugin/limits.rs index ae95d712..2741835c 100644 --- a/crates/clarion-core/src/plugin/limits.rs +++ b/crates/loomweave-core/src/plugin/limits.rs @@ -10,11 +10,11 @@ //! | ADR-021 §2c | Entity/edge/finding cap| [`EntityCountCap`] | //! | ADR-021 §2d | Virtual-address limit | [`apply_prlimit_as`] | //! -//! # Deferred: CLA-INFRA-PLUGIN-ENTITY-OVERRUN-WARNING +//! # Deferred: LMWV-INFRA-PLUGIN-ENTITY-OVERRUN-WARNING //! //! ADR-021 §2c also calls for a *warning* finding emitted when the cap is //! approached (e.g. at 80 % of `DEFAULT_MAX`). This warning finding -//! (`CLA-INFRA-PLUGIN-ENTITY-OVERRUN-WARNING`) is **not implemented in +//! (`LMWV-INFRA-PLUGIN-ENTITY-OVERRUN-WARNING`) is **not implemented in //! Sprint 1**. It requires the Filigree scan-result ingest path that lands in //! WP5/WP6; deferring avoids a hard dependency on that infrastructure. //! When the ingest path is ready, add a `try_admit_with_warning` variant that @@ -34,24 +34,24 @@ use thiserror::Error; // ── Finding subcode constants (ADR-021, consumed by Task 6) ────────────────── /// Finding subcode emitted when a plugin returns a path that escapes the jail. -pub const FINDING_PATH_ESCAPE: &str = "CLA-INFRA-PLUGIN-PATH-ESCAPE"; +pub const FINDING_PATH_ESCAPE: &str = "LMWV-INFRA-PLUGIN-PATH-ESCAPE"; /// Finding subcode emitted when the path-escape breaker trips and the plugin /// is killed (the "disabled" sense: further entities from this plugin are /// refused entirely). -pub const FINDING_DISABLED_PATH_ESCAPE: &str = "CLA-INFRA-PLUGIN-DISABLED-PATH-ESCAPE"; +pub const FINDING_DISABLED_PATH_ESCAPE: &str = "LMWV-INFRA-PLUGIN-DISABLED-PATH-ESCAPE"; /// Finding subcode emitted when `read_frame` rejects a frame because its /// `Content-Length` exceeds the configured [`ContentLengthCeiling`]. -pub const FINDING_FRAME_OVERSIZE: &str = "CLA-INFRA-PLUGIN-FRAME-OVERSIZE"; +pub const FINDING_FRAME_OVERSIZE: &str = "LMWV-INFRA-PLUGIN-FRAME-OVERSIZE"; /// Finding subcode emitted when [`EntityCountCap::try_admit`] returns /// [`CapExceeded`] and the supervisor stops processing plugin output. -pub const FINDING_ENTITY_CAP: &str = "CLA-INFRA-PLUGIN-ENTITY-CAP"; +pub const FINDING_ENTITY_CAP: &str = "LMWV-INFRA-PLUGIN-ENTITY-CAP"; /// Finding subcode emitted when the plugin process is killed by the OS due to /// exceeding its `RLIMIT_AS` memory ceiling (OOM kill on `RLIMIT_AS`). -pub const FINDING_OOM_KILLED: &str = "CLA-INFRA-PLUGIN-OOM-KILLED"; +pub const FINDING_OOM_KILLED: &str = "LMWV-INFRA-PLUGIN-OOM-KILLED"; // ── ContentLengthCeiling (ADR-021 §2b) ─────────────────────────────────────── @@ -291,7 +291,7 @@ pub const DEFAULT_MAX_NPROC: u64 = 32; /// Apply `RLIMIT_AS` to the current process. /// /// Called inside `CommandExt::pre_exec` (Task 6) so the limit applies to the -/// plugin child process, not the Clarion host process. Setting the limit in +/// plugin child process, not the Loomweave host process. Setting the limit in /// `pre_exec` is safe because `pre_exec` runs after `fork()` but before /// `exec()`, so only the child's address-space limit is affected. /// @@ -346,7 +346,7 @@ pub fn apply_prlimit_as(_max_rss_mib: u64) -> std::io::Result<()> { /// Emit a one-time warning on non-Linux/macOS platforms. /// -/// Uses `std::sync::Once` rather than `tracing` — clarion-core has no tracing +/// Uses `std::sync::Once` rather than `tracing` — loomweave-core has no tracing /// dep and we do not add one for this single warning (per task spec). #[cfg(not(any(target_os = "linux", target_os = "macos")))] fn warn_once_non_linux() { @@ -354,7 +354,7 @@ fn warn_once_non_linux() { static WARN: Once = Once::new(); WARN.call_once(|| { eprintln!( - "clarion: RLIMIT_AS enforcement is Linux/macOS only; \ + "loomweave: RLIMIT_AS enforcement is Linux/macOS only; \ plugin memory ceiling will not be applied on this platform" ); }); diff --git a/crates/clarion-core/src/plugin/manifest.rs b/crates/loomweave-core/src/plugin/manifest.rs similarity index 86% rename from crates/clarion-core/src/plugin/manifest.rs rename to crates/loomweave-core/src/plugin/manifest.rs index 18b90d8f..5bdf79eb 100644 --- a/crates/clarion-core/src/plugin/manifest.rs +++ b/crates/loomweave-core/src/plugin/manifest.rs @@ -5,7 +5,7 @@ //! # Usage //! //! ```no_run -//! use clarion_core::plugin::parse_manifest; +//! use loomweave_core::plugin::parse_manifest; //! //! let bytes = std::fs::read("plugin.toml").unwrap(); //! let manifest = parse_manifest(&bytes).unwrap(); @@ -29,15 +29,15 @@ pub const RESERVED_ENTITY_KINDS: &[&str] = &["file", "subsystem", "guidance"]; /// Rule-ID prefixes the core owns; plugins may not claim these (ADR-022 §Core owns). /// -/// `CLA-INFRA-` is core/pipeline-only; `CLA-FACT-` is shared (core or any tool may +/// `LMWV-INFRA-` is core/pipeline-only; `LMWV-FACT-` is shared (core or any tool may /// emit) but a plugin manifest may not claim it as *the plugin's* prefix. -const RESERVED_RULE_ID_PREFIXES: &[&str] = &["CLA-INFRA-", "CLA-FACT-"]; +const RESERVED_RULE_ID_PREFIXES: &[&str] = &["LMWV-INFRA-", "LMWV-FACT-"]; // ── Error type ──────────────────────────────────────────────────────────────── /// Errors returned by [`parse_manifest`] and [`Manifest::validate_for_v0_1`]. /// -/// Each variant corresponds to a `CLA-INFRA-MANIFEST-*` finding code that Task 6 +/// Each variant corresponds to a `LMWV-INFRA-MANIFEST-*` finding code that Task 6 /// surfaces in the `initialize` handshake reply. Use [`ManifestError::subcode`] to /// obtain the machine-readable finding code. #[derive(Debug, Error, PartialEq, Eq)] @@ -45,43 +45,43 @@ const RESERVED_RULE_ID_PREFIXES: &[&str] = &["CLA-INFRA-", "CLA-FACT-"]; pub enum ManifestError { /// TOML parse failure or a required field is absent. /// - /// Finding code: `CLA-INFRA-MANIFEST-MALFORMED`. - #[error("CLA-INFRA-MANIFEST-MALFORMED: {message}")] + /// Finding code: `LMWV-INFRA-MANIFEST-MALFORMED`. + #[error("LMWV-INFRA-MANIFEST-MALFORMED: {message}")] Malformed { message: String }, /// An identifier string fails the ADR-022 grammar `[a-z][a-z0-9_]*` (kinds) - /// or `CLA-[A-Z]+(-[A-Z0-9]+)+-` (rule-ID prefix). + /// or `LMWV-[A-Z]+(-[A-Z0-9]+)+-` (rule-ID prefix). /// - /// Finding code: `CLA-INFRA-MANIFEST-MALFORMED`. - #[error("CLA-INFRA-MANIFEST-MALFORMED: {field} {value:?} violates ADR-022 identifier grammar")] + /// Finding code: `LMWV-INFRA-MANIFEST-MALFORMED`. + #[error("LMWV-INFRA-MANIFEST-MALFORMED: {field} {value:?} violates ADR-022 identifier grammar")] GrammarViolation { field: &'static str, value: String }, /// A plugin manifest declares one of the core-reserved entity kinds. /// - /// Finding code: `CLA-INFRA-MANIFEST-RESERVED-KIND`. + /// Finding code: `LMWV-INFRA-MANIFEST-RESERVED-KIND`. #[error( - "CLA-INFRA-MANIFEST-RESERVED-KIND: entity kind {kind:?} is reserved by the core (ADR-022)" + "LMWV-INFRA-MANIFEST-RESERVED-KIND: entity kind {kind:?} is reserved by the core (ADR-022)" )] ReservedKind { kind: String }, /// A plugin manifest claims a rule-ID prefix owned by the core. /// - /// Finding code: `CLA-INFRA-RULE-ID-NAMESPACE`. + /// Finding code: `LMWV-INFRA-RULE-ID-NAMESPACE`. #[error( - "CLA-INFRA-RULE-ID-NAMESPACE: rule_id_prefix {prefix:?} is a core-reserved namespace (ADR-022)" + "LMWV-INFRA-RULE-ID-NAMESPACE: rule_id_prefix {prefix:?} is a core-reserved namespace (ADR-022)" )] ReservedPrefix { prefix: String }, /// A manifest declares a capability that v0.1 does not support. /// - /// Finding code: `CLA-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY`. + /// Finding code: `LMWV-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY`. /// /// This variant is produced by [`Manifest::validate_for_v0_1`], not by /// [`parse_manifest`]. The parser accepts the field faithfully; Task 6's /// supervisor calls `validate_for_v0_1` and surfaces this error as a /// handshake rejection. #[error( - "CLA-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY: capability {capability:?} is not supported in v0.1" + "LMWV-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY: capability {capability:?} is not supported in v0.1" )] UnsupportedCapability { capability: &'static str }, } @@ -89,17 +89,17 @@ pub enum ManifestError { impl ManifestError { /// Return the machine-readable finding code for this error. /// - /// Task 6 uses this to populate the `rule_id` field of the `CLA-INFRA-*` + /// Task 6 uses this to populate the `rule_id` field of the `LMWV-INFRA-*` /// finding emitted when a plugin fails to start. pub fn subcode(&self) -> &'static str { match self { ManifestError::Malformed { .. } | ManifestError::GrammarViolation { .. } => { - "CLA-INFRA-MANIFEST-MALFORMED" + "LMWV-INFRA-MANIFEST-MALFORMED" } - ManifestError::ReservedKind { .. } => "CLA-INFRA-MANIFEST-RESERVED-KIND", - ManifestError::ReservedPrefix { .. } => "CLA-INFRA-RULE-ID-NAMESPACE", + ManifestError::ReservedKind { .. } => "LMWV-INFRA-MANIFEST-RESERVED-KIND", + ManifestError::ReservedPrefix { .. } => "LMWV-INFRA-RULE-ID-NAMESPACE", ManifestError::UnsupportedCapability { .. } => { - "CLA-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY" + "LMWV-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY" } } } @@ -180,7 +180,7 @@ pub const MAX_INTEGRATIONS: usize = 64; #[derive(Debug, Clone, PartialEq, Deserialize)] #[serde(deny_unknown_fields)] pub struct PluginMeta { - /// Package name, e.g. `"clarion-plugin-python"`. Informational; hyphens allowed. + /// Package name, e.g. `"loomweave-plugin-python"`. Informational; hyphens allowed. pub name: String, /// Identifier fed to `entity_id()`, e.g. `"python"`. Must satisfy `[a-z][a-z0-9_]*` @@ -225,7 +225,7 @@ pub struct CapabilitiesRuntime { /// Must be > 0. pub expected_max_rss_mb: u64, - /// Declared per-file entity budget. Exceeding triggers `CLA-INFRA-PLUGIN-ENTITY-OVERRUN-WARNING` + /// Declared per-file entity budget. Exceeding triggers `LMWV-INFRA-PLUGIN-ENTITY-OVERRUN-WARNING` /// (implementation deferred to Tier B sprint). pub expected_entities_per_file: u64, @@ -234,7 +234,7 @@ pub struct CapabilitiesRuntime { /// `true` if the plugin needs to read paths outside the project root. /// - /// v0.1 refuses `true` at `initialize` with `CLA-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY`. + /// v0.1 refuses `true` at `initialize` with `LMWV-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY`. /// The parser accepts the field faithfully; [`Manifest::validate_for_v0_1`] performs /// the rejection check. pub reads_outside_project_root: bool, @@ -266,8 +266,8 @@ pub struct Ontology { #[serde(default)] pub edge_kinds: Vec, - /// Rule-ID prefix, e.g. `"CLA-PY-"`. Must end with `-` and match - /// `CLA-[A-Z]+(-[A-Z0-9]+)+-`. Must not be a core-reserved prefix. + /// Rule-ID prefix, e.g. `"LMWV-PY-"`. Must end with `-` and match + /// `LMWV-[A-Z]+(-[A-Z0-9]+)+-`. Must not be a core-reserved prefix. pub rule_id_prefix: String, /// Ontology version (semver). Bumped when entity/edge/rule set changes. @@ -514,16 +514,16 @@ fn validate_role_kinds( /// /// Rules: /// 1. Must end with `-`. -/// 2. Strip the trailing `-`; the remainder must match `CLA-[A-Z]+(-[A-Z0-9]+)+` -/// (one-or-more `-[A-Z0-9]+` segments after `CLA`, per ADR-022). -/// Implementation: split on `-`, verify the first segment is `CLA`, and each +/// 2. Strip the trailing `-`; the remainder must match `LMWV-[A-Z]+(-[A-Z0-9]+)+` +/// (one-or-more `-[A-Z0-9]+` segments after `LMWV`, per ADR-022). +/// Implementation: split on `-`, verify the first segment is `LMWV`, and each /// subsequent non-empty segment is `[A-Z0-9]+` (ASCII uppercase or digit). /// The `segments.len() < 2` guard below is how the `+` quantifier is enforced -/// without a regex engine — `CLA-` alone has only one segment and is rejected. +/// without a regex engine — `LMWV-` alone has only one segment and is rejected. /// -/// Examples of valid prefixes: `CLA-PY-`, `CLA-JAVA-`, `CLA-FOO-BAR-`. -/// Examples of invalid prefixes: `PY-`, `cla-py-`, `CLA-py-`, `CLA-PY` (no trailing -/// hyphen), `CLA-` (no segment after CLA), `CLA--PY-` (empty segment). +/// Examples of valid prefixes: `LMWV-PY-`, `LMWV-JAVA-`, `LMWV-FOO-BAR-`. +/// Examples of invalid prefixes: `PY-`, `lmwv-py-`, `LMWV-py-`, `LMWV-PY` (no trailing +/// hyphen), `LMWV-` (no segment after LMWV), `LMWV--PY-` (empty segment). fn validate_rule_id_prefix_grammar(prefix: &str) -> Result<(), ManifestError> { // Rule 1: must end with `-`. let Some(without_trailing) = prefix.strip_suffix('-') else { @@ -533,19 +533,19 @@ fn validate_rule_id_prefix_grammar(prefix: &str) -> Result<(), ManifestError> { }); }; - // Rule 2: split on `-`; first segment must be `CLA`; all subsequent segments - // must be non-empty `[A-Z0-9]+`; at least one segment must follow `CLA`. + // Rule 2: split on `-`; first segment must be `LMWV`; all subsequent segments + // must be non-empty `[A-Z0-9]+`; at least one segment must follow `LMWV`. let segments: Vec<&str> = without_trailing.split('-').collect(); - // First segment must be exactly `CLA`. - if segments.first().copied() != Some("CLA") { + // First segment must be exactly `LMWV`. + if segments.first().copied() != Some("LMWV") { return Err(ManifestError::GrammarViolation { field: "rule_id_prefix", value: prefix.to_owned(), }); } - // There must be at least one segment after `CLA`. + // There must be at least one segment after `LMWV`. if segments.len() < 2 { return Err(ManifestError::GrammarViolation { field: "rule_id_prefix", @@ -581,11 +581,11 @@ mod tests { /// The canonical valid manifest fixture (mirrors the L5 schema in §2). const VALID_MANIFEST: &str = r#" [plugin] -name = "clarion-plugin-python" +name = "loomweave-plugin-python" plugin_id = "mockplugin" version = "0.1.0" protocol_version = "1.0" -executable = "clarion-plugin-python" +executable = "loomweave-plugin-python" language = "python" extensions = ["py"] @@ -601,7 +601,7 @@ pin = "1.1.409" [ontology] entity_kinds = ["function", "class", "module", "decorator"] edge_kinds = ["imports", "calls", "decorates", "contains"] -rule_id_prefix = "CLA-PY-" +rule_id_prefix = "LMWV-PY-" ontology_version = "0.1.0" [ontology.roles] @@ -657,11 +657,11 @@ syntax_degraded_module = ["module"] let manifest = parse_manifest(VALID_MANIFEST.as_bytes()).unwrap(); // [plugin] - assert_eq!(manifest.plugin.name, "clarion-plugin-python"); + assert_eq!(manifest.plugin.name, "loomweave-plugin-python"); assert_eq!(manifest.plugin.plugin_id, "mockplugin"); assert_eq!(manifest.plugin.version, "0.1.0"); assert_eq!(manifest.plugin.protocol_version, "1.0"); - assert_eq!(manifest.plugin.executable, "clarion-plugin-python"); + assert_eq!(manifest.plugin.executable, "loomweave-plugin-python"); assert_eq!(manifest.plugin.language, "python"); assert_eq!(manifest.plugin.extensions, vec!["py"]); @@ -692,7 +692,7 @@ syntax_degraded_module = ["module"] manifest.ontology.edge_kinds, vec!["imports", "calls", "decorates", "contains"] ); - assert_eq!(manifest.ontology.rule_id_prefix, "CLA-PY-"); + assert_eq!(manifest.ontology.rule_id_prefix, "LMWV-PY-"); assert_eq!(manifest.ontology.ontology_version, "0.1.0"); assert_eq!(manifest.ontology.roles.file_scope, vec!["module"]); assert_eq!( @@ -740,7 +740,7 @@ reads_outside_project_root = false [ontology] entity_kinds = ["widget"] edge_kinds = ["contains", "calls"] -rule_id_prefix = "CLA-MY-" +rule_id_prefix = "LMWV-MY-" ontology_version = "0.1.0" "#; let manifest = parse_manifest(toml.as_bytes()).unwrap(); @@ -757,11 +757,11 @@ ontology_version = "0.1.0" fn positive_non_python_manifest_roles_parse_successfully() { let toml = r#" [plugin] -name = "clarion-plugin-fixture" +name = "loomweave-plugin-fixture" plugin_id = "fixture" version = "0.1.0" protocol_version = "1.0" -executable = "clarion-plugin-fixture" +executable = "loomweave-plugin-fixture" language = "fixture" extensions = ["mt"] @@ -774,7 +774,7 @@ reads_outside_project_root = false [ontology] entity_kinds = ["widget"] edge_kinds = ["uses"] -rule_id_prefix = "CLA-FIXTURE-" +rule_id_prefix = "LMWV-FIXTURE-" ontology_version = "0.1.0" [ontology.roles] @@ -796,11 +796,11 @@ file_scope = ["widget"] // WP3's plugin.toml adds [integrations.wardline]; must parse without error. let toml = r#" [plugin] -name = "clarion-plugin-python" +name = "loomweave-plugin-python" plugin_id = "python" version = "0.1.0" protocol_version = "1.0" -executable = "clarion-plugin-python" +executable = "loomweave-plugin-python" language = "python" extensions = ["py"] @@ -813,7 +813,7 @@ reads_outside_project_root = false [ontology] entity_kinds = ["function"] edge_kinds = [] -rule_id_prefix = "CLA-PY-" +rule_id_prefix = "LMWV-PY-" ontology_version = "0.1.0" [integrations.wardline] @@ -843,11 +843,11 @@ max_version = "1.0.0" use std::fmt::Write; let mut toml = String::from( r#"[plugin] -name = "clarion-plugin-x" +name = "loomweave-plugin-x" plugin_id = "x" version = "0.1.0" protocol_version = "1.0" -executable = "clarion-plugin-x" +executable = "loomweave-plugin-x" language = "x" extensions = ["x"] @@ -860,7 +860,7 @@ reads_outside_project_root = false [ontology] entity_kinds = ["widget"] edge_kinds = [] -rule_id_prefix = "CLA-X-" +rule_id_prefix = "LMWV-X-" ontology_version = "0.1.0" "#, @@ -883,15 +883,15 @@ ontology_version = "0.1.0" fn positive_plugin_id_can_differ_from_name() { // Verifies that [plugin].name (hyphens OK) and plugin_id (kind grammar) // are independently valid. This is the exact case that caused the - // wp2/wp3 contradiction: name = "clarion-plugin-python" (hyphens) while + // wp2/wp3 contradiction: name = "loomweave-plugin-python" (hyphens) while // the entity_id needed the segment "python". let toml = r#" [plugin] -name = "clarion-plugin-python" +name = "loomweave-plugin-python" plugin_id = "python" version = "0.1.0" protocol_version = "1.0" -executable = "clarion-plugin-python" +executable = "loomweave-plugin-python" language = "python" extensions = ["py"] @@ -904,11 +904,11 @@ reads_outside_project_root = false [ontology] entity_kinds = ["function"] edge_kinds = [] -rule_id_prefix = "CLA-PY-" +rule_id_prefix = "LMWV-PY-" ontology_version = "0.1.0" "#; let manifest = parse_manifest(toml.as_bytes()).unwrap(); - assert_eq!(manifest.plugin.name, "clarion-plugin-python"); + assert_eq!(manifest.plugin.name, "loomweave-plugin-python"); assert_eq!(manifest.plugin.plugin_id, "python"); } @@ -920,7 +920,7 @@ ontology_version = "0.1.0" // plugin_id is a required field (no serde default). let toml = manifest_without("plugin_id"); let err = parse_manifest(toml.as_bytes()).unwrap_err(); - assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-MALFORMED"); + assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-MALFORMED"); assert!(matches!(err, ManifestError::Malformed { .. })); } @@ -933,7 +933,7 @@ ontology_version = "0.1.0" // plugin_id from name. let toml = manifest_with(r#"plugin_id = "my-plugin""#); let err = parse_manifest(toml.as_bytes()).unwrap_err(); - assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-MALFORMED"); + assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-MALFORMED"); assert!(matches!( err, ManifestError::GrammarViolation { @@ -949,7 +949,7 @@ ontology_version = "0.1.0" fn negative_missing_plugin_name_returns_malformed() { let toml = manifest_without("name"); let err = parse_manifest(toml.as_bytes()).unwrap_err(); - assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-MALFORMED"); + assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-MALFORMED"); assert!(matches!(err, ManifestError::Malformed { .. })); } @@ -959,7 +959,7 @@ ontology_version = "0.1.0" fn negative_zero_rss_mb_rejected() { let toml = manifest_with("expected_max_rss_mb = 0"); let err = parse_manifest(toml.as_bytes()).unwrap_err(); - assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-MALFORMED"); + assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-MALFORMED"); assert!( matches!(err, ManifestError::Malformed { message } if message.contains("expected_max_rss_mb")) ); @@ -977,7 +977,7 @@ ontology_version = "0.1.0" "syntax_degraded_module = []", ); let err = parse_manifest(toml.as_bytes()).unwrap_err(); - assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-MALFORMED"); + assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-MALFORMED"); assert!( matches!(err, ManifestError::Malformed { message } if message.contains("entity_kinds")) ); @@ -1003,7 +1003,7 @@ ontology_version = "0.1.0" fn negative_entity_kind_uppercase_is_grammar_violation() { let toml = manifest_with(r#"entity_kinds = ["Function"]"#); let err = parse_manifest(toml.as_bytes()).unwrap_err(); - assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-MALFORMED"); + assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-MALFORMED"); assert!(matches!( err, ManifestError::GrammarViolation { field: "entity_kinds", value } if value == "Function" @@ -1014,7 +1014,7 @@ ontology_version = "0.1.0" fn negative_entity_kind_hyphen_is_grammar_violation() { let toml = manifest_with(r#"entity_kinds = ["func-tion"]"#); let err = parse_manifest(toml.as_bytes()).unwrap_err(); - assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-MALFORMED"); + assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-MALFORMED"); assert!(matches!( err, ManifestError::GrammarViolation { field: "entity_kinds", value } if value == "func-tion" @@ -1025,7 +1025,7 @@ ontology_version = "0.1.0" fn negative_entity_kind_digit_prefix_is_grammar_violation() { let toml = manifest_with(r#"entity_kinds = ["1function"]"#); let err = parse_manifest(toml.as_bytes()).unwrap_err(); - assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-MALFORMED"); + assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-MALFORMED"); assert!(matches!( err, ManifestError::GrammarViolation { field: "entity_kinds", value } if value == "1function" @@ -1036,10 +1036,10 @@ ontology_version = "0.1.0" #[test] fn negative_rule_id_prefix_no_cla_prefix_rejected() { - // "PY-" — does not start with CLA. + // "PY-" — does not start with LMWV. let toml = manifest_with(r#"rule_id_prefix = "PY-""#); let err = parse_manifest(toml.as_bytes()).unwrap_err(); - assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-MALFORMED"); + assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-MALFORMED"); assert!(matches!( err, ManifestError::GrammarViolation { field: "rule_id_prefix", value } if value == "PY-" @@ -1048,25 +1048,25 @@ ontology_version = "0.1.0" #[test] fn negative_rule_id_prefix_lowercase_rejected() { - // "cla-py-" — lowercase is invalid. - let toml = manifest_with(r#"rule_id_prefix = "cla-py-""#); + // "lmwv-py-" — lowercase is invalid. + let toml = manifest_with(r#"rule_id_prefix = "lmwv-py-""#); let err = parse_manifest(toml.as_bytes()).unwrap_err(); - assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-MALFORMED"); + assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-MALFORMED"); assert!(matches!( err, - ManifestError::GrammarViolation { field: "rule_id_prefix", value } if value == "cla-py-" + ManifestError::GrammarViolation { field: "rule_id_prefix", value } if value == "lmwv-py-" )); } #[test] fn negative_rule_id_prefix_mixed_case_segment_rejected() { - // "CLA-py-" — mixed-case segment after CLA. - let toml = manifest_with(r#"rule_id_prefix = "CLA-py-""#); + // "LMWV-py-" — mixed-case segment after LMWV. + let toml = manifest_with(r#"rule_id_prefix = "LMWV-py-""#); let err = parse_manifest(toml.as_bytes()).unwrap_err(); - assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-MALFORMED"); + assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-MALFORMED"); assert!(matches!( err, - ManifestError::GrammarViolation { field: "rule_id_prefix", value } if value == "CLA-py-" + ManifestError::GrammarViolation { field: "rule_id_prefix", value } if value == "LMWV-py-" )); } @@ -1076,7 +1076,7 @@ ontology_version = "0.1.0" fn negative_reserved_entity_kind_file_rejected() { let toml = manifest_with(r#"entity_kinds = ["file", "widget"]"#); let err = parse_manifest(toml.as_bytes()).unwrap_err(); - assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-RESERVED-KIND"); + assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-RESERVED-KIND"); assert!(matches!( err, ManifestError::ReservedKind { kind } if kind == "file" @@ -1087,7 +1087,7 @@ ontology_version = "0.1.0" fn negative_reserved_entity_kind_subsystem_rejected() { let toml = manifest_with(r#"entity_kinds = ["subsystem"]"#); let err = parse_manifest(toml.as_bytes()).unwrap_err(); - assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-RESERVED-KIND"); + assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-RESERVED-KIND"); assert!(matches!( err, ManifestError::ReservedKind { kind } if kind == "subsystem" @@ -1098,7 +1098,7 @@ ontology_version = "0.1.0" fn negative_reserved_entity_kind_guidance_rejected() { let toml = manifest_with(r#"entity_kinds = ["guidance"]"#); let err = parse_manifest(toml.as_bytes()).unwrap_err(); - assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-RESERVED-KIND"); + assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-RESERVED-KIND"); assert!(matches!( err, ManifestError::ReservedKind { kind } if kind == "guidance" @@ -1109,23 +1109,23 @@ ontology_version = "0.1.0" #[test] fn negative_reserved_prefix_cla_infra_rejected() { - let toml = manifest_with(r#"rule_id_prefix = "CLA-INFRA-""#); + let toml = manifest_with(r#"rule_id_prefix = "LMWV-INFRA-""#); let err = parse_manifest(toml.as_bytes()).unwrap_err(); - assert_eq!(err.subcode(), "CLA-INFRA-RULE-ID-NAMESPACE"); + assert_eq!(err.subcode(), "LMWV-INFRA-RULE-ID-NAMESPACE"); assert!(matches!( err, - ManifestError::ReservedPrefix { prefix } if prefix == "CLA-INFRA-" + ManifestError::ReservedPrefix { prefix } if prefix == "LMWV-INFRA-" )); } #[test] fn negative_reserved_prefix_cla_fact_rejected() { - let toml = manifest_with(r#"rule_id_prefix = "CLA-FACT-""#); + let toml = manifest_with(r#"rule_id_prefix = "LMWV-FACT-""#); let err = parse_manifest(toml.as_bytes()).unwrap_err(); - assert_eq!(err.subcode(), "CLA-INFRA-RULE-ID-NAMESPACE"); + assert_eq!(err.subcode(), "LMWV-INFRA-RULE-ID-NAMESPACE"); assert!(matches!( err, - ManifestError::ReservedPrefix { prefix } if prefix == "CLA-FACT-" + ManifestError::ReservedPrefix { prefix } if prefix == "LMWV-FACT-" )); } @@ -1141,7 +1141,7 @@ ontology_version = "0.1.0" // validate_for_v0_1 must surface the unsupported-capability error. let err = manifest.validate_for_v0_1().unwrap_err(); - assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY"); + assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY"); assert!(matches!( err, ManifestError::UnsupportedCapability { @@ -1159,7 +1159,7 @@ ontology_version = "0.1.0" message: String::new() } .subcode(), - "CLA-INFRA-MANIFEST-MALFORMED" + "LMWV-INFRA-MANIFEST-MALFORMED" ); assert_eq!( ManifestError::GrammarViolation { @@ -1167,25 +1167,25 @@ ontology_version = "0.1.0" value: String::new() } .subcode(), - "CLA-INFRA-MANIFEST-MALFORMED" + "LMWV-INFRA-MANIFEST-MALFORMED" ); assert_eq!( ManifestError::ReservedKind { kind: String::new() } .subcode(), - "CLA-INFRA-MANIFEST-RESERVED-KIND" + "LMWV-INFRA-MANIFEST-RESERVED-KIND" ); assert_eq!( ManifestError::ReservedPrefix { prefix: String::new() } .subcode(), - "CLA-INFRA-RULE-ID-NAMESPACE" + "LMWV-INFRA-RULE-ID-NAMESPACE" ); assert_eq!( ManifestError::UnsupportedCapability { capability: "x" }.subcode(), - "CLA-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY" + "LMWV-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY" ); } @@ -1193,10 +1193,10 @@ ontology_version = "0.1.0" #[test] fn negative_rule_id_prefix_no_trailing_hyphen_rejected() { - // "CLA-PY" — missing trailing `-`. - let toml = manifest_with(r#"rule_id_prefix = "CLA-PY""#); + // "LMWV-PY" — missing trailing `-`. + let toml = manifest_with(r#"rule_id_prefix = "LMWV-PY""#); let err = parse_manifest(toml.as_bytes()).unwrap_err(); - assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-MALFORMED"); + assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-MALFORMED"); assert!(matches!( err, ManifestError::GrammarViolation { @@ -1208,10 +1208,10 @@ ontology_version = "0.1.0" #[test] fn negative_rule_id_prefix_empty_inner_segment_rejected() { - // "CLA--PY-" — empty segment between hyphens. - let toml = manifest_with(r#"rule_id_prefix = "CLA--PY-""#); + // "LMWV--PY-" — empty segment between hyphens. + let toml = manifest_with(r#"rule_id_prefix = "LMWV--PY-""#); let err = parse_manifest(toml.as_bytes()).unwrap_err(); - assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-MALFORMED"); + assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-MALFORMED"); assert!(matches!( err, ManifestError::GrammarViolation { @@ -1223,10 +1223,10 @@ ontology_version = "0.1.0" #[test] fn negative_rule_id_prefix_only_cla_rejected() { - // "CLA-" — no segment after CLA. - let toml = manifest_with(r#"rule_id_prefix = "CLA-""#); + // "LMWV-" — no segment after LMWV. + let toml = manifest_with(r#"rule_id_prefix = "LMWV-""#); let err = parse_manifest(toml.as_bytes()).unwrap_err(); - assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-MALFORMED"); + assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-MALFORMED"); assert!(matches!( err, ManifestError::GrammarViolation { @@ -1238,10 +1238,10 @@ ontology_version = "0.1.0" #[test] fn positive_multi_segment_rule_id_prefix_valid() { - // "CLA-FOO-BAR-" — valid multi-segment prefix. - let toml = manifest_with(r#"rule_id_prefix = "CLA-FOO-BAR-""#); + // "LMWV-FOO-BAR-" — valid multi-segment prefix. + let toml = manifest_with(r#"rule_id_prefix = "LMWV-FOO-BAR-""#); let manifest = parse_manifest(toml.as_bytes()).unwrap(); - assert_eq!(manifest.ontology.rule_id_prefix, "CLA-FOO-BAR-"); + assert_eq!(manifest.ontology.rule_id_prefix, "LMWV-FOO-BAR-"); } // ── Extension format grammar (ticket clarion-fa35cad487) ───────────────── @@ -1266,7 +1266,7 @@ reads_outside_project_root = false [ontology] entity_kinds = ["widget"] edge_kinds = [] -rule_id_prefix = "CLA-MY-" +rule_id_prefix = "LMWV-MY-" ontology_version = "0.1.0" "# ) @@ -1281,7 +1281,7 @@ ontology_version = "0.1.0" #[test] fn negative_extension_uppercase_rejected() { let err = parse_manifest(ext_manifest(r#"["PY"]"#).as_bytes()).unwrap_err(); - assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-MALFORMED"); + assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-MALFORMED"); assert!(matches!( err, ManifestError::GrammarViolation { field: "extensions", value } if value == "PY" @@ -1291,7 +1291,7 @@ ontology_version = "0.1.0" #[test] fn negative_extension_with_dot_rejected() { let err = parse_manifest(ext_manifest(r#"[".py"]"#).as_bytes()).unwrap_err(); - assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-MALFORMED"); + assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-MALFORMED"); assert!(matches!( err, ManifestError::GrammarViolation { field: "extensions", value } if value == ".py" @@ -1301,7 +1301,7 @@ ontology_version = "0.1.0" #[test] fn negative_extension_empty_string_rejected() { let err = parse_manifest(ext_manifest(r#"[""]"#).as_bytes()).unwrap_err(); - assert_eq!(err.subcode(), "CLA-INFRA-MANIFEST-MALFORMED"); + assert_eq!(err.subcode(), "LMWV-INFRA-MANIFEST-MALFORMED"); assert!(matches!( err, ManifestError::GrammarViolation { field: "extensions", value } if value.is_empty() diff --git a/crates/clarion-core/src/plugin/mock.rs b/crates/loomweave-core/src/plugin/mock.rs similarity index 100% rename from crates/clarion-core/src/plugin/mock.rs rename to crates/loomweave-core/src/plugin/mock.rs diff --git a/crates/clarion-core/src/plugin/mod.rs b/crates/loomweave-core/src/plugin/mod.rs similarity index 95% rename from crates/clarion-core/src/plugin/mod.rs rename to crates/loomweave-core/src/plugin/mod.rs index ee556ea9..cf46643f 100644 --- a/crates/clarion-core/src/plugin/mod.rs +++ b/crates/loomweave-core/src/plugin/mod.rs @@ -6,7 +6,7 @@ //! - `transport` — Task 2: LSP-style Content-Length framing (L4). //! - `jail` — Task 4: path-jail enforcement (ADR-021 §2a). //! - `limits` — Task 4: core-enforced ceilings and circuit-breakers (ADR-021 §2b–§2d). -//! - `discovery` — Task 5: `$PATH` scanning for `clarion-plugin-*` executables (L9, ADR-021 §L9). +//! - `discovery` — Task 5: `$PATH` scanning for `loomweave-plugin-*` executables (L9, ADR-021 §L9). //! - `host` — Task 6: plugin-host supervisor (ADR-021 §Layer 2, ADR-022, UQ-WP2-11). //! - `host_findings` — `HostFinding` subcodes and constructors used by `host`. //! - `breaker` — Task 7: crash-loop breaker (ADR-002 + UQ-WP2-10). diff --git a/crates/clarion-core/src/plugin/protocol.rs b/crates/loomweave-core/src/plugin/protocol.rs similarity index 99% rename from crates/clarion-core/src/plugin/protocol.rs rename to crates/loomweave-core/src/plugin/protocol.rs index 6c0f7ee9..175b3152 100644 --- a/crates/clarion-core/src/plugin/protocol.rs +++ b/crates/loomweave-core/src/plugin/protocol.rs @@ -1,4 +1,4 @@ -//! JSON-RPC 2.0 protocol types for the Clarion plugin host. +//! JSON-RPC 2.0 protocol types for the Loomweave plugin host. //! //! # Design choice: Option B — struct-per-method with manual dispatch //! @@ -98,7 +98,7 @@ impl<'de> Deserialize<'de> for JsonRpcVersion { /// /// Wire shape: `{"jsonrpc":"2.0","method":"...","params":{...},"id":1}` /// -/// JSON-RPC 2.0 permits string, number, or null request IDs. Clarion narrows +/// JSON-RPC 2.0 permits string, number, or null request IDs. Loomweave narrows /// this to `i64` as an application-level policy because the plugin transport is /// core-initiated and core-controlled. Revisit this type before exposing the /// envelope to external JSON-RPC peers or plugins that originate arbitrary IDs. @@ -128,7 +128,7 @@ pub struct NotificationEnvelope { /// Wire shape (error): `{"jsonrpc":"2.0","error":{...},"id":1}` /// /// Like [`RequestEnvelope`], this intentionally accepts only `i64` IDs. That is -/// narrower than JSON-RPC 2.0 but matches Clarion's current core-issued request +/// narrower than JSON-RPC 2.0 but matches Loomweave's current core-issued request /// IDs; a plugin response with a string or null ID is treated as malformed /// plugin traffic rather than general JSON-RPC interop. /// @@ -677,7 +677,7 @@ mod tests { // initialize result let r = InitializeResult { - name: "clarion-plugin-python".to_owned(), + name: "loomweave-plugin-python".to_owned(), version: "0.1.0".to_owned(), ontology_version: "0.1.0".to_owned(), capabilities: serde_json::json!({"wardline_aware": true}), @@ -727,7 +727,7 @@ mod tests { extractor_parse_latency_ms: 5, }, findings: vec![AnalyzeFileFinding { - subcode: "CLA-PY-PYRIGHT-RESTART".to_owned(), + subcode: "LMWV-PY-PYRIGHT-RESTART".to_owned(), severity: Some("warning".to_owned()), message: "pyright subprocess died and was restarted".to_owned(), metadata: BTreeMap::new(), diff --git a/crates/clarion-core/src/plugin/transport.rs b/crates/loomweave-core/src/plugin/transport.rs similarity index 99% rename from crates/clarion-core/src/plugin/transport.rs rename to crates/loomweave-core/src/plugin/transport.rs index c3bab569..e8a5e060 100644 --- a/crates/clarion-core/src/plugin/transport.rs +++ b/crates/loomweave-core/src/plugin/transport.rs @@ -1,4 +1,4 @@ -//! LSP-style Content-Length framing for the Clarion plugin transport. +//! LSP-style Content-Length framing for the Loomweave plugin transport. //! //! Each frame is a self-describing byte sequence: //! diff --git a/crates/clarion-core/tests/fixtures/plugin.toml b/crates/loomweave-core/tests/fixtures/plugin.toml similarity index 78% rename from crates/clarion-core/tests/fixtures/plugin.toml rename to crates/loomweave-core/tests/fixtures/plugin.toml index 85a1173b..c385a6ba 100644 --- a/crates/clarion-core/tests/fixtures/plugin.toml +++ b/crates/loomweave-core/tests/fixtures/plugin.toml @@ -1,9 +1,9 @@ [plugin] -name = "clarion-plugin-fixture" +name = "loomweave-plugin-fixture" plugin_id = "fixture" version = "0.1.0" protocol_version = "1.0" -executable = "clarion-plugin-fixture" +executable = "loomweave-plugin-fixture" language = "fixture" extensions = ["mt"] @@ -16,7 +16,7 @@ reads_outside_project_root = false [ontology] entity_kinds = ["widget"] edge_kinds = ["uses"] -rule_id_prefix = "CLA-FIXTURE-" +rule_id_prefix = "LMWV-FIXTURE-" ontology_version = "0.1.0" [ontology.roles] diff --git a/crates/clarion-core/tests/fixtures/sample.mt b/crates/loomweave-core/tests/fixtures/sample.mt similarity index 100% rename from crates/clarion-core/tests/fixtures/sample.mt rename to crates/loomweave-core/tests/fixtures/sample.mt diff --git a/crates/clarion-core/tests/host_subprocess.rs b/crates/loomweave-core/tests/host_subprocess.rs similarity index 81% rename from crates/clarion-core/tests/host_subprocess.rs rename to crates/loomweave-core/tests/host_subprocess.rs index 18b4a77c..e3852793 100644 --- a/crates/clarion-core/tests/host_subprocess.rs +++ b/crates/loomweave-core/tests/host_subprocess.rs @@ -1,6 +1,6 @@ //! T1 — subprocess happy path integration test. //! -//! Spawns the `clarion-plugin-fixture` binary via [`PluginHost::spawn`], +//! Spawns the `loomweave-plugin-fixture` binary via [`PluginHost::spawn`], //! performs the full handshake, issues one `analyze_file` for a fixture file, //! receives one entity, shuts down cleanly, and asserts exit code 0. //! @@ -9,32 +9,37 @@ //! for binaries in the same crate; cross-crate binary resolution requires //! either `-Z bindeps` (unstable) or a runtime search. -use clarion_core::PluginHost; -use clarion_core::plugin::parse_manifest; +use loomweave_core::PluginHost; +use loomweave_core::plugin::parse_manifest; /// Path to the fixture plugin.toml — embedded at compile time. const FIXTURE_MANIFEST_BYTES: &[u8] = include_bytes!("fixtures/plugin.toml"); -/// Locate the `clarion-plugin-fixture` binary in the Cargo target directory. +/// Locate the `loomweave-fixture-plugin` binary in the Cargo target directory. +/// +/// The cargo artifact is named `loomweave-fixture-plugin` (off the +/// `loomweave-plugin-*` discovery glob — see the fixture crate's Cargo.toml). +/// Tests that spawn it stage it under its `loomweave-plugin-fixture` manifest +/// name first (see [`staged_fixture`]). /// /// Searches the standard Cargo output locations in order: -/// 1. `CARGO_BIN_EXE_clarion-plugin-fixture` env var (set by cargo nextest +/// 1. `CARGO_BIN_EXE_loomweave-fixture-plugin` env var (set by cargo nextest /// when artifact deps are enabled — future use). -/// 2. `/debug/clarion-plugin-fixture` (default dev build). -/// 3. `/release/clarion-plugin-fixture` (release build). +/// 2. `/debug/loomweave-fixture-plugin` (default dev build). +/// 3. `/release/loomweave-fixture-plugin` (release build). /// /// Builds the fixture on demand and panics with a clear message if the binary /// still cannot be found. fn fixture_binary_path() -> std::path::PathBuf { // Check if an explicit path was provided (e.g. by a future artifact dep). - if let Ok(path) = std::env::var("CARGO_BIN_EXE_clarion-plugin-fixture") { + if let Ok(path) = std::env::var("CARGO_BIN_EXE_loomweave-fixture-plugin") { return std::path::PathBuf::from(path); } // Locate the workspace target directory via CARGO_MANIFEST_DIR. // CARGO_MANIFEST_DIR for an integration test is the crate's directory. let manifest_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); - // clarion-core is at crates/clarion-core; workspace root is ../../ + // loomweave-core is at crates/loomweave-core; workspace root is ../../ let workspace_root = manifest_dir .parent() // crates/ .and_then(|p| p.parent()) // workspace root @@ -55,17 +60,38 @@ fn fixture_binary_path() -> std::path::PathBuf { } panic!( - "clarion-plugin-fixture binary not found. \ - Tried `cargo build -p clarion-plugin-fixture --bin clarion-plugin-fixture`. \ + "loomweave-fixture-plugin binary not found. \ + Tried `cargo build -p loomweave-plugin-fixture --bin loomweave-fixture-plugin`. \ Searched in: {}", target_dir.display() ); } +/// Stage the fixture binary under its manifest-declared basename +/// (`loomweave-plugin-fixture`) and return `(tempdir, staged_path)`. +/// +/// The cargo artifact is named `loomweave-fixture-plugin` (off the +/// `loomweave-plugin-*` discovery glob), so it cannot be spawned directly: +/// [`PluginHost::spawn`] requires the binary's basename to equal the manifest's +/// `plugin.executable` (`loomweave-plugin-fixture`). Staging a copy under that +/// name mirrors how a real install presents the binary. Keep the returned +/// `TempDir` alive for the duration of the spawn. +fn staged_fixture() -> (tempfile::TempDir, std::path::PathBuf) { + let dir = tempfile::TempDir::new().expect("create fixture staging dir"); + let staged = dir.path().join(format!( + "loomweave-plugin-fixture{}", + std::env::consts::EXE_SUFFIX + )); + // `copy` preserves the exec bit on Unix and compiles on all platforms + // (unlike `os::unix::fs::symlink`); this test is not `cfg(unix)`-gated. + std::fs::copy(fixture_binary_path(), &staged).expect("stage fixture binary"); + (dir, staged) +} + fn find_fixture_binary(target_dir: &std::path::Path) -> Option { for profile in &["debug", "release"] { let candidate = target_dir.join(profile).join(format!( - "clarion-plugin-fixture{}", + "loomweave-fixture-plugin{}", std::env::consts::EXE_SUFFIX )); if candidate.exists() { @@ -81,17 +107,17 @@ fn build_fixture_binary(workspace_root: &std::path::Path, target_dir: &std::path .current_dir(workspace_root) .arg("build") .arg("-p") - .arg("clarion-plugin-fixture") + .arg("loomweave-plugin-fixture") .arg("--bin") - .arg("clarion-plugin-fixture") + .arg("loomweave-fixture-plugin") .arg("--target-dir") .arg(target_dir) .output() - .expect("spawn cargo build for clarion-plugin-fixture"); + .expect("spawn cargo build for loomweave-plugin-fixture"); assert!( output.status.success(), - "cargo build for clarion-plugin-fixture failed with status {}.\nstdout:\n{}\nstderr:\n{}", + "cargo build for loomweave-plugin-fixture failed with status {}.\nstdout:\n{}\nstderr:\n{}", output.status, String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr) @@ -105,7 +131,7 @@ fn fixture_manifest_parses_correctly() { let manifest = parse_manifest(FIXTURE_MANIFEST_BYTES).expect("fixture manifest must parse"); assert_eq!(manifest.plugin.plugin_id, "fixture"); assert_eq!(manifest.ontology.entity_kinds, vec!["widget"]); - assert_eq!(manifest.ontology.rule_id_prefix, "CLA-FIXTURE-"); + assert_eq!(manifest.ontology.rule_id_prefix, "LMWV-FIXTURE-"); assert!( !manifest.capabilities.runtime.reads_outside_project_root, "fixture manifest must not request reads_outside_project_root" @@ -129,8 +155,10 @@ fn t1_subprocess_happy_path() { let sample_path = project_dir.path().join("sample.mt"); std::fs::write(&sample_path, b"widget demo.sample {}\n").expect("write sample.mt"); - // 3. Spawn the plugin with the discovered binary path. - let exec = fixture_binary_path(); + // 3. Spawn the plugin with the discovered binary path, staged under its + // manifest-declared `loomweave-plugin-fixture` basename so spawn's + // basename check passes. + let (_fixture_stage, exec) = staged_fixture(); let (mut host, mut child) = PluginHost::spawn(manifest, project_dir.path(), &exec).expect("spawn must succeed"); @@ -203,13 +231,13 @@ fn t9_handshake_failure_on_immediate_exit_returns_err_promptly() { let project_dir = tempfile::TempDir::new().expect("tmpdir"); // Construct a symlink whose basename matches the manifest-declared - // `plugin.executable` (`clarion-plugin-fixture`) but whose target is + // `plugin.executable` (`loomweave-plugin-fixture`) but whose target is // `/bin/true`. This exits immediately without reading stdin, which is // the handshake-failure mode we want to test. Pointing `spawn` at // `/bin/true` directly would fail the basename-match check before // forking, which tests a different property. let stub_dir = tempfile::TempDir::new().expect("stub dir"); - let stub_exec = stub_dir.path().join("clarion-plugin-fixture"); + let stub_exec = stub_dir.path().join("loomweave-plugin-fixture"); std::os::unix::fs::symlink("/bin/true", &stub_exec).expect("symlink /bin/true"); let start = std::time::Instant::now(); @@ -243,7 +271,7 @@ fn t9a_handshake_failure_reaps_exited_subprocess() { let project_dir = tempfile::TempDir::new().expect("tmpdir"); let stub_dir = tempfile::TempDir::new().expect("stub dir"); let pid_file = stub_dir.path().join("plugin.pid"); - let stub_exec = stub_dir.path().join("clarion-plugin-fixture"); + let stub_exec = stub_dir.path().join("loomweave-plugin-fixture"); std::fs::write( &stub_exec, format!( @@ -321,7 +349,7 @@ fn t9b_stderr_tail_is_some_after_spawn() { let sample_path = project_dir.path().join("sample.mt"); std::fs::write(&sample_path, b"widget demo.sample {}\n").expect("write sample.mt"); - let exec = fixture_binary_path(); + let (_fixture_stage, exec) = staged_fixture(); let (mut host, mut child) = PluginHost::spawn(manifest, project_dir.path(), &exec).expect("spawn must succeed"); @@ -346,7 +374,7 @@ fn t9b_stderr_tail_is_some_after_spawn() { #[test] #[cfg(unix)] fn t10_manifest_executable_with_path_separator_is_refused() { - use clarion_core::HostError; + use loomweave_core::HostError; let mut manifest = parse_manifest(FIXTURE_MANIFEST_BYTES).expect("fixture manifest must parse"); manifest.plugin.executable = "/bin/sh".to_owned(); @@ -376,11 +404,11 @@ fn t10_manifest_executable_with_path_separator_is_refused() { #[test] #[cfg(unix)] fn t11_manifest_executable_basename_mismatch_is_refused() { - use clarion_core::HostError; + use loomweave_core::HostError; let mut manifest = parse_manifest(FIXTURE_MANIFEST_BYTES).expect("fixture manifest must parse"); // Declare a basename that will not match the discovered binary. - manifest.plugin.executable = "clarion-plugin-other".to_owned(); + manifest.plugin.executable = "loomweave-plugin-other".to_owned(); let project_dir = tempfile::TempDir::new().expect("tmpdir"); let exec = fixture_binary_path(); diff --git a/crates/clarion-federation/Cargo.toml b/crates/loomweave-federation/Cargo.toml similarity index 78% rename from crates/clarion-federation/Cargo.toml rename to crates/loomweave-federation/Cargo.toml index 1cc05352..27006e45 100644 --- a/crates/clarion-federation/Cargo.toml +++ b/crates/loomweave-federation/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "clarion-federation" +name = "loomweave-federation" version.workspace = true edition.workspace = true license.workspace = true @@ -10,7 +10,7 @@ rust-version.workspace = true workspace = true [dependencies] -clarion-core = { path = "../clarion-core", version = "1.3.0" } +loomweave-core = { path = "../loomweave-core", version = "1.0.0" } reqwest.workspace = true serde.workspace = true serde_json.workspace = true diff --git a/crates/clarion-federation/src/config.rs b/crates/loomweave-federation/src/config.rs similarity index 92% rename from crates/clarion-federation/src/config.rs rename to crates/loomweave-federation/src/config.rs index 92b316b6..e3b790a5 100644 --- a/crates/clarion-federation/src/config.rs +++ b/crates/loomweave-federation/src/config.rs @@ -39,13 +39,13 @@ impl McpConfig { || self.llm.anthropic_api_key_env.is_some() { return Err(ConfigError::DeprecatedProvider { - code: "CLA-CONFIG-DEPRECATED-PROVIDER", + code: "LMWV-CONFIG-DEPRECATED-PROVIDER", }); } if self.integrations.filigree.enabled && self.integrations.filigree.actor.trim().is_empty() { return Err(ConfigError::InvalidFiligreeActor { - code: "CLA-CONFIG-FILIGREE-ACTOR-BLANK", + code: "LMWV-CONFIG-FILIGREE-ACTOR-BLANK", }); } self.serve.http.validate_loopback_trust()?; @@ -90,7 +90,7 @@ impl Default for LlmConfig { } /// Semantic-search (embeddings) policy for `search_semantic` (`WS5b` / ADR-040). -/// **Opt-in, off by default** — mirrors [`LlmConfig`]; Loom is local-first, so +/// **Opt-in, off by default** — mirrors [`LlmConfig`]; Weft is local-first, so /// nothing here makes a hosted embedding service required. When `enabled` is /// false the `search_semantic` tool degrades honestly to "not enabled". #[derive(Debug, Clone, PartialEq, Deserialize)] @@ -170,8 +170,8 @@ pub struct OpenRouterAttributionConfig { impl Default for OpenRouterAttributionConfig { fn default() -> Self { Self { - referer: "https://github.com/tachyon-beep/clarion".to_owned(), - title: "Clarion".to_owned(), + referer: "https://github.com/foundryside-dev/loomweave".to_owned(), + title: "Loomweave".to_owned(), } } } @@ -286,7 +286,7 @@ pub struct ServeConfig { #[serde(default)] pub struct McpServeConfig { /// Enable MCP tools that can mutate state, spawn processes, or call an LLM. - /// Default false: `clarion serve` exposes consult-mode read tools unless an + /// Default false: `loomweave serve` exposes consult-mode read tools unless an /// operator explicitly opts into write-capable MCP operations. pub enable_write_tools: bool, } @@ -303,14 +303,14 @@ pub struct HttpReadConfig { /// `Authorization: Bearer `; the capabilities probe is /// always unauthenticated. When the env var is unset on a loopback /// bind, the surface stays unauthenticated (the v0.1 trust model). - /// When the env var is unset on a non-loopback bind, `clarion serve` - /// refuses to start (`CLA-CONFIG-HTTP-NO-AUTH`). Default - /// `CLARION_LOOM_TOKEN` matches Filigree's pinned client default. + /// When the env var is unset on a non-loopback bind, `loomweave serve` + /// refuses to start (`LMWV-CONFIG-HTTP-NO-AUTH`). Default + /// `WEFT_TOKEN` matches Filigree's pinned client default. pub token_env: String, - /// Optional env var holding the Loom component identity HMAC secret. - /// When configured, `clarion serve` refuses to start unless the env var + /// Optional env var holding the Weft component identity HMAC secret. + /// When configured, `loomweave serve` refuses to start unless the env var /// exists and protected HTTP read routes require - /// `X-Loom-Component: clarion:`. + /// `X-Weft-Component: loomweave:`. pub identity_token_env: Option, /// Enable the Wardline taint-store WRITE API (POST /api/wardline/taint-facts). /// Default false — `serve` is read-only unless explicitly opted in (ADR-036). @@ -325,7 +325,7 @@ impl Default for HttpReadConfig { enabled: false, bind: SocketAddr::from(([127, 0, 0, 1], 9111)), allow_non_loopback: false, - token_env: "CLARION_LOOM_TOKEN".to_owned(), + token_env: "WEFT_TOKEN".to_owned(), identity_token_env: None, wardline_taint_write: false, } @@ -336,7 +336,7 @@ impl HttpReadConfig { pub fn validate_loopback_trust(&self) -> Result<(), ConfigError> { if self.enabled && !self.allow_non_loopback && !self.is_loopback_bind() { return Err(ConfigError::NonLoopbackHttpBind { - code: "CLA-CONFIG-HTTP-NON-LOOPBACK", + code: "LMWV-CONFIG-HTTP-NON-LOOPBACK", bind: self.bind, }); } @@ -361,7 +361,7 @@ impl HttpReadConfig { .is_some_and(|value| !value.trim().is_empty()); if !has_secret { return Err(ConfigError::MissingHttpIdentitySecret { - code: "CLA-CONFIG-HTTP-IDENTITY-MISSING", + code: "LMWV-CONFIG-HTTP-IDENTITY-MISSING", token_env: env_var.to_owned(), }); } @@ -382,7 +382,7 @@ impl HttpReadConfig { return Ok(()); } Err(ConfigError::NonLoopbackHttpNoAuth { - code: "CLA-CONFIG-HTTP-NO-AUTH", + code: "LMWV-CONFIG-HTTP-NO-AUTH", bind: self.bind, token_env: self.token_env.clone(), }) @@ -411,16 +411,16 @@ pub struct FiligreeConfig { pub actor: String, pub token_env: String, pub timeout_seconds: u64, - /// Whether `clarion analyze` POSTs its findings to Filigree's + /// Whether `loomweave analyze` POSTs its findings to Filigree's /// `POST /api/v1/scan-results` intake on completion (WP9-B, - /// REQ-FINDING-03). Emission is a one-way Clarion→Filigree data egress, so + /// REQ-FINDING-03). Emission is a one-way Loomweave→Filigree data egress, so /// it is its own explicit opt-in: it requires both `enabled` *and* this /// flag, and **both default `false`**. Enabling the integration for the /// read side (`issues_for` reverse-lookup) therefore does not silently /// start outbound emission — the operator opts into the write direction /// separately by setting `emit_findings: true`. pub emit_findings: bool, - /// Age threshold (days) for `clarion analyze --prune-unseen` (REQ-FINDING-06): + /// Age threshold (days) for `loomweave analyze --prune-unseen` (REQ-FINDING-06): /// findings Filigree has marked `unseen_in_latest` and that are older than /// this are soft-archived (`fixed`) by the retention sweep. Default 30. /// Only consulted when `--prune-unseen` is passed; the sweep itself is @@ -433,7 +433,7 @@ impl Default for FiligreeConfig { Self { enabled: false, base_url: "http://127.0.0.1:8766".to_owned(), - actor: "clarion-mcp".to_owned(), + actor: "loomweave-mcp".to_owned(), token_env: "FILIGREE_API_TOKEN".to_owned(), timeout_seconds: 5, emit_findings: false, @@ -465,10 +465,10 @@ where match config.llm.provider { LlmProviderKind::Recording => Ok(ProviderSelection::Recording), LlmProviderKind::Anthropic => Err(ConfigError::DeprecatedProvider { - code: "CLA-CONFIG-DEPRECATED-PROVIDER", + code: "LMWV-CONFIG-DEPRECATED-PROVIDER", }), LlmProviderKind::OpenRouter => { - let live_env_opt_in = env_lookup("CLARION_LLM_LIVE").as_deref() == Some("1"); + let live_env_opt_in = env_lookup("LOOMWEAVE_LLM_LIVE").as_deref() == Some("1"); if !config.llm.allow_live_provider && !live_env_opt_in { return Ok(ProviderSelection::Disabled); } @@ -486,14 +486,14 @@ where }) } LlmProviderKind::CodexCli => { - let live_env_opt_in = env_lookup("CLARION_LLM_LIVE").as_deref() == Some("1"); + let live_env_opt_in = env_lookup("LOOMWEAVE_LLM_LIVE").as_deref() == Some("1"); if !config.llm.allow_live_provider && !live_env_opt_in { return Ok(ProviderSelection::Disabled); } Ok(ProviderSelection::CodexCli) } LlmProviderKind::ClaudeCli => { - let live_env_opt_in = env_lookup("CLARION_LLM_LIVE").as_deref() == Some("1"); + let live_env_opt_in = env_lookup("LOOMWEAVE_LLM_LIVE").as_deref() == Some("1"); if !config.llm.allow_live_provider && !live_env_opt_in { return Ok(ProviderSelection::Disabled); } @@ -526,7 +526,7 @@ pub enum ConfigError { InvalidFiligreeActor { code: &'static str }, #[error( - "{code}: serve.http.bind {bind} exposes the unauthenticated non-loopback Clarion HTTP read API; \ + "{code}: serve.http.bind {bind} exposes the unauthenticated non-loopback Loomweave HTTP read API; \ bind to loopback (127.0.0.1 or ::1) or set serve.http.allow_non_loopback: true only on a trusted network" )] NonLoopbackHttpBind { @@ -548,7 +548,7 @@ pub enum ConfigError { #[error( "{code}: serve.http.identity_token_env names ${token_env}, but that env var is unset; \ - refusing to start an HTTP read API with incomplete Loom component identity configuration." + refusing to start an HTTP read API with incomplete Weft component identity configuration." )] MissingHttpIdentitySecret { code: &'static str, @@ -556,7 +556,7 @@ pub enum ConfigError { }, #[error( - "{code}: clarion.yaml contains both `llm` and `llm_policy` top-level keys; \ + "{code}: loomweave.yaml contains both `llm` and `llm_policy` top-level keys; \ `llm_policy` is a serde alias for `llm` and serde silently discards one. \ Pick one and remove the other." )] @@ -581,7 +581,7 @@ fn reject_llm_policy_alias_collision(raw: &str) -> Result<(), ConfigError> { let has_llm_policy = mapping.contains_key("llm_policy"); if has_llm && has_llm_policy { return Err(ConfigError::AmbiguousLlmKey { - code: "CLA-CONFIG-AMBIGUOUS-LLM-KEY", + code: "LMWV-CONFIG-AMBIGUOUS-LLM-KEY", }); } Ok(()) @@ -604,15 +604,15 @@ llm: endpoint_url: http://localhost:4000/api/v1 api_key_env: TEST_OPENROUTER_KEY attribution: - referer: https://example.invalid/clarion - title: Clarion Test + referer: https://example.invalid/loomweave + title: Loomweave Test max_inferred_edges_per_caller: 3 cache_max_age_days: 7 integrations: filigree: enabled: true base_url: "http://127.0.0.1:9999" - actor: "clarion-test" + actor: "loomweave-test" token_env: TEST_FILIGREE_TOKEN timeout_seconds: 2 "#, @@ -630,15 +630,15 @@ integrations: assert_eq!(cfg.llm.openrouter.api_key_env, "TEST_OPENROUTER_KEY"); assert_eq!( cfg.llm.openrouter.attribution.referer, - "https://example.invalid/clarion" + "https://example.invalid/loomweave" ); - assert_eq!(cfg.llm.openrouter.attribution.title, "Clarion Test"); + assert_eq!(cfg.llm.openrouter.attribution.title, "Loomweave Test"); assert_eq!(cfg.llm.openrouter.timeout_seconds, 300); // default — not set in YAML assert_eq!(cfg.llm.max_inferred_edges_per_caller, 3); assert_eq!(cfg.llm.cache_max_age_days, 7); assert!(cfg.integrations.filigree.enabled); assert_eq!(cfg.integrations.filigree.base_url, "http://127.0.0.1:9999"); - assert_eq!(cfg.integrations.filigree.actor, "clarion-test"); + assert_eq!(cfg.integrations.filigree.actor, "loomweave-test"); assert_eq!(cfg.integrations.filigree.token_env, "TEST_FILIGREE_TOKEN"); assert_eq!(cfg.integrations.filigree.timeout_seconds, 2); } @@ -709,7 +709,7 @@ llm_policy: match err { ConfigError::AmbiguousLlmKey { code } => { - assert_eq!(code, "CLA-CONFIG-AMBIGUOUS-LLM-KEY"); + assert_eq!(code, "LMWV-CONFIG-AMBIGUOUS-LLM-KEY"); } other => panic!("expected AmbiguousLlmKey error, got: {other:?}"), } @@ -781,7 +781,7 @@ llm_policy: codex_cli: executable: /tmp/fake-codex model: gpt-5.5 - profile: clarion + profile: loomweave sandbox: read-only timeout_seconds: 30 ", @@ -792,7 +792,7 @@ llm_policy: assert_eq!(cfg.llm.model_id, "codex-cli-default"); assert_eq!(cfg.llm.codex_cli.executable, "/tmp/fake-codex"); assert_eq!(cfg.llm.codex_cli.model.as_deref(), Some("gpt-5.5")); - assert_eq!(cfg.llm.codex_cli.profile.as_deref(), Some("clarion")); + assert_eq!(cfg.llm.codex_cli.profile.as_deref(), Some("loomweave")); assert_eq!(cfg.llm.codex_cli.sandbox, CodexSandboxMode::ReadOnly); assert_eq!(cfg.llm.codex_cli.timeout_seconds, 30); @@ -817,7 +817,7 @@ llm_policy: assert_eq!(selected, ProviderSelection::Disabled); let env_selected = select_provider_with_env(&cfg, |name| { - (name == "CLARION_LLM_LIVE").then(|| "1".to_owned()) + (name == "LOOMWEAVE_LLM_LIVE").then(|| "1".to_owned()) }) .expect("provider selection via env opt-in"); assert_eq!(env_selected, ProviderSelection::CodexCli); @@ -881,7 +881,7 @@ llm_policy: assert_eq!(selected, ProviderSelection::Disabled); let env_selected = select_provider_with_env(&cfg, |name| { - (name == "CLARION_LLM_LIVE").then(|| "1".to_owned()) + (name == "LOOMWEAVE_LLM_LIVE").then(|| "1".to_owned()) }) .expect("provider selection via env opt-in"); assert_eq!(env_selected, ProviderSelection::ClaudeCli); @@ -947,14 +947,14 @@ serve: http: enabled: true bind: "127.0.0.1:0" - identity_token_env: CLARION_TEST_IDENTITY + identity_token_env: LOOMWEAVE_TEST_IDENTITY "#, ) .expect("parse HTTP identity_token_env"); assert_eq!( cfg.serve.http.identity_token_env.as_deref(), - Some("CLARION_TEST_IDENTITY") + Some("LOOMWEAVE_TEST_IDENTITY") ); } @@ -1080,7 +1080,7 @@ llm: .expect_err("old provider shape should be rejected"); assert!(matches!(err, ConfigError::DeprecatedProvider { .. })); - assert!(err.to_string().contains("CLA-CONFIG-DEPRECATED-PROVIDER")); + assert!(err.to_string().contains("LMWV-CONFIG-DEPRECATED-PROVIDER")); assert!(err.to_string().contains("provider: openrouter")); } @@ -1096,6 +1096,6 @@ integrations: ) .expect_err("blank Filigree actor should be rejected"); - assert!(err.to_string().contains("CLA-CONFIG-FILIGREE-ACTOR-BLANK")); + assert!(err.to_string().contains("LMWV-CONFIG-FILIGREE-ACTOR-BLANK")); } } diff --git a/crates/clarion-federation/src/filigree.rs b/crates/loomweave-federation/src/filigree.rs similarity index 90% rename from crates/clarion-federation/src/filigree.rs rename to crates/loomweave-federation/src/filigree.rs index 869d88b3..4ceda8a1 100644 --- a/crates/clarion-federation/src/filigree.rs +++ b/crates/loomweave-federation/src/filigree.rs @@ -1,11 +1,11 @@ -//! Filigree HTTP/MCP contract helpers for Clarion MCP. +//! Filigree HTTP/MCP contract helpers for Loomweave MCP. use std::io::{BufReader, Write}; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use std::time::Duration; -use clarion_core::plugin::{ContentLengthCeiling, Frame, read_frame, write_frame}; +use loomweave_core::plugin::{ContentLengthCeiling, Frame, read_frame, write_frame}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -20,9 +20,9 @@ pub struct EntityAssociationsResponse { pub associations: Vec, } -/// The subset of a Filigree issue Clarion surfaces alongside an +/// The subset of a Filigree issue Loomweave surfaces alongside an /// entity-association match: enough to render the match without an agent -/// having to call back into Filigree. Sourced from `GET /api/loom/issues/{id}`. +/// having to call back into Filigree. Sourced from `GET /api/weft/issues/{id}`. /// Unknown fields in the response are ignored, so Filigree can grow the route /// without breaking this read. #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] @@ -32,8 +32,8 @@ pub struct IssueDetail { pub priority: i64, } -/// Request Clarion sends to Filigree's observation scratchpad when an agent -/// proposes guidance. This is an observation, not a Clarion sheet. +/// Request Loomweave sends to Filigree's observation scratchpad when an agent +/// proposes guidance. This is an observation, not a Loomweave sheet. #[derive(Debug, Clone, PartialEq, Eq, Serialize)] pub struct ObservationCreateRequest { pub summary: String, @@ -51,7 +51,7 @@ pub struct ObservationCreateResponse { pub observation_id: String, } -/// Pending Filigree observation row, as read from `GET /api/loom/observations` +/// Pending Filigree observation row, as read from `GET /api/weft/observations` /// or from a test double. Unknown live fields are ignored. #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] pub struct ObservationRecord { @@ -72,17 +72,17 @@ pub struct ObservationRecord { #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct EntityAssociation { pub issue_id: String, - /// Opaque Clarion association key as stored by Filigree. New bindings use - /// the entity's SEI (`clarion:eid:*`); legacy rows may still carry the + /// Opaque Loomweave association key as stored by Filigree. New bindings use + /// the entity's SEI (`loomweave:eid:*`); legacy rows may still carry the /// mutable locator (`{plugin}:{kind}:{qualname}`). - pub clarion_entity_id: String, + pub loomweave_entity_id: String, pub content_hash_at_attach: String, pub attached_at: String, pub attached_by: String, } -/// One Wardline finding as Clarion surfaces it — the subset of Filigree's -/// `ScanFindingLoom` (`GET /api/loom/findings`) used for read-time +/// One Wardline finding as Loomweave surfaces it — the subset of Filigree's +/// `ScanFindingWeft` (`GET /api/weft/findings`) used for read-time /// reconciliation. Unknown fields are ignored so Filigree can grow the row. #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub struct WardlineFinding { @@ -106,13 +106,13 @@ pub struct WardlineFinding { pub metadata: serde_json::Value, } -/// Envelope returned by `GET /api/loom/findings` — the paged list of -/// [`WardlineFinding`] rows Clarion reconciles against. +/// Envelope returned by `GET /api/weft/findings` — the paged list of +/// [`WardlineFinding`] rows Loomweave reconciles against. #[derive(Debug, Clone, PartialEq, Deserialize)] pub struct WardlineFindingsResponse { #[serde(default)] pub items: Vec, - /// True when more findings pages follow. Clarion does not page the findings + /// True when more findings pages follow. Loomweave does not page the findings /// list (the offset param is unpinned in the federation contract); when this /// is true the first page is an incomplete view, so the caller fails closed /// to `unavailable` rather than silently undercounting the file's findings. @@ -120,20 +120,20 @@ pub struct WardlineFindingsResponse { pub has_more: bool, } -/// One row of `GET /api/loom/files` — only the fields needed to map a path to +/// One row of `GET /api/weft/files` — only the fields needed to map a path to /// Filigree's `file_id`. #[derive(Debug, Clone, PartialEq, Deserialize)] -pub struct LoomFileRecord { +pub struct WeftFileRecord { pub file_id: String, pub path: String, } -/// Envelope returned by `GET /api/loom/files` — the paged list of -/// [`LoomFileRecord`] rows Clarion uses to map a path to a `file_id`. +/// Envelope returned by `GET /api/weft/files` — the paged list of +/// [`WeftFileRecord`] rows Loomweave uses to map a path to a `file_id`. #[derive(Debug, Clone, PartialEq, Deserialize)] -pub struct LoomFilesResponse { +pub struct WeftFilesResponse { #[serde(default)] - pub items: Vec, + pub items: Vec, /// True when more pages follow. When the exact-path match is absent and /// `has_more` is true, the result is indeterminate — the file may be on a /// later page — so callers must degrade to `unavailable` rather than @@ -143,7 +143,7 @@ pub struct LoomFilesResponse { } #[derive(Debug, Clone, PartialEq, Deserialize)] -pub struct LoomObservationsResponse { +pub struct WeftObservationsResponse { #[serde(default)] pub items: Vec, #[serde(default)] @@ -160,7 +160,7 @@ pub fn parse_wardline_findings_response( serde_json::from_str(body).map_err(FiligreeContractError::from) } -pub fn parse_loom_files_response(body: &str) -> Result { +pub fn parse_weft_files_response(body: &str) -> Result { serde_json::from_str(body).map_err(FiligreeContractError::from) } @@ -257,7 +257,7 @@ pub trait FiligreeLookup: Send + Sync { Ok(None) } - /// Mark a pending observation as consumed after Clarion writes the local + /// Mark a pending observation as consumed after Loomweave writes the local /// guidance sheet. Default no-ops so promotion remains local-first if the /// scratchpad cleanup route is unavailable. fn dismiss_observation( @@ -315,7 +315,7 @@ impl FiligreeHttpClient { } /// POST a scan-results batch to Filigree's native intake (WP9-B, - /// REQ-FINDING-03). One-way Clarion→Filigree push; the caller is expected to + /// REQ-FINDING-03). One-way Loomweave→Filigree push; the caller is expected to /// inspect [`ScanResultsResponse::warnings`] (severity coercion, unknown /// `scan_run_id`, etc.) rather than just the counts. /// @@ -358,10 +358,10 @@ impl FiligreeHttpClient { } /// POST a retention sweep to Filigree's `clean-stale` route (REQ-FINDING-06, - /// `--prune-unseen`). One-way Clarion→Filigree call; Filigree soft-archives + /// `--prune-unseen`). One-way Loomweave→Filigree call; Filigree soft-archives /// its own `unseen_in_latest` findings for the given `scan_source`. The /// `scan_source` scoping is enforced server-side, so this can only sweep - /// Clarion's findings. + /// Loomweave's findings. /// /// # Errors /// @@ -501,20 +501,20 @@ impl FiligreeHttpClient { &mut stdin, &serde_json::json!({ "jsonrpc": "2.0", - "id": "clarion-init", + "id": "loomweave-init", "method": "initialize", "params": { "protocolVersion": "2025-11-25", "capabilities": {}, "clientInfo": { - "name": "clarion", + "name": "loomweave", "version": env!("CARGO_PKG_VERSION") } } }), tool, )?; - let _ = read_mcp_json(&mut stdout, "clarion-init", tool)?; + let _ = read_mcp_json(&mut stdout, "loomweave-init", tool)?; write_mcp_json( &mut stdin, @@ -530,7 +530,7 @@ impl FiligreeHttpClient { &mut stdin, &serde_json::json!({ "jsonrpc": "2.0", - "id": "clarion-call", + "id": "loomweave-call", "method": "tools/call", "params": { "name": tool, @@ -541,7 +541,7 @@ impl FiligreeHttpClient { )?; drop(stdin); - let response = read_mcp_json(&mut stdout, "clarion-call", tool)?; + let response = read_mcp_json(&mut stdout, "loomweave-call", tool)?; let _ = child.wait(); if let Some(error) = response.get("error") { return Err(FiligreeClientError::McpTool { @@ -592,8 +592,8 @@ impl FiligreeLookup for FiligreeHttpClient { ) -> Result, FiligreeClientError> { // Hop 1: path -> Filigree file_id. path_prefix is a prefix filter, so // take only the row whose path is byte-exact. - let files: LoomFilesResponse = - self.get_json(&loom_files_url(&self.base_url, "wardline", path))?; + let files: WeftFilesResponse = + self.get_json(&weft_files_url(&self.base_url, "wardline", path))?; let exact = files.items.into_iter().find(|f| f.path == path); let Some(file_id) = exact.map(|f| f.file_id) else { // No exact match on this page. If has_more is true the result is @@ -603,22 +603,22 @@ impl FiligreeLookup for FiligreeHttpClient { return Err(FiligreeClientError::HttpStatus { status: 0, body: - "loom/files truncated before exact path match; cannot conclude no findings" + "weft/files truncated before exact path match; cannot conclude no findings" .to_owned(), }); } return Ok(Vec::new()); }; - // Hop 2: file_id -> wardline findings. As with hop-1, Clarion reads only + // Hop 2: file_id -> wardline findings. As with hop-1, Loomweave reads only // the first page; if it is truncated (`has_more`) the findings view is // incomplete, so fail closed to `unavailable` rather than returning a // silent undercount. let findings: WardlineFindingsResponse = - self.get_json(&loom_findings_url(&self.base_url, "wardline", &file_id))?; + self.get_json(&weft_findings_url(&self.base_url, "wardline", &file_id))?; if findings.has_more { return Err(FiligreeClientError::HttpStatus { status: 0, - body: "loom/findings truncated; cannot enumerate all findings for file".to_owned(), + body: "weft/findings truncated; cannot enumerate all findings for file".to_owned(), }); } Ok(findings.items) @@ -653,8 +653,8 @@ impl FiligreeLookup for FiligreeHttpClient { let mut offset = 0_u64; let limit = 100_u64; loop { - let page: LoomObservationsResponse = - self.get_json(&loom_observations_url(&self.base_url, limit, offset))?; + let page: WeftObservationsResponse = + self.get_json(&weft_observations_url(&self.base_url, limit, offset))?; if let Some(found) = page .items .into_iter() @@ -696,7 +696,7 @@ pub fn parse_issue_detail_response(body: &str) -> Result String { format!( - "{}/api/loom/issues/{}", + "{}/api/weft/issues/{}", base_url.trim_end_matches('/'), percent_encode_query_value(issue_id) ) @@ -710,27 +710,27 @@ pub fn entity_associations_url(base_url: &str, entity_id: &str) -> String { ) } -pub fn loom_files_url(base_url: &str, scan_source: &str, path_prefix: &str) -> String { +pub fn weft_files_url(base_url: &str, scan_source: &str, path_prefix: &str) -> String { format!( - "{}/api/loom/files?scan_source={}&path_prefix={}", + "{}/api/weft/files?scan_source={}&path_prefix={}", base_url.trim_end_matches('/'), percent_encode_query_value(scan_source), percent_encode_query_value(path_prefix) ) } -pub fn loom_findings_url(base_url: &str, scan_source: &str, file_id: &str) -> String { +pub fn weft_findings_url(base_url: &str, scan_source: &str, file_id: &str) -> String { format!( - "{}/api/loom/findings?scan_source={}&file_id={}", + "{}/api/weft/findings?scan_source={}&file_id={}", base_url.trim_end_matches('/'), percent_encode_query_value(scan_source), percent_encode_query_value(file_id) ) } -pub fn loom_observations_url(base_url: &str, limit: u64, offset: u64) -> String { +pub fn weft_observations_url(base_url: &str, limit: u64, offset: u64) -> String { format!( - "{}/api/loom/observations?limit={}&offset={}", + "{}/api/weft/observations?limit={}&offset={}", base_url.trim_end_matches('/'), limit, offset @@ -780,7 +780,7 @@ fn read_mcp_json( } fn resolve_filigree_mcp_command(project_root: Option<&Path>) -> (String, Vec) { - if let Ok(raw) = std::env::var("CLARION_FILIGREE_MCP_COMMAND") { + if let Ok(raw) = std::env::var("LOOMWEAVE_FILIGREE_MCP_COMMAND") { let mut parts: Vec = raw .split_whitespace() .map(|part| replace_project_placeholder(part, project_root)) @@ -860,7 +860,7 @@ mod tests { "associations": [ { "issue_id": "filigree-1234567890", - "clarion_entity_id": "python:function:demo.hello", + "loomweave_entity_id": "python:function:demo.hello", "content_hash_at_attach": "hash-a", "attached_at": "2026-05-17T00:00:00.000Z", "attached_by": "codex" @@ -873,7 +873,7 @@ mod tests { assert_eq!(parsed.associations.len(), 1); let row = &parsed.associations[0]; assert_eq!(row.issue_id, "filigree-1234567890"); - assert_eq!(row.clarion_entity_id, "python:function:demo.hello"); + assert_eq!(row.loomweave_entity_id, "python:function:demo.hello"); assert_eq!(row.content_hash_at_attach, "hash-a"); assert_eq!(row.attached_at, "2026-05-17T00:00:00.000Z"); assert_eq!(row.attached_by, "codex"); @@ -886,7 +886,7 @@ mod tests { "associations": [ { "issue_id": "filigree-1234567890", - "clarion_entity_id": "clarion:eid:0123456789abcdef0123456789abcdef", + "loomweave_entity_id": "loomweave:eid:0123456789abcdef0123456789abcdef", "content_hash_at_attach": "hash-a", "attached_at": "2026-05-17T00:00:00.000Z", "attached_by": "codex" @@ -897,8 +897,8 @@ mod tests { .expect("parse SEI-keyed Filigree reverse route response"); assert_eq!( - parsed.associations[0].clarion_entity_id, - "clarion:eid:0123456789abcdef0123456789abcdef" + parsed.associations[0].loomweave_entity_id, + "loomweave:eid:0123456789abcdef0123456789abcdef" ); } @@ -916,12 +916,12 @@ mod tests { fn builds_reverse_route_url_with_encoded_sei_key() { let url = entity_associations_url( "http://127.0.0.1:8766/", - "clarion:eid:0123456789abcdef0123456789abcdef", + "loomweave:eid:0123456789abcdef0123456789abcdef", ); assert_eq!( url, - "http://127.0.0.1:8766/api/entity-associations?entity_id=clarion%3Aeid%3A0123456789abcdef0123456789abcdef" + "http://127.0.0.1:8766/api/entity-associations?entity_id=loomweave%3Aeid%3A0123456789abcdef0123456789abcdef" ); } @@ -937,10 +937,10 @@ mod tests { assert!(request.contains( "GET /api/entity-associations?entity_id=python%3Afunction%3Ademo.hello HTTP/1.1" )); - assert!(request.contains("x-filigree-actor: clarion-test")); + assert!(request.contains("x-filigree-actor: loomweave-test")); assert!(request.contains("authorization: Bearer secret-token")); - let body = r#"{"associations":[{"issue_id":"filigree-1234567890","clarion_entity_id":"python:function:demo.hello","content_hash_at_attach":"hash-a","attached_at":"2026-05-17T00:00:00.000Z","attached_by":"codex"}]}"#; + let body = r#"{"associations":[{"issue_id":"filigree-1234567890","loomweave_entity_id":"python:function:demo.hello","content_hash_at_attach":"hash-a","attached_at":"2026-05-17T00:00:00.000Z","attached_by":"codex"}]}"#; write!( stream, "HTTP/1.1 200 OK\r\ncontent-type: application/json\r\ncontent-length: {}\r\n\r\n{}", @@ -952,7 +952,7 @@ mod tests { let config = FiligreeConfig { enabled: true, base_url: format!("http://{addr}"), - actor: "clarion-test".to_owned(), + actor: "loomweave-test".to_owned(), token_env: "TEST_FILIGREE_TOKEN".to_owned(), timeout_seconds: 1, emit_findings: true, @@ -995,7 +995,7 @@ mod tests { let url = issue_detail_url("http://127.0.0.1:8542/", "clarion-51a2868c86"); assert_eq!( url, - "http://127.0.0.1:8542/api/loom/issues/clarion-51a2868c86" + "http://127.0.0.1:8542/api/weft/issues/clarion-51a2868c86" ); } @@ -1008,7 +1008,7 @@ mod tests { let mut request = [0_u8; 4096]; let read = stream.read(&mut request).expect("read request"); let request = String::from_utf8_lossy(&request[..read]); - assert!(request.contains("GET /api/loom/issues/clarion-51a2868c86 HTTP/1.1")); + assert!(request.contains("GET /api/weft/issues/clarion-51a2868c86 HTTP/1.1")); let body = r#"{"issue_id":"clarion-51a2868c86","title":"enrich","status":"proposed","priority":3}"#; write!( @@ -1049,7 +1049,7 @@ mod tests { }); let client = detail_test_client(addr); let detail = client - .issue_detail("clarion-missing") + .issue_detail("loomweave-missing") .expect("404 is Ok(None), not an error"); assert!(detail.is_none(), "404 degrades to None: {detail:?}"); handle.join().expect("server thread"); @@ -1068,11 +1068,11 @@ mod tests { request.contains("POST /api/v1/scan-results HTTP/1.1"), "request line: {request}" ); - assert!(request.contains("x-filigree-actor: clarion-test")); + assert!(request.contains("x-filigree-actor: loomweave-test")); assert!(request.contains("authorization: Bearer secret-token")); // The wire body carries the mapped severity, not the internal one. assert!( - request.contains("\"scan_source\":\"clarion\""), + request.contains("\"scan_source\":\"loomweave\""), "body: {request}" ); assert!( @@ -1096,7 +1096,7 @@ mod tests { let config = FiligreeConfig { enabled: true, base_url: format!("http://{addr}"), - actor: "clarion-test".to_owned(), + actor: "loomweave-test".to_owned(), token_env: "TEST_FILIGREE_TOKEN".to_owned(), timeout_seconds: 1, emit_findings: true, @@ -1110,7 +1110,7 @@ mod tests { let row = crate::scan_results::FindingForEmit { id: "core:finding:run-1:circular".to_owned(), - rule_id: "CLA-PY-STRUCTURE-001".to_owned(), + rule_id: "LMWV-PY-STRUCTURE-001".to_owned(), kind: "defect".to_owned(), severity: "WARN".to_owned(), confidence: Some(0.9), @@ -1182,7 +1182,7 @@ mod tests { } #[test] - fn parses_loom_findings_list_envelope() { + fn parses_weft_findings_list_envelope() { let resp = parse_wardline_findings_response( r#"{"items":[ {"finding_id":"f-1","file_id":"file-9","severity":"high","status":"open", @@ -1209,8 +1209,8 @@ mod tests { } #[test] - fn parses_loom_files_list_envelope() { - let resp = parse_loom_files_response( + fn parses_weft_files_list_envelope() { + let resp = parse_weft_files_response( r#"{"items":[ {"file_id":"file-9","path":"src/demo.py","language":"python","file_type":"source"}, {"file_id":"file-10","path":"src/demo_helpers.py","language":"python","file_type":"source"} @@ -1223,14 +1223,14 @@ mod tests { } #[test] - fn builds_loom_url_builders_with_encoding() { + fn builds_weft_url_builders_with_encoding() { assert_eq!( - loom_files_url("http://127.0.0.1:8542/", "wardline", "src/demo.py"), - "http://127.0.0.1:8542/api/loom/files?scan_source=wardline&path_prefix=src%2Fdemo.py" + weft_files_url("http://127.0.0.1:8542/", "wardline", "src/demo.py"), + "http://127.0.0.1:8542/api/weft/files?scan_source=wardline&path_prefix=src%2Fdemo.py" ); assert_eq!( - loom_findings_url("http://127.0.0.1:8542/", "wardline", "file-9"), - "http://127.0.0.1:8542/api/loom/findings?scan_source=wardline&file_id=file-9" + weft_findings_url("http://127.0.0.1:8542/", "wardline", "file-9"), + "http://127.0.0.1:8542/api/weft/findings?scan_source=wardline&file_id=file-9" ); } @@ -1239,14 +1239,14 @@ mod tests { let listener = TcpListener::bind("127.0.0.1:0").expect("bind test server"); let addr = listener.local_addr().expect("local addr"); let handle = std::thread::spawn(move || { - // Hop 1: GET /api/loom/files — path_prefix matches two files; the + // Hop 1: GET /api/weft/files — path_prefix matches two files; the // exact-path filter must pick file-9, not the helpers file. let (mut s1, _) = listener.accept().expect("accept files"); let mut buf = [0_u8; 4096]; let n = s1.read(&mut buf).expect("read files req"); let req = String::from_utf8_lossy(&buf[..n]); assert!(req.contains( - "GET /api/loom/files?scan_source=wardline&path_prefix=src%2Fdemo.py HTTP/1.1" + "GET /api/weft/files?scan_source=wardline&path_prefix=src%2Fdemo.py HTTP/1.1" )); let body = r#"{"items":[{"file_id":"file-9","path":"src/demo.py","language":"python","file_type":"source"},{"file_id":"file-10","path":"src/demo.py.bak","language":"python","file_type":"source"}],"has_more":false}"#; // connection: close forces reqwest to open a fresh TCP connection for @@ -1261,12 +1261,12 @@ mod tests { ) .unwrap(); - // Hop 2: GET /api/loom/findings for file-9. + // Hop 2: GET /api/weft/findings for file-9. let (mut s2, _) = listener.accept().expect("accept findings"); let n = s2.read(&mut buf).expect("read findings req"); let req = String::from_utf8_lossy(&buf[..n]); assert!( - req.contains("GET /api/loom/findings?scan_source=wardline&file_id=file-9 HTTP/1.1") + req.contains("GET /api/weft/findings?scan_source=wardline&file_id=file-9 HTTP/1.1") ); let body = r#"{"items":[{"finding_id":"f-1","file_id":"file-9","severity":"high","status":"open","scan_source":"wardline","rule_id":"WLN-TAINT-001","message":"sink","suggestion":"","scan_run_id":"r-1","line_start":12,"line_end":12,"fingerprint":"fp","issue_id":null,"seen_count":1,"metadata":{"wardline":{"qualname":"demo.Foo.bar"}},"data_warnings":[]}],"has_more":false}"#; write!( @@ -1283,7 +1283,7 @@ mod tests { let config = FiligreeConfig { enabled: true, base_url: format!("http://{addr}"), - actor: "clarion-test".to_owned(), + actor: "loomweave-test".to_owned(), token_env: "TEST_FILIGREE_TOKEN".to_owned(), timeout_seconds: 5, emit_findings: true, @@ -1315,7 +1315,7 @@ mod tests { let n = s1.read(&mut buf).expect("read files req"); let req = String::from_utf8_lossy(&buf[..n]); assert!(req.contains( - "GET /api/loom/files?scan_source=wardline&path_prefix=src%2Fdemo.py HTTP/1.1" + "GET /api/weft/files?scan_source=wardline&path_prefix=src%2Fdemo.py HTTP/1.1" )); // Return a page that omits the target path with has_more:true. let body = r#"{"items":[{"file_id":"file-1","path":"src/demo_other.py","language":"python","file_type":"source"}],"has_more":true}"#; @@ -1331,7 +1331,7 @@ mod tests { let config = FiligreeConfig { enabled: true, base_url: format!("http://{addr}"), - actor: "clarion-test".to_owned(), + actor: "loomweave-test".to_owned(), token_env: "TEST_FILIGREE_TOKEN".to_owned(), timeout_seconds: 5, emit_findings: true, @@ -1364,7 +1364,7 @@ mod tests { let n = s1.read(&mut buf).expect("read files req"); let req = String::from_utf8_lossy(&buf[..n]); assert!(req.contains( - "GET /api/loom/files?scan_source=wardline&path_prefix=src%2Fdemo.py HTTP/1.1" + "GET /api/weft/files?scan_source=wardline&path_prefix=src%2Fdemo.py HTTP/1.1" )); let body = r#"{"items":[{"file_id":"file-9","path":"src/demo.py","language":"python","file_type":"source"}],"has_more":false}"#; write!( @@ -1380,7 +1380,7 @@ mod tests { let n = s2.read(&mut buf).expect("read findings req"); let req = String::from_utf8_lossy(&buf[..n]); assert!( - req.contains("GET /api/loom/findings?scan_source=wardline&file_id=file-9 HTTP/1.1") + req.contains("GET /api/weft/findings?scan_source=wardline&file_id=file-9 HTTP/1.1") ); let body = r#"{"items":[{"finding_id":"f-1","file_id":"file-9","severity":"high","status":"open","scan_source":"wardline","rule_id":"WLN-TAINT-001","message":"sink","suggestion":"","scan_run_id":"r-1","line_start":12,"line_end":12,"fingerprint":"fp","issue_id":null,"seen_count":1,"metadata":{"wardline":{"qualname":"demo.Foo.bar"}},"data_warnings":[]}],"has_more":true}"#; write!( @@ -1394,7 +1394,7 @@ mod tests { let config = FiligreeConfig { enabled: true, base_url: format!("http://{addr}"), - actor: "clarion-test".to_owned(), + actor: "loomweave-test".to_owned(), token_env: "TEST_FILIGREE_TOKEN".to_owned(), timeout_seconds: 5, emit_findings: true, @@ -1415,7 +1415,7 @@ mod tests { let config = FiligreeConfig { enabled: true, base_url: format!("http://{addr}"), - actor: "clarion-test".to_owned(), + actor: "loomweave-test".to_owned(), token_env: "TEST_FILIGREE_TOKEN".to_owned(), timeout_seconds: 1, emit_findings: true, diff --git a/crates/clarion-federation/src/filigree_url.rs b/crates/loomweave-federation/src/filigree_url.rs similarity index 94% rename from crates/clarion-federation/src/filigree_url.rs rename to crates/loomweave-federation/src/filigree_url.rs index a8e90cbb..f7d208c2 100644 --- a/crates/clarion-federation/src/filigree_url.rs +++ b/crates/loomweave-federation/src/filigree_url.rs @@ -9,9 +9,9 @@ //! - `filigree/src/filigree/ephemeral.py::{write,read}_port_file` //! - `filigree/src/filigree/scanner_callback.py::resolve_scanner_api_url_with_source` //! -//! Federation discipline (`docs/suite/loom.md` §5): this is enrich-only -//! connection discovery. Clarion stays solo-useful — when no live port file is -//! present (or Filigree is disabled) Clarion falls back to its *own* configured +//! Federation discipline (`docs/suite/weft.md` §5): this is enrich-only +//! connection discovery. Loomweave stays solo-useful — when no live port file is +//! present (or Filigree is disabled) Loomweave falls back to its *own* configured //! `base_url`, never to a Filigree-internal default (copying Filigree's //! `DEFAULT_PORT` would be a silent cross-product coupling). Reading the port //! file is fail-soft: any missing/corrupt/out-of-range content degrades to the @@ -34,17 +34,17 @@ use crate::config::FiligreeConfig; pub const SOURCE_DISABLED: &str = "disabled"; /// The live ethereal port published by Filigree's running dashboard. pub const SOURCE_EPHEMERAL_PORT: &str = ".filigree/ephemeral.port"; -/// Clarion's own configured `integrations.filigree.base_url`. +/// Loomweave's own configured `integrations.filigree.base_url`. pub const SOURCE_CONFIG: &str = "config"; -/// The outcome of resolving where Clarion should reach Filigree's read API. +/// The outcome of resolving where Loomweave should reach Filigree's read API. #[derive(Debug, Clone, PartialEq, Eq, Serialize)] pub struct FiligreeUrlResolution { /// Whether the Filigree integration is enabled in config at all. pub enabled: bool, /// The statically configured base URL (`integrations.filigree.base_url`). pub configured_url: String, - /// The URL Clarion will actually call. `None` only when disabled. + /// The URL Loomweave will actually call. `None` only when disabled. pub resolved_url: Option, /// Which input produced [`Self::resolved_url`]; one of the `SOURCE_*` labels. pub source: &'static str, diff --git a/crates/clarion-federation/src/lib.rs b/crates/loomweave-federation/src/lib.rs similarity index 100% rename from crates/clarion-federation/src/lib.rs rename to crates/loomweave-federation/src/lib.rs diff --git a/crates/clarion-federation/src/scan_results.rs b/crates/loomweave-federation/src/scan_results.rs similarity index 85% rename from crates/clarion-federation/src/scan_results.rs rename to crates/loomweave-federation/src/scan_results.rs index 667bf10c..6910d684 100644 --- a/crates/clarion-federation/src/scan_results.rs +++ b/crates/loomweave-federation/src/scan_results.rs @@ -1,23 +1,23 @@ //! Filigree-native scan-results emission (WP9-B, REQ-FINDING-03). //! -//! Maps Clarion's persisted findings onto Filigree's `POST /api/v1/scan-results` +//! Maps Loomweave's persisted findings onto Filigree's `POST /api/v1/scan-results` //! intake schema (ADR-004 + detailed-design §7) and models the response. This //! module is pure — request building and response parsing only; the HTTP POST //! lives on [`crate::filigree::FiligreeHttpClient::post_scan_results`]. //! -//! Emission is enrich-only: a one-way Clarion→Filigree push that adds no -//! Filigree-side routes and never gates Clarion's own semantics. Clarion's -//! richer fields nest under `metadata.clarion.*` so Filigree's silent +//! Emission is enrich-only: a one-way Loomweave→Filigree push that adds no +//! Filigree-side routes and never gates Loomweave's own semantics. Loomweave's +//! richer fields nest under `metadata.loomweave.*` so Filigree's silent //! top-level-key drop (verified against the live intake) cannot lose them. use serde::{Deserialize, Serialize}; use serde_json::{Map, Value, json}; -/// The `scan_source` Clarion stamps on every emitted finding. Filigree's dedup +/// The `scan_source` Loomweave stamps on every emitted finding. Filigree's dedup /// key includes `scan_source`, so this is stable across runs. -pub const CLARION_SCAN_SOURCE: &str = "clarion"; +pub const LOOMWEAVE_SCAN_SOURCE: &str = "loomweave"; -/// Federation-owned Clarion finding projection for Filigree scan-results +/// Federation-owned Loomweave finding projection for Filigree scan-results /// emission. Storage maps persistence rows into this DTO at the caller /// boundary so this contract layer does not depend on `SQLite` row shape. #[derive(Debug, Clone, PartialEq)] @@ -38,11 +38,11 @@ pub struct FindingForEmit { pub source_line_end: Option, } -/// Map Clarion's internal severity vocabulary (`INFO` | `WARN` | `ERROR` | +/// Map Loomweave's internal severity vocabulary (`INFO` | `WARN` | `ERROR` | /// `CRITICAL` | `NONE`) to Filigree's wire vocabulary (detailed-design §7 /// table). Anything unrecognised — including `NONE` (facts) and `INFO` — maps /// to `info`, mirroring the coercion Filigree applies server-side, except done -/// here so the original survives in `metadata.clarion.internal_severity`. +/// here so the original survives in `metadata.loomweave.internal_severity`. /// /// This mapping is load-bearing: a live probe confirmed Filigree coerces an /// unmapped uppercase `WARN` to `info` (with a response warning), so emitting @@ -58,11 +58,11 @@ pub fn severity_to_wire(internal: &str) -> &'static str { } } -/// Knobs the emitter sets per `clarion analyze` invocation. `create_observations` -/// is always `false` (Clarion emits findings, not observations). +/// Knobs the emitter sets per `loomweave analyze` invocation. `create_observations` +/// is always `false` (Loomweave emits findings, not observations). #[derive(Debug, Clone)] pub struct EmitOptions { - /// Filigree's `scan_run_id`; Clarion passes its `run_id` here. An unknown + /// Filigree's `scan_run_id`; Loomweave passes its `run_id` here. An unknown /// id is tolerated by Filigree (it warns and proceeds), so this carries the /// REQ-FINDING-05 wire shape without a pre-create handshake. pub scan_run_id: Option, @@ -76,7 +76,7 @@ pub struct EmitOptions { /// rejects path-less findings, so when this is set such a finding emits /// against this stand-in path (the project root, mirroring the /// `core:project:*` finding anchor) and carries - /// `metadata.clarion.synthetic_anchor=true` so a consumer knows the path is a + /// `metadata.loomweave.synthetic_anchor=true` so a consumer knows the path is a /// placeholder for a non-file entity, not the finding's real location. When /// `None`, path-less findings are skipped (`skipped_no_path`) as before. pub default_path: Option, @@ -123,7 +123,7 @@ pub fn prepare_batch(rows: &[FindingForEmit], opts: &EmitOptions) -> PreparedBat let emitted = findings.len(); PreparedBatch { request: ScanResultsRequest { - scan_source: CLARION_SCAN_SOURCE.to_owned(), + scan_source: LOOMWEAVE_SCAN_SOURCE.to_owned(), scan_run_id: opts.scan_run_id.clone(), mark_unseen: opts.mark_unseen, create_observations: false, @@ -142,7 +142,7 @@ pub fn prepare_batch(rows: &[FindingForEmit], opts: &EmitOptions) -> PreparedBat /// `default_path` is the [`EmitOptions::default_path`] fallback: when the anchor /// entity has no `source_file_path` (a synthetic, non-file entity) but a fallback /// is supplied, the finding emits against it and is flagged -/// `metadata.clarion.synthetic_anchor=true`. A synthetic anchor never carries +/// `metadata.loomweave.synthetic_anchor=true`. A synthetic anchor never carries /// line numbers (the placeholder path has no meaningful position). fn wire_finding(row: &FindingForEmit, default_path: Option<&str>) -> Option { let row_path = row @@ -181,8 +181,8 @@ fn wire_finding(row: &FindingForEmit, default_path: Option<&str>) -> Option Value { let mut meta = Map::new(); meta.insert("kind".to_owned(), json!(row.kind)); @@ -193,30 +193,30 @@ fn wire_metadata(row: &FindingForEmit, synthetic_anchor: bool) -> Value { meta.insert("confidence_basis".to_owned(), json!(basis)); } - let mut clarion = Map::new(); - clarion.insert("entity_id".to_owned(), json!(row.entity_id)); - clarion.insert( + let mut loomweave = Map::new(); + loomweave.insert("entity_id".to_owned(), json!(row.entity_id)); + loomweave.insert( "related_entities".to_owned(), json_array_or_empty(&row.related_entities_json), ); - clarion.insert( + loomweave.insert( "supports".to_owned(), json_array_or_empty(&row.supports_json), ); - clarion.insert( + loomweave.insert( "supported_by".to_owned(), json_array_or_empty(&row.supported_by_json), ); // Lossless round-trip: the wire `severity` is the mapped value, so the // internal vocabulary is preserved here for read-back. - clarion.insert("internal_severity".to_owned(), json!(row.severity)); - clarion.insert("internal_status".to_owned(), json!("open")); + loomweave.insert("internal_severity".to_owned(), json!(row.severity)); + loomweave.insert("internal_status".to_owned(), json!("open")); // Flag the placeholder anchor so a consumer never mistakes the stand-in // `path` (the project root) for the finding's real file location. if synthetic_anchor { - clarion.insert("synthetic_anchor".to_owned(), json!(true)); + loomweave.insert("synthetic_anchor".to_owned(), json!(true)); } - meta.insert("clarion".to_owned(), Value::Object(clarion)); + meta.insert("loomweave".to_owned(), Value::Object(loomweave)); Value::Object(meta) } @@ -230,7 +230,7 @@ fn json_array_or_empty(raw: &str) -> Value { } /// Filigree's scan-results response. `#[serde(default)]` keeps the read -/// forward-compatible: Filigree may add fields without breaking Clarion. +/// forward-compatible: Filigree may add fields without breaking Loomweave. #[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize)] #[serde(default)] pub struct ScanResultsResponse { @@ -264,24 +264,24 @@ pub fn scan_results_url(base_url: &str) -> String { } /// The retention-sweep URL for a Filigree base URL (REQ-FINDING-06, -/// `--prune-unseen`). This is a **loom-generation** route (`/api/loom/…`), +/// `--prune-unseen`). This is a **weft-generation** route (`/api/weft/…`), /// unlike the classic `/api/v1/scan-results` emission intake — do not derive it /// from [`scan_results_url`]. Verified against Filigree's own route handler and /// API tests. #[must_use] pub fn clean_stale_url(base_url: &str) -> String { format!( - "{}/api/loom/findings/clean-stale", + "{}/api/weft/findings/clean-stale", base_url.trim_end_matches('/') ) } -/// The `POST /api/loom/findings/clean-stale` request body (REQ-FINDING-06). +/// The `POST /api/weft/findings/clean-stale` request body (REQ-FINDING-06). /// Filigree **soft-archives** `unseen_in_latest` findings older than /// `older_than_days`, scoped to `scan_source`, moving them to `fixed` status /// (they auto-reopen if a later scan re-detects them — see Filigree ADR-015). /// `scan_source` is required server-side as an accident-guard so a caller -/// cannot sweep every tool's findings; Clarion always sends `"clarion"`. +/// cannot sweep every tool's findings; Loomweave always sends `"loomweave"`. #[derive(Debug, Clone, PartialEq, Serialize)] pub struct CleanStaleRequest { pub scan_source: String, @@ -316,7 +316,7 @@ mod tests { fn defect_row() -> FindingForEmit { FindingForEmit { id: "core:finding:run-1:circular".to_owned(), - rule_id: "CLA-PY-STRUCTURE-001".to_owned(), + rule_id: "LMWV-PY-STRUCTURE-001".to_owned(), kind: "defect".to_owned(), severity: "WARN".to_owned(), confidence: Some(0.95), @@ -344,11 +344,11 @@ mod tests { } #[test] - fn wire_finding_carries_mapped_severity_and_nested_clarion_metadata() { + fn wire_finding_carries_mapped_severity_and_nested_loomweave_metadata() { let finding = wire_finding(&defect_row(), None).expect("path present"); assert_eq!(finding["path"], json!("src/auth/tokens.py")); - assert_eq!(finding["rule_id"], json!("CLA-PY-STRUCTURE-001")); + assert_eq!(finding["rule_id"], json!("LMWV-PY-STRUCTURE-001")); assert_eq!(finding["message"], json!("Circular import detected")); // Internal WARN maps to wire medium... assert_eq!(finding["severity"], json!("medium")); @@ -360,20 +360,20 @@ mod tests { assert_eq!(meta["confidence"], json!(0.95)); assert_eq!(meta["confidence_basis"], json!("ast_match")); - let clarion = &meta["clarion"]; + let loomweave = &meta["loomweave"]; assert_eq!( - clarion["entity_id"], + loomweave["entity_id"], json!("python:class:auth.tokens::TokenManager") ); assert_eq!( - clarion["related_entities"], + loomweave["related_entities"], json!(["python:class:auth.sessions::SessionStore"]) ); - assert_eq!(clarion["supports"], json!([])); - assert_eq!(clarion["supported_by"], json!([])); - // ...while the internal value round-trips under clarion.*. - assert_eq!(clarion["internal_severity"], json!("WARN")); - assert_eq!(clarion["internal_status"], json!("open")); + assert_eq!(loomweave["supports"], json!([])); + assert_eq!(loomweave["supported_by"], json!([])); + // ...while the internal value round-trips under loomweave.*. + assert_eq!(loomweave["internal_severity"], json!("WARN")); + assert_eq!(loomweave["internal_status"], json!("open")); } #[test] @@ -396,7 +396,7 @@ mod tests { meta.get("confidence_basis").is_none(), "confidence_basis omitted: {meta}" ); - assert_eq!(meta["clarion"]["internal_severity"], json!("NONE")); + assert_eq!(meta["loomweave"]["internal_severity"], json!("NONE")); } #[test] @@ -424,7 +424,7 @@ mod tests { let finding = wire_finding(&row, Some("/repo/root")).expect("emits via default path"); assert_eq!(finding["path"], json!("/repo/root")); assert_eq!( - finding["metadata"]["clarion"]["synthetic_anchor"], + finding["metadata"]["loomweave"]["synthetic_anchor"], json!(true) ); assert!( @@ -436,7 +436,7 @@ mod tests { let finding = wire_finding(&defect_row(), Some("/repo/root")).expect("path present"); assert_eq!(finding["path"], json!("src/auth/tokens.py")); assert!( - finding["metadata"]["clarion"] + finding["metadata"]["loomweave"] .get("synthetic_anchor") .is_none(), "real-path finding is not a synthetic anchor: {finding}" @@ -454,7 +454,7 @@ mod tests { row.related_entities_json = "not json".to_owned(); let finding = wire_finding(&row, None).expect("path present"); assert_eq!( - finding["metadata"]["clarion"]["related_entities"], + finding["metadata"]["loomweave"]["related_entities"], json!([]) ); } @@ -480,7 +480,7 @@ mod tests { assert_eq!(batch.emitted, 1); assert_eq!(batch.skipped_no_path, 1); assert_eq!(batch.request.findings.len(), 1); - assert_eq!(batch.request.scan_source, "clarion"); + assert_eq!(batch.request.scan_source, "loomweave"); assert_eq!(batch.request.scan_run_id.as_deref(), Some("run-1")); assert!(batch.request.mark_unseen); assert!(batch.request.complete_scan_run); @@ -500,7 +500,7 @@ mod tests { ); let value = serde_json::to_value(&batch.request).expect("serialize request"); - assert_eq!(value["scan_source"], json!("clarion")); + assert_eq!(value["scan_source"], json!("loomweave")); assert_eq!(value["scan_run_id"], json!("run-1")); assert_eq!(value["mark_unseen"], json!(true)); assert_eq!(value["create_observations"], json!(false)); @@ -578,41 +578,41 @@ mod tests { } #[test] - fn clean_stale_url_targets_the_loom_route() { - // Prune is a loom-generation route, distinct from the classic + fn clean_stale_url_targets_the_weft_route() { + // Prune is a weft-generation route, distinct from the classic // /api/v1 emission intake. assert_eq!( clean_stale_url("http://127.0.0.1:8542/"), - "http://127.0.0.1:8542/api/loom/findings/clean-stale" + "http://127.0.0.1:8542/api/weft/findings/clean-stale" ); assert_eq!( clean_stale_url("http://127.0.0.1:8542"), - "http://127.0.0.1:8542/api/loom/findings/clean-stale" + "http://127.0.0.1:8542/api/weft/findings/clean-stale" ); } #[test] fn clean_stale_request_serializes_to_filigree_wire_shape() { let request = CleanStaleRequest { - scan_source: CLARION_SCAN_SOURCE.to_owned(), + scan_source: LOOMWEAVE_SCAN_SOURCE.to_owned(), older_than_days: 30, - actor: "clarion-mcp".to_owned(), + actor: "loomweave-mcp".to_owned(), }; let value = serde_json::to_value(&request).expect("serialize clean-stale request"); - assert_eq!(value["scan_source"], json!("clarion")); + assert_eq!(value["scan_source"], json!("loomweave")); assert_eq!(value["older_than_days"], json!(30)); - assert_eq!(value["actor"], json!("clarion-mcp")); + assert_eq!(value["actor"], json!("loomweave-mcp")); } #[test] fn parses_clean_stale_response_shape() { // Pinned to Filigree's clean-stale handler response. let response = parse_clean_stale_response( - r#"{"findings_fixed": 4, "scan_source": "clarion", "older_than_days": 30}"#, + r#"{"findings_fixed": 4, "scan_source": "loomweave", "older_than_days": 30}"#, ) .expect("parse clean-stale response"); assert_eq!(response.findings_fixed, 4); - assert_eq!(response.scan_source, "clarion"); + assert_eq!(response.scan_source, "loomweave"); assert_eq!(response.older_than_days, 30); } diff --git a/crates/clarion-mcp/Cargo.toml b/crates/loomweave-mcp/Cargo.toml similarity index 71% rename from crates/clarion-mcp/Cargo.toml rename to crates/loomweave-mcp/Cargo.toml index 9955ff29..457cedcd 100644 --- a/crates/clarion-mcp/Cargo.toml +++ b/crates/loomweave-mcp/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "clarion-mcp" +name = "loomweave-mcp" version.workspace = true edition.workspace = true license.workspace = true @@ -12,9 +12,9 @@ workspace = true [dependencies] async-trait.workspace = true blake3.workspace = true -clarion-core = { path = "../clarion-core", version = "1.3.0" } -clarion-federation = { path = "../clarion-federation", version = "1.3.0" } -clarion-storage = { path = "../clarion-storage", version = "1.3.0" } +loomweave-core = { path = "../loomweave-core", version = "1.0.0" } +loomweave-federation = { path = "../loomweave-federation", version = "1.0.0" } +loomweave-storage = { path = "../loomweave-storage", version = "1.0.0" } reqwest.workspace = true rusqlite.workspace = true serde.workspace = true diff --git a/crates/clarion-mcp/assets/skills/clarion-workflow/SKILL.md b/crates/loomweave-mcp/assets/skills/loomweave-workflow/SKILL.md similarity index 90% rename from crates/clarion-mcp/assets/skills/clarion-workflow/SKILL.md rename to crates/loomweave-mcp/assets/skills/loomweave-workflow/SKILL.md index db6925b6..1b074574 100644 --- a/crates/clarion-mcp/assets/skills/clarion-workflow/SKILL.md +++ b/crates/loomweave-mcp/assets/skills/loomweave-workflow/SKILL.md @@ -1,21 +1,21 @@ --- -name: clarion-workflow +name: loomweave-workflow description: > Use when orienting in an unfamiliar or large codebase and you want to avoid re-reading or grepping the whole source tree: answering "what calls X", "where is X defined", "what does X depend on", "what subsystem is X in", or - "find the function/class/module that does Y". Applies whenever a Clarion - code-archaeology MCP server (clarion serve / mcp__clarion__* tools) is + "find the function/class/module that does Y". Applies whenever a Loomweave + code-archaeology MCP server (loomweave serve / mcp__loomweave__* tools) is available for the project. --- -# Clarion Workflow +# Loomweave Workflow ## Overview -Clarion pre-extracts a codebase into a queryable map — entities (functions, +Loomweave pre-extracts a codebase into a queryable map — entities (functions, classes, modules, files), the call/reference/import edges between them, and -subsystem clusters — and serves it over MCP. **Ask Clarion instead of +subsystem clusters — and serves it over MCP. **Ask Loomweave instead of re-exploring the tree.** One `find_entity` + one `callers_of` answers "what calls this?" without reading a single file. @@ -26,7 +26,7 @@ calls this?" without reading a single file. - You need a function's neighborhood, execution paths, or which subsystem it belongs to. **Not for:** editing code, reading exact implementation bodies (use `summary` or -read the file once you have its path), or codebases with no `.clarion/` index. +read the file once you have its path), or codebases with no `.loomweave/` index. ## Entity IDs — the model @@ -44,10 +44,10 @@ They are not interchangeable: - **`id`** is the entity's *locator* — a mutable address. It changes when the code is renamed or moved, and it's the right thing to feed into the next - Clarion tool call (above). + Loomweave tool call (above). - **`sei`** is the entity's *durable, stable identity*. It survives renames and moves. **When you record a cross-tool binding** — e.g. attaching a Filigree - issue to a Clarion entity — **bind on the `sei`, not the `id`.** A binding + issue to a Loomweave entity — **bind on the `sei`, not the `id`.** A binding keyed on the mutable `id` silently breaks the first time the entity moves. `sei` is `null` when the index predates SEI support or the entity has no binding @@ -99,7 +99,7 @@ re-reading each path element. `truncated`/`truncation_reason` report `edge-cap` ## Catalogue tools — inspection · faceted search · shortcuts -Beyond navigation, Clarion serves a **stateless catalogue** of read tools. All +Beyond navigation, Loomweave serves a **stateless catalogue** of read tools. All of them: take explicit ids/scopes (no cursor/session — there is no `goto`/`back` state to manage); **paginate** (`limit`/`offset`, with a `page` block reporting `total`/`returned`/`truncated` — no silent caps); carry `sei` on every entity @@ -151,14 +151,14 @@ honest-empty unless a plugin emits those tags. Likewise `high_churn` and `index_diff` for repo-level freshness). `search_semantic` is also in the catalogue. It is opt-in under -`semantic_search:`; when enabled, `clarion analyze` populates the git-ignored -`.clarion/embeddings.db` sidecar and the query path filters stale vectors by +`semantic_search:`; when enabled, `loomweave analyze` populates the git-ignored +`.loomweave/embeddings.db` sidecar and the query path filters stale vectors by content hash. > Not in this catalogue: `emit_observation` as a general-purpose write surface. **Guidance authoring has an operator boundary.** Operators can manage sheets via -`clarion guidance create/edit/show/list/delete/promote` (plus `export`/`import` +`loomweave guidance create/edit/show/list/delete/promote` (plus `export`/`import` for team sharing). Agents may call `propose_guidance` to create a Filigree observation, but that proposal is inert until an operator promotes it through `promote_guidance` or the CLI. Promoted sheets reach you through `guidance_for` @@ -192,10 +192,10 @@ and are composed into `summary` prompts with a real guidance fingerprint. ## Launch -`clarion serve --path ` where `` contains `.clarion/clarion.db` -(built by `clarion analyze `). In an MCP client the tools appear as -`mcp__clarion__find_entity`, etc. +`loomweave serve --path ` where `` contains `.loomweave/loomweave.db` +(built by `loomweave analyze `). In an MCP client the tools appear as +`mcp__loomweave__find_entity`, etc. -Besides the tools, the server exposes a `clarion://context` **resource** — live +Besides the tools, the server exposes a `loomweave://context` **resource** — live entity/subsystem/finding counts and index freshness as JSON, a lightweight read when you only want the numbers (`project_status` is the fuller tool-based view). diff --git a/crates/clarion-mcp/src/analyze_runs.rs b/crates/loomweave-mcp/src/analyze_runs.rs similarity index 94% rename from crates/clarion-mcp/src/analyze_runs.rs rename to crates/loomweave-mcp/src/analyze_runs.rs index 3c9ee1fd..c9616ae7 100644 --- a/crates/clarion-mcp/src/analyze_runs.rs +++ b/crates/loomweave-mcp/src/analyze_runs.rs @@ -1,4 +1,4 @@ -//! In-memory registry of `clarion analyze` subprocesses launched over MCP +//! In-memory registry of `loomweave analyze` subprocesses launched over MCP //! (`analyze_start` / `analyze_status` / `analyze_cancel`, clarion-7e0c21558a). //! //! Decomposition decision: the MCP owns the subprocess and the cancel kill + @@ -23,7 +23,7 @@ use std::sync::{Arc, Mutex}; /// One supervised analyze subprocess. pub(crate) struct RunHandle { - /// The `clarion analyze` child. `try_wait`/`wait` reap it; held by value so + /// The `loomweave analyze` child. `try_wait`/`wait` reap it; held by value so /// the registry owns the process. pub child: Child, /// Process-group id (== child pid; the child is spawned as a group leader) @@ -43,7 +43,7 @@ pub(crate) struct RunHandle { /// surface is low-volume. pub(crate) type RunRegistry = Arc>>; -/// Spawn `clarion analyze` for `project_root` as a new process-group leader so +/// Spawn `loomweave analyze` for `project_root` as a new process-group leader so /// the whole subtree (plugin + pyright) can be group-killed on cancel. /// /// `program` is the launcher (`current_exe()` in production; a stub in tests). @@ -66,7 +66,7 @@ pub(crate) fn spawn_analyze( .arg(progress_path) // Isolate the child's stdio. When analyze_start is driven from the // stdio MCP server, the child would otherwise inherit the server's - // stdout — and `clarion analyze` initializes tracing at `info`, so its + // stdout — and `loomweave analyze` initializes tracing at `info`, so its // non-framed progress bytes would interleave with the MCP JSON-RPC // responses on the same stream and corrupt the client connection. // Progress is reported via --progress-file, not stdout. @@ -120,8 +120,8 @@ pub(crate) fn kill_run(handle: &mut RunHandle) { /// Best-effort delete a finished run's progress file as its handle is evicted /// from the registry. A missing file is success — a run may exit before writing -/// one. Keeps `.clarion/runs/*.progress.json` from accumulating across a -/// long-lived `clarion serve` (clarion-7e0c21558a). +/// one. Keeps `.loomweave/runs/*.progress.json` from accumulating across a +/// long-lived `loomweave serve` (clarion-7e0c21558a). pub(crate) fn reap_progress_file(path: &std::path::Path) { match std::fs::remove_file(path) { Ok(()) => {} @@ -184,7 +184,7 @@ pub(crate) fn mark_run_cancelled_in_db(db_path: &std::path::Path, run_id: &str, return; } }; - if let Err(err) = clarion_storage::pragma::apply_write_pragmas(&conn) { + if let Err(err) = loomweave_storage::pragma::apply_write_pragmas(&conn) { tracing::warn!(error = %err, run_id, "cancel: write pragmas failed"); return; } @@ -209,7 +209,7 @@ mod tests { use super::*; /// The stdio MCP server speaks JSON-RPC framing on its own stdout. A - /// spawned `clarion analyze` that inherited that stdout and emitted `info` + /// spawned `loomweave analyze` that inherited that stdout and emitted `info` /// tracing would interleave non-framed bytes onto the wire and corrupt the /// client connection. The child's stdout must be isolated. We prove it by /// having a stub record where its fd 1 actually points: `/dev/null` when diff --git a/crates/clarion-mcp/src/catalogue/faceted.rs b/crates/loomweave-mcp/src/catalogue/faceted.rs similarity index 98% rename from crates/clarion-mcp/src/catalogue/faceted.rs rename to crates/loomweave-mcp/src/catalogue/faceted.rs index 866164e3..52235cd6 100644 --- a/crates/clarion-mcp/src/catalogue/faceted.rs +++ b/crates/loomweave-mcp/src/catalogue/faceted.rs @@ -9,8 +9,8 @@ use serde_json::{Value, json}; -use clarion_core::McpErrorCode; -use clarion_storage::{ +use loomweave_core::McpErrorCode; +use loomweave_storage::{ EntityRow, entities_by_kind, entities_by_tag, entities_with_wardline_facts, get_taint_facts, }; @@ -100,7 +100,7 @@ impl ServerState { let (candidates, scan_truncated) = match entities_by_kind(conn, &kind, FACET_SCAN_CAP) { Ok(found) => found, - Err(clarion_storage::StorageError::InvalidQuery(message)) => { + Err(loomweave_storage::StorageError::InvalidQuery(message)) => { return Ok(crate::tool_error_envelope( McpErrorCode::InvalidPath, &message, @@ -126,7 +126,7 @@ impl ServerState { /// `find_by_wardline(tier?, group?, scope?)` — entities carrying a Wardline /// taint fact, optionally filtered by `tier`/`group`. The Wardline blob is - /// opaque to Clarion; tier/group filtering is **best-effort** (a top-level + /// opaque to Loomweave; tier/group filtering is **best-effort** (a top-level /// `tier`/`group` field on the blob) and honest-empty when the field is /// absent or no entity matches. Each returned entity carries its `wardline` /// blob verbatim plus its `sei`. diff --git a/crates/clarion-mcp/src/catalogue/inspection.rs b/crates/loomweave-mcp/src/catalogue/inspection.rs similarity index 98% rename from crates/clarion-mcp/src/catalogue/inspection.rs rename to crates/loomweave-mcp/src/catalogue/inspection.rs index 7c9a2311..70848d8b 100644 --- a/crates/clarion-mcp/src/catalogue/inspection.rs +++ b/crates/loomweave-mcp/src/catalogue/inspection.rs @@ -9,8 +9,8 @@ use std::collections::HashSet; use serde_json::{Value, json}; -use clarion_core::McpErrorCode; -use clarion_storage::{ +use loomweave_core::McpErrorCode; +use loomweave_storage::{ MatchFacts, RuleVerdict, entity_by_id, get_taint_facts, rule_match, sei_for_locator, }; @@ -163,7 +163,7 @@ impl ServerState { json!([missing_signal( "wardline_group", "guidance wardline_group match-rules are not evaluated here: the \ - Wardline blob is opaque to Clarion; use wardline_for / find_by_wardline" + Wardline blob is opaque to Loomweave; use wardline_for / find_by_wardline" )]), ); } @@ -257,7 +257,7 @@ impl ServerState { /// `wardline_for(entity_id)` — the Wardline metadata recorded for the entity /// (declared tier, groups, boundary contracts), returned **verbatim**: the - /// `wardline_json` blob is opaque to Clarion (federation opacity contract). + /// `wardline_json` blob is opaque to Loomweave (federation opacity contract). /// `result_kind` is `present` when a fact exists, else `no_facts` with a /// missing-signal note — taint facts are populated via Filigree Flow-B /// (`POST /api/wardline/taint-facts`), so locally-empty is honest, not an @@ -284,7 +284,7 @@ impl ServerState { Some(fact) => { // Opaque: parse as JSON for structured return, but if the // blob is not JSON, return it as a raw string rather than - // failing — Clarion never depends on its shape. + // failing — Loomweave never depends on its shape. let wardline = serde_json::from_str::(&fact.wardline_json) .unwrap_or(Value::String(fact.wardline_json)); json!({ @@ -315,7 +315,7 @@ impl ServerState { fn guides_edge_sources( conn: &rusqlite::Connection, entity_id: &str, -) -> clarion_storage::Result> { +) -> loomweave_storage::Result> { let mut set = HashSet::new(); let mut stmt = conn.prepare("SELECT from_id FROM edges WHERE kind = 'guides' AND to_id = ?1")?; diff --git a/crates/clarion-mcp/src/catalogue/mod.rs b/crates/loomweave-mcp/src/catalogue/mod.rs similarity index 96% rename from crates/clarion-mcp/src/catalogue/mod.rs rename to crates/loomweave-mcp/src/catalogue/mod.rs index 5405e97a..a27858e5 100644 --- a/crates/clarion-mcp/src/catalogue/mod.rs +++ b/crates/loomweave-mcp/src/catalogue/mod.rs @@ -31,7 +31,7 @@ use std::collections::HashSet; use serde_json::{Value, json}; -use clarion_storage::contained_entity_ids; +use loomweave_storage::contained_entity_ids; use crate::ParamError; @@ -93,17 +93,17 @@ pub(crate) fn paginate(rows: &[T], page: Page) -> (Vec, Value) { pub(crate) fn finalize_entity_page( conn: &rusqlite::Connection, project_root: &std::path::Path, - candidates: Vec, + candidates: Vec, scope: &ScopeFilter, page: Page, scan_truncated: bool, ) -> Value { - let in_scope: Vec = candidates + let in_scope: Vec = candidates .into_iter() .filter(|e| scope.contains(&e.id, e.source_file_path.as_deref(), project_root)) .collect(); let total = in_scope.len(); - let returned: Vec = in_scope + let returned: Vec = in_scope .into_iter() .skip(page.offset) .take(page.limit) @@ -141,10 +141,10 @@ pub(crate) fn missing_signal(signal: &str, reason: &str) -> Value { } /// Glob-match `path` against a `**`/`*`/`?` `pattern`. Re-exported from -/// `clarion-storage` so the read (`scope` / guidance `match_rules`) and write +/// `loomweave-storage` so the read (`scope` / guidance `match_rules`) and write /// (CLI guidance `--for-entity`) surfaces share one matcher — see -/// `clarion_storage::glob`. -pub(crate) use clarion_storage::glob_match; +/// `loomweave_storage::glob`. +pub(crate) use loomweave_storage::glob_match; /// Bound on entity ids materialised when resolving an entity-descendant scope. const SCOPE_DESCENDANT_CAP: usize = 50_000; @@ -196,7 +196,7 @@ impl RawScope { pub(crate) fn resolve( &self, conn: &rusqlite::Connection, - ) -> clarion_storage::Result { + ) -> loomweave_storage::Result { match self { RawScope::Project => Ok(ScopeFilter::Project), RawScope::PathGlob(pattern) => Ok(ScopeFilter::Path { @@ -271,7 +271,7 @@ impl ScopeFilter { &self, conn: &rusqlite::Connection, project_root: &std::path::Path, - ) -> clarion_storage::Result<(Option>, bool)> { + ) -> loomweave_storage::Result<(Option>, bool)> { match self { ScopeFilter::Project => Ok((None, false)), ScopeFilter::Ids { ids, truncated } => Ok((Some(ids.clone()), *truncated)), diff --git a/crates/clarion-mcp/src/catalogue/semantic.rs b/crates/loomweave-mcp/src/catalogue/semantic.rs similarity index 97% rename from crates/clarion-mcp/src/catalogue/semantic.rs rename to crates/loomweave-mcp/src/catalogue/semantic.rs index af7ead47..a884271a 100644 --- a/crates/clarion-mcp/src/catalogue/semantic.rs +++ b/crates/loomweave-mcp/src/catalogue/semantic.rs @@ -4,7 +4,7 @@ //! or no provider is configured, the tool returns an explicit "not enabled" //! result — never a faked or empty-as-if-complete answer. When enabled it embeds //! the query, runs a **bounded exact cosine scan** over the git-ignored sidecar -//! (`.clarion/embeddings.db`), and returns ranked, SEI-carrying entities. Only +//! (`.loomweave/embeddings.db`), and returns ranked, SEI-carrying entities. Only //! embeddings whose `content_hash` matches the entity's current hash are //! considered, so stale vectors never surface (freshness, like the summary //! cache). @@ -13,7 +13,7 @@ use std::collections::HashMap; use serde_json::{Value, json}; -use clarion_storage::{EmbeddingStore, embeddings_db_path, entity_by_id}; +use loomweave_storage::{EmbeddingStore, embeddings_db_path, entity_by_id}; use crate::ParamError; use crate::ServerState; @@ -22,7 +22,7 @@ use crate::{ entity_json, flatten_storage_envelope_result, required_str, success_envelope, tool_error_envelope, }; -use clarion_core::McpErrorCode; +use loomweave_core::McpErrorCode; const SEMANTIC_PAGE_DEFAULT: usize = 20; const SEMANTIC_PAGE_MAX: usize = 100; @@ -117,7 +117,7 @@ fn rank_semantic( model_id: &str, query_vector: &[f32], page: Page, -) -> clarion_storage::Result { +) -> loomweave_storage::Result { let filter = scope.resolve(conn)?; let (in_scope, scope_truncated) = filter.in_scope_ids(conn, project_root)?; diff --git a/crates/clarion-mcp/src/catalogue/shortcuts.rs b/crates/loomweave-mcp/src/catalogue/shortcuts.rs similarity index 97% rename from crates/clarion-mcp/src/catalogue/shortcuts.rs rename to crates/loomweave-mcp/src/catalogue/shortcuts.rs index 1339b768..8515b0c0 100644 --- a/crates/clarion-mcp/src/catalogue/shortcuts.rs +++ b/crates/loomweave-mcp/src/catalogue/shortcuts.rs @@ -14,8 +14,8 @@ use std::collections::{BTreeSet, HashMap, HashSet}; use serde_json::{Value, json}; -use clarion_core::{EdgeConfidence, McpErrorCode}; -use clarion_storage::{call_edges_targeting, entities_by_churn, entity_by_id}; +use loomweave_core::{EdgeConfidence, McpErrorCode}; +use loomweave_storage::{call_edges_targeting, entities_by_churn, entity_by_id}; use crate::ParamError; use crate::ServerState; @@ -65,8 +65,8 @@ const RUNTIME_IMPORT_EDGE_SQL: &str = "\ OR (COALESCE(json_extract(properties, '$.type_only'), 0) != 1 \ AND COALESCE(json_extract(properties, '$.scope'), 'module') = 'module'))"; -/// Rule id for an emitted dead-code candidate (ADR-017 `CLA-FACT-*` namespace). -const DEAD_CODE_RULE_ID: &str = "CLA-FACT-DEAD-CODE-CANDIDATE"; +/// Rule id for an emitted dead-code candidate (ADR-017 `LMWV-FACT-*` namespace). +const DEAD_CODE_RULE_ID: &str = "LMWV-FACT-DEAD-CODE-CANDIDATE"; /// Heuristic confidence for a dead-code candidate — never presented as certain. const DEAD_CODE_CONFIDENCE: f64 = 0.6; const HOTSPOTS_PAGE_DEFAULT: usize = 20; @@ -287,7 +287,7 @@ impl ServerState { /// is empty; the naive computation would then flag the *entire* codebase as /// dead. Instead the tool returns an honest signal-unavailable result with /// zero candidates — never a false positive. Results are heuristic - /// (`CLA-FACT-DEAD-CODE-CANDIDATE`, confidence < 1), bounded, SEI-carrying. + /// (`LMWV-FACT-DEAD-CODE-CANDIDATE`, confidence < 1), bounded, SEI-carrying. pub(crate) async fn tool_find_dead_code( &self, arguments: &serde_json::Map, @@ -634,7 +634,7 @@ impl ServerState { } /// `recently_changed(since?, scope?)` — entities changed since a timestamp. - /// Clarion does not index a per-entity git change timestamp in v1.0, so this + /// Loomweave does not index a per-entity git change timestamp in v1.0, so this /// is an honest no-op: it returns an empty set with a missing-signal note /// pointing at `index_diff` for repo-level freshness. The args are accepted /// for forward-compatibility. Never fabricates a change set. @@ -658,7 +658,7 @@ impl ServerState { "page": { "total": 0, "offset": 0, "limit": 0, "returned": 0, "truncated": false }, "signal": missing_signal( "git_change_time", - "Clarion does not index a per-entity git change timestamp in v1.0; use index_diff \ + "Loomweave does not index a per-entity git change timestamp in v1.0; use index_diff \ for repo-level freshness (HEAD vs last analyze)" ), }))) @@ -670,7 +670,7 @@ impl ServerState { fn test_tagged_subset( conn: &rusqlite::Connection, ids: &HashSet, -) -> clarion_storage::Result> { +) -> loomweave_storage::Result> { let mut out = HashSet::new(); let all: Vec<&String> = ids.iter().collect(); for chunk in all.chunks(500) { @@ -693,7 +693,7 @@ fn test_tagged_subset( fn ids_with_any_tag( conn: &rusqlite::Connection, tags: &[&str], -) -> clarion_storage::Result> { +) -> loomweave_storage::Result> { if tags.is_empty() { return Ok(HashSet::new()); } @@ -711,7 +711,7 @@ fn ids_with_any_tag( } /// All entity ids, bounded by [`ENTITY_SCAN_CAP`]. Returns `(ids, truncated)`. -fn all_entity_ids(conn: &rusqlite::Connection) -> clarion_storage::Result<(Vec, bool)> { +fn all_entity_ids(conn: &rusqlite::Connection) -> loomweave_storage::Result<(Vec, bool)> { let cap = i64::try_from(ENTITY_SCAN_CAP.saturating_add(1)).unwrap_or(i64::MAX); let mut stmt = conn.prepare("SELECT id FROM entities ORDER BY id LIMIT ?1")?; let mut rows = stmt.query(rusqlite::params![cap])?; @@ -732,7 +732,7 @@ fn all_entity_ids(conn: &rusqlite::Connection) -> clarion_storage::Result<(Vec clarion_storage::Result<(HashMap>, bool)> { +) -> loomweave_storage::Result<(HashMap>, bool)> { call_import_adjacency_with_cap(conn, EDGE_SCAN_CAP) } @@ -740,7 +740,7 @@ fn import_adjacency_for_cycles( conn: &rusqlite::Connection, confidence: EdgeConfidence, scan_cap: usize, -) -> clarion_storage::Result<(HashMap>, bool)> { +) -> loomweave_storage::Result<(HashMap>, bool)> { let in_clause = confidence_in_clause(confidence); let sql = format!( "SELECT from_id, to_id FROM edges \ @@ -772,7 +772,7 @@ fn import_adjacency_for_cycles( fn call_import_adjacency_with_cap( conn: &rusqlite::Connection, scan_cap: usize, -) -> clarion_storage::Result<(HashMap>, bool)> { +) -> loomweave_storage::Result<(HashMap>, bool)> { let cap = i64::try_from(scan_cap.saturating_add(1)).unwrap_or(i64::MAX); let mut stmt = conn.prepare( "SELECT kind, from_id, to_id, confidence, properties \ diff --git a/crates/loomweave-mcp/src/config.rs b/crates/loomweave-mcp/src/config.rs new file mode 100644 index 00000000..28d77732 --- /dev/null +++ b/crates/loomweave-mcp/src/config.rs @@ -0,0 +1 @@ +pub use loomweave_federation::config::*; diff --git a/crates/loomweave-mcp/src/filigree.rs b/crates/loomweave-mcp/src/filigree.rs new file mode 100644 index 00000000..24e0be49 --- /dev/null +++ b/crates/loomweave-mcp/src/filigree.rs @@ -0,0 +1 @@ +pub use loomweave_federation::filigree::*; diff --git a/crates/loomweave-mcp/src/filigree_url.rs b/crates/loomweave-mcp/src/filigree_url.rs new file mode 100644 index 00000000..a6ed6938 --- /dev/null +++ b/crates/loomweave-mcp/src/filigree_url.rs @@ -0,0 +1 @@ +pub use loomweave_federation::filigree_url::*; diff --git a/crates/clarion-mcp/src/index_diff.rs b/crates/loomweave-mcp/src/index_diff.rs similarity index 99% rename from crates/clarion-mcp/src/index_diff.rs rename to crates/loomweave-mcp/src/index_diff.rs index 1e9b24d7..99fe1748 100644 --- a/crates/clarion-mcp/src/index_diff.rs +++ b/crates/loomweave-mcp/src/index_diff.rs @@ -3,7 +3,7 @@ //! Answers "what changed since the last analyze, and is this checkout newer //! than the graph?" without an agent hand-rolling git + mtime checks. //! -//! **Git posture (per the issue's design fork).** Clarion persists no +//! **Git posture (per the issue's design fork).** Loomweave persists no //! analyze-time commit SHA — `project_status` reports `git_sha: null` and the //! analyze write path never captures HEAD. Rather than reverse that stance to //! populate one side of a comparison, `index_diff` reads git *at query time*, @@ -18,12 +18,12 @@ use std::collections::BTreeSet; use std::path::Path; use std::time::SystemTime; -use clarion_core::hardened_git_command; +use loomweave_core::hardened_git_command; use serde_json::{Value, json}; use time::OffsetDateTime; use time::format_description::well_known::Rfc3339; -use clarion_storage::{StorageError, normalize_source_path}; +use loomweave_storage::{StorageError, normalize_source_path}; /// Default per-list cap so a pathological repo cannot produce an unbounded /// packet. Overridable via the `limit` argument. @@ -385,7 +385,7 @@ pub(crate) fn build_report( "analyzed_at": Value::Null, "analyzed_commit": Value::Null, "git": git_json, - "notes": ["no completed analyze run; run `clarion analyze` first"], + "notes": ["no completed analyze run; run `loomweave analyze` first"], }); }; let analyzed_time = parse_rfc3339(analyzed_at); diff --git a/crates/clarion-mcp/src/lib.rs b/crates/loomweave-mcp/src/lib.rs similarity index 93% rename from crates/clarion-mcp/src/lib.rs rename to crates/loomweave-mcp/src/lib.rs index 2c0b0d85..fc29d681 100644 --- a/crates/clarion-mcp/src/lib.rs +++ b/crates/loomweave-mcp/src/lib.rs @@ -1,4 +1,4 @@ -//! MCP protocol surface for Clarion. +//! MCP protocol surface for Loomweave. mod analyze_runs; mod catalogue; @@ -15,7 +15,7 @@ use std::collections::{BTreeSet, HashMap}; use std::path::{Component, Path, PathBuf}; use std::sync::{Arc, Mutex}; -use clarion_core::{ +use loomweave_core::{ EdgeConfidence, EmbeddingProvider, LlmProvider, LlmProviderError, LlmRequest, LlmResponse, McpErrorCode, }; @@ -27,8 +27,8 @@ use thiserror::Error; use time::{Date, Month, OffsetDateTime, macros::format_description}; use tokio::sync::{Mutex as AsyncMutex, Notify, broadcast, mpsc}; -use clarion_core::plugin::{ContentLengthCeiling, Frame, TransportError}; -use clarion_storage::{ +use loomweave_core::plugin::{ContentLengthCeiling, Frame, TransportError}; +use loomweave_storage::{ CallEdgeMatch, EntityRow, InferredCallEdgeRecord, InferredEdgeCacheEntry, InferredEdgeCacheKey, InferredEdgeWriteStats, ReaderPool, ReferenceDirection, ReferenceEdgeMatch, RolledUpReferenceEdge, StorageError, SummaryCacheEntry, SummaryCacheKey, UnresolvedCallSiteRow, @@ -43,7 +43,7 @@ use crate::filigree::{ EntityAssociation, EntityAssociationsResponse, FiligreeLookup, IssueDetail, ObservationCreateRequest, }; -use clarion_storage::{ +use loomweave_storage::{ GuidanceProposal, GuidanceSheetInput, invalidate_summaries_for_sheet, upsert_guidance_sheet, }; @@ -51,18 +51,19 @@ use clarion_storage::{ pub const MCP_PROTOCOL_VERSION: &str = "2025-11-25"; const EMPTY_GUIDANCE_FINGERPRINT: &str = "guidance-empty"; -/// The bundled clarion-workflow skill text, embedded for the `prompts/get` +/// The bundled loomweave-workflow skill text, embedded for the `prompts/get` /// surface and reused as the canonical orientation reference. The asset lives -/// in this crate's own tree; the CLI (which depends on clarion-mcp) reaches +/// in this crate's own tree; the CLI (which depends on loomweave-mcp) reaches /// down into it to embed the same bytes for its on-disk `install --skills` /// copy (clarion-04391392c7). -pub const CLARION_WORKFLOW_SKILL: &str = include_str!("../assets/skills/clarion-workflow/SKILL.md"); +pub const LOOMWEAVE_WORKFLOW_SKILL: &str = + include_str!("../assets/skills/loomweave-workflow/SKILL.md"); /// Orientation text returned in the MCP `initialize` result's `instructions` /// field. The `Tools:` enumeration is derived from [`list_tools`] (the single /// source of truth) so it can never drift from the advertised tool set as tools /// are added or removed; the surrounding prose is static. Kept consistent with -/// the clarion-workflow skill. +/// the loomweave-workflow skill. fn server_instructions() -> String { let tool_names = list_tools() .iter() @@ -70,9 +71,9 @@ fn server_instructions() -> String { .collect::>() .join(", "); format!( - "Clarion is a code-archaeology server: it has pre-extracted this project \ + "Loomweave is a code-archaeology server: it has pre-extracted this project \ into a queryable map of entities (functions, classes, modules, files), the call \ -/ reference / import edges between them, and subsystem clusters. Ask Clarion \ +/ reference / import edges between them, and subsystem clusters. Ask Loomweave \ instead of re-reading or grepping the tree. Entity IDs are `{{plugin}}:{{kind}}:{{qualified_name}}` (e.g. \ @@ -85,9 +86,9 @@ take a `confidence` tier (resolved | ambiguous | inferred; default resolved). \ `project_status_get` reports index freshness, counts, LLM policy, and the resolved \ Filigree endpoint. -For the full workflow see the clarion-workflow skill (installed by \ -`clarion install --skills`), or read the `clarion-workflow` prompt. Live \ -project counts and index freshness are in the `clarion://context` resource." +For the full workflow see the loomweave-workflow skill (installed by \ +`loomweave install --skills`), or read the `loomweave-workflow` prompt. Live \ +project counts and index freshness are in the `loomweave://context` resource." ) } @@ -288,7 +289,7 @@ pub fn list_tools() -> Vec { vec![ ToolDefinition { name: "entity_at", - description: "Return the innermost Clarion entity whose source range contains a file and line, plus an `entity_context` evidence block: match_reason (decorator_range / declaration / body_range / containing_range / no_match) explaining why the line matched, the module→entity containing stack, the matched entity's decl/body/decorator sub-ranges, any same-granularity ambiguity alternatives, and index freshness. Paths are normalized relative to the project root. A blank or comment line that only a module spans reports containing_range — never a fabricated exact match.", + description: "Return the innermost Loomweave entity whose source range contains a file and line, plus an `entity_context` evidence block: match_reason (decorator_range / declaration / body_range / containing_range / no_match) explaining why the line matched, the module→entity containing stack, the matched entity's decl/body/decorator sub-ranges, any same-granularity ambiguity alternatives, and index freshness. Paths are normalized relative to the project root. A blank or comment line that only a module spans reports containing_range — never a fabricated exact match.", input_schema: json!({ "type": "object", "properties": { @@ -301,7 +302,7 @@ pub fn list_tools() -> Vec { }, ToolDefinition { name: "entity_find", - description: "Search Clarion entities by id, name, short name, and summary text stored on entity rows. Results are paginated and ranked by FTS match where possible. This does not traverse the graph and does not search on-demand summary_cache entries. Pass an optional `kind` (e.g. \"subsystem\", \"function\", \"class\", \"module\") to return only entities of that kind — the way to locate a subsystem without visually filtering results.", + description: "Search Loomweave entities by id, name, short name, and summary text stored on entity rows. Results are paginated and ranked by FTS match where possible. This does not traverse the graph and does not search on-demand summary_cache entries. Pass an optional `kind` (e.g. \"subsystem\", \"function\", \"class\", \"module\") to return only entities of that kind — the way to locate a subsystem without visually filtering results.", input_schema: json!({ "type": "object", "properties": { @@ -340,7 +341,7 @@ pub fn list_tools() -> Vec { }, ToolDefinition { name: "entity_issue_list", - description: "Return Filigree issues attached to this Clarion entity, optionally including issues attached to contained entities. Filigree is an enrichment source; if unavailable, the tool returns an unavailable envelope instead of failing Clarion. The result carries a result_kind (matched | no_matches | unavailable) so a reachable-but-empty Filigree is distinct from an unreachable one, and a filigree_endpoint block (configured vs resolved URL + resolution_source) so you can see which endpoint — e.g. a live ethereal port — the answer came from. Each matched/drifted entry carries an `issue` object with the issue's title, status, and priority (fetched once per distinct issue, no N+1); `issue` is null when the issue-detail route is unavailable, so the match still resolves without a second hop into Filigree. Includes a `wardline_findings` section (enrich-only) reconciling Wardline findings to the entity by qualname; `result_kind` is matched|no_matches|unavailable.", + description: "Return Filigree issues attached to this Loomweave entity, optionally including issues attached to contained entities. Filigree is an enrichment source; if unavailable, the tool returns an unavailable envelope instead of failing Loomweave. The result carries a result_kind (matched | no_matches | unavailable) so a reachable-but-empty Filigree is distinct from an unreachable one, and a filigree_endpoint block (configured vs resolved URL + resolution_source) so you can see which endpoint — e.g. a live ethereal port — the answer came from. Each matched/drifted entry carries an `issue` object with the issue's title, status, and priority (fetched once per distinct issue, no N+1); `issue` is null when the issue-detail route is unavailable, so the match still resolves without a second hop into Filigree. Includes a `wardline_findings` section (enrich-only) reconciling Wardline findings to the entity by qualname; `result_kind` is matched|no_matches|unavailable.", input_schema: json!({ "type": "object", "properties": { @@ -353,7 +354,7 @@ pub fn list_tools() -> Vec { }, ToolDefinition { name: "entity_neighborhood_get", - description: "Return the one-hop Clarion neighborhood around an entity: callers, callees, container, contained entities, references, and imports (imports_in = who imports this module, imports_out = what it imports; module-to-module). Default confidence is resolved; ambiguous and inferred calls are opt-in. References and imports are not execution flow. When the entity is a module, references_in/references_out are rolled up over the symbols it contains (references_rolled_up=true) — each neighbor carries a `via` naming the contained symbol the edge touches, so \"who imports this module/contract\" is answered at module altitude rather than reading empty. On references_in each rolled-up neighbor also carries `importer_module` — the importing symbol's containing module — so reverse-import names importing modules, not just symbols. The result carries scope_excludes naming blind spots not searched (e.g. attribute-receiver-calls) so empty sections are never read as guaranteed true negatives.", + description: "Return the one-hop Loomweave neighborhood around an entity: callers, callees, container, contained entities, references, and imports (imports_in = who imports this module, imports_out = what it imports; module-to-module). Default confidence is resolved; ambiguous and inferred calls are opt-in. References and imports are not execution flow. When the entity is a module, references_in/references_out are rolled up over the symbols it contains (references_rolled_up=true) — each neighbor carries a `via` naming the contained symbol the edge touches, so \"who imports this module/contract\" is answered at module altitude rather than reading empty. On references_in each rolled-up neighbor also carries `importer_module` — the importing symbol's containing module — so reverse-import names importing modules, not just symbols. The result carries scope_excludes naming blind spots not searched (e.g. attribute-receiver-calls) so empty sections are never read as guaranteed true negatives.", input_schema: id_confidence_schema(), }, ToolDefinition { @@ -368,7 +369,7 @@ pub fn list_tools() -> Vec { }, ToolDefinition { name: "project_status_get", - description: "Return deterministic Clarion diagnostics: repo root, db path, latest run (id/status/started/completed), entity/subsystem/edge/finding/briefing-blocked counts, index staleness, per-plugin entity counts from the current index, LLM policy (provider/live/cache), and the resolved Filigree endpoint (configured vs resolved URL + resolution source). Answers \"is the graph fresh, plugin-less, LLM-live, Filigree-reachable?\" without shelling out. No LLM call.", + description: "Return deterministic Loomweave diagnostics: repo root, db path, latest run (id/status/started/completed), entity/subsystem/edge/finding/briefing-blocked counts, index staleness, per-plugin entity counts from the current index, LLM policy (provider/live/cache), and the resolved Filigree endpoint (configured vs resolved URL + resolution source). Answers \"is the graph fresh, plugin-less, LLM-live, Filigree-reachable?\" without shelling out. No LLM call.", input_schema: json!({ "type": "object", "properties": {}, @@ -382,7 +383,7 @@ pub fn list_tools() -> Vec { }, ToolDefinition { name: "entity_source_get", - description: "Return the exact indexed source span for one entity (its source_line_start..source_line_end, which includes any decorators/signature/docstring the plugin captured) plus a bounded window of surrounding context, as line-numbered lines each flagged in_entity true/false. No LLM call. Lets an agent read and trust the entity without shelling out. source_status reports `ok`, or — instead of a misleading stale snippet — `missing` (file gone), `no_range`/`no_source_path` (entity has no anchor), `binary` (non-UTF-8), or `drifted` (the file no longer matches the indexed content_hash; rerun `clarion analyze`). context_lines defaults to 10.", + description: "Return the exact indexed source span for one entity (its source_line_start..source_line_end, which includes any decorators/signature/docstring the plugin captured) plus a bounded window of surrounding context, as line-numbered lines each flagged in_entity true/false. No LLM call. Lets an agent read and trust the entity without shelling out. source_status reports `ok`, or — instead of a misleading stale snippet — `missing` (file gone), `no_range`/`no_source_path` (entity has no anchor), `binary` (non-UTF-8), or `drifted` (the file no longer matches the indexed content_hash; rerun `loomweave analyze`). context_lines defaults to 10.", input_schema: json!({ "type": "object", "properties": { @@ -395,7 +396,7 @@ pub fn list_tools() -> Vec { }, ToolDefinition { name: "entity_call_site_list", - description: "Show the actual source sites behind calls/references edges, so an agent can see WHY Clarion believes an edge exists rather than trusting it blind. role=caller (default) returns this entity's outgoing sites (what it calls/references); role=callee returns incoming sites (who calls/references it). Each site carries the file path, 1-based line, byte column, the source line text, edge kind, confidence, and a resolution of resolved | ambiguous (with candidate ids) | unresolved (a static call Clarion could not bind, kept separate so it is never mixed with resolved evidence). Filter by edge kind (`calls`/`references`) and by a best-effort production/test path heuristic (`all`/`production`/`test`; path partitioning is not indexed — the heuristic matches conventional test paths). Output is bounded; truncated flags when the site cap trims. No LLM call.", + description: "Show the actual source sites behind calls/references edges, so an agent can see WHY Loomweave believes an edge exists rather than trusting it blind. role=caller (default) returns this entity's outgoing sites (what it calls/references); role=callee returns incoming sites (who calls/references it). Each site carries the file path, 1-based line, byte column, the source line text, edge kind, confidence, and a resolution of resolved | ambiguous (with candidate ids) | unresolved (a static call Loomweave could not bind, kept separate so it is never mixed with resolved evidence). Filter by edge kind (`calls`/`references`) and by a best-effort production/test path heuristic (`all`/`production`/`test`; path partitioning is not indexed — the heuristic matches conventional test paths). Output is bounded; truncated flags when the site cap trims. No LLM call.", input_schema: json!({ "type": "object", "properties": { @@ -424,7 +425,7 @@ pub fn list_tools() -> Vec { }, ToolDefinition { name: "analyze_start", - description: "Start a `clarion analyze` run over this project in the background and return its run handle immediately — do not block on the (possibly many-minute) run. Re-indexes the source tree and refreshes entities/edges/subsystems. Returns run_id, status (`started`), and the progress-file path. Only one analyze may run per project at a time (a cross-process lock enforces it); a second start while one is active is rejected. Poll analyze_status for progress; analyze_cancel to stop. No arguments.", + description: "Start a `loomweave analyze` run over this project in the background and return its run handle immediately — do not block on the (possibly many-minute) run. Re-indexes the source tree and refreshes entities/edges/subsystems. Returns run_id, status (`started`), and the progress-file path. Only one analyze may run per project at a time (a cross-process lock enforces it); a second start while one is active is rejected. Poll analyze_status for progress; analyze_cancel to stop. No arguments.", input_schema: json!({ "type": "object", "properties": {}, @@ -482,7 +483,7 @@ pub fn list_tools() -> Vec { }, ToolDefinition { name: "propose_guidance", - description: "Propose a guidance sheet for operator review by creating a Filigree observation. This is deliberately inert: it does not write a Clarion guidance entity and cannot enter summaries until `promote_guidance` or `clarion guidance promote` consumes the observation.", + description: "Propose a guidance sheet for operator review by creating a Filigree observation. This is deliberately inert: it does not write a Loomweave guidance entity and cannot enter summaries until `promote_guidance` or `loomweave guidance promote` consumes the observation.", input_schema: json!({ "type": "object", "properties": { @@ -504,7 +505,7 @@ pub fn list_tools() -> Vec { }, ToolDefinition { name: "promote_guidance", - description: "Promote a reviewed Filigree observation produced by `propose_guidance` into a local Clarion guidance sheet. This operator action is the anti-poisoning boundary: only promoted observations become prompt-composed guidance.", + description: "Promote a reviewed Filigree observation produced by `propose_guidance` into a local Loomweave guidance sheet. This operator action is the anti-poisoning boundary: only promoted observations become prompt-composed guidance.", input_schema: json!({ "type": "object", "properties": { @@ -539,7 +540,7 @@ pub fn list_tools() -> Vec { }, ToolDefinition { name: "entity_wardline_get", - description: "Return the Wardline metadata recorded for one entity (declared tier, groups, boundary contracts) returned VERBATIM — the `wardline_json` blob is opaque to Clarion. result_kind is `present` when a taint fact exists, else `no_facts` with a missing-signal note: facts are populated via Filigree Flow-B (POST /api/wardline/taint-facts), so a locally-empty result is honest, not an error. The entity carries its `sei`. No LLM call.", + description: "Return the Wardline metadata recorded for one entity (declared tier, groups, boundary contracts) returned VERBATIM — the `wardline_json` blob is opaque to Loomweave. result_kind is `present` when a taint fact exists, else `no_facts` with a missing-signal note: facts are populated via Filigree Flow-B (POST /api/wardline/taint-facts), so a locally-empty result is honest, not an error. The entity carries its `sei`. No LLM call.", input_schema: id_schema(), }, ToolDefinition { @@ -554,7 +555,7 @@ pub fn list_tools() -> Vec { }, ToolDefinition { name: "entity_wardline_list", - description: "Return entities carrying a Wardline taint fact, optionally filtered by `tier` and/or `group`, within an optional `scope` (entity id → descendants, OR path glob; omitted → whole project). The Wardline blob is opaque to Clarion: tier/group filtering is best-effort against a top-level field on the blob and honest-empty when absent. Each entity carries its `wardline` blob verbatim plus its `sei`. Bounded (limit/offset, page.total/truncated). Facts are populated via Filigree Flow-B. No LLM call.", + description: "Return entities carrying a Wardline taint fact, optionally filtered by `tier` and/or `group`, within an optional `scope` (entity id → descendants, OR path glob; omitted → whole project). The Wardline blob is opaque to Loomweave: tier/group filtering is best-effort against a top-level field on the blob and honest-empty when absent. Each entity carries its `wardline` blob verbatim plus its `sei`. Bounded (limit/offset, page.total/truncated). Facts are populated via Filigree Flow-B. No LLM call.", input_schema: scope_facet_schema(&[("tier", false), ("group", false)]), }, ToolDefinition { @@ -636,17 +637,17 @@ pub fn list_tools() -> Vec { }, ToolDefinition { name: "entity_recent_change_list", - description: "Return entities changed since a timestamp (`since?`), within an optional `scope`. Clarion does not index a per-entity git change timestamp in v1.0, so this is an HONEST NO-OP: it returns an empty set with a missing-signal note pointing at `index_diff` for repo-level freshness (HEAD vs last analyze). Never fabricates a change set. No LLM call.", + description: "Return entities changed since a timestamp (`since?`), within an optional `scope`. Loomweave does not index a per-entity git change timestamp in v1.0, so this is an HONEST NO-OP: it returns an empty set with a missing-signal note pointing at `index_diff` for repo-level freshness (HEAD vs last analyze). Never fabricates a change set. No LLM call.", input_schema: scope_page_schema(true), }, ToolDefinition { name: "entity_dead_list", - description: "Return entities NOT reachable from the root set (entry points ∪ exported API ∪ tests ∪ HTTP routes ∪ CLI commands ∪ data models) over the call+import graph, within an optional `scope`. On-demand graph query (no analyze-time precompute). CONSERVATIVE (fails toward `live`): reachability counts ALL edge confidence tiers (resolved ∪ ambiguous ∪ inferred), dynamic-dispatch/reflection barrier tags force their entities live, and framework-magic kinds are excluded from candidacy — so it under-reports rather than over-reports. No `confidence` argument (a ceiling would only make more code look dead). HONEST SIGNAL-UNAVAILABLE: if the current index has no root categorisation tags, the tool returns zero candidates with a missing-signal note (NOT a flood of false positives, and NOT a guarantee there is no dead code). Heuristic results (CLA-FACT-DEAD-CODE-CANDIDATE, confidence < 1) — never certain. Bounded; SEI-carrying. No LLM call.", + description: "Return entities NOT reachable from the root set (entry points ∪ exported API ∪ tests ∪ HTTP routes ∪ CLI commands ∪ data models) over the call+import graph, within an optional `scope`. On-demand graph query (no analyze-time precompute). CONSERVATIVE (fails toward `live`): reachability counts ALL edge confidence tiers (resolved ∪ ambiguous ∪ inferred), dynamic-dispatch/reflection barrier tags force their entities live, and framework-magic kinds are excluded from candidacy — so it under-reports rather than over-reports. No `confidence` argument (a ceiling would only make more code look dead). HONEST SIGNAL-UNAVAILABLE: if the current index has no root categorisation tags, the tool returns zero candidates with a missing-signal note (NOT a flood of false positives, and NOT a guarantee there is no dead code). Heuristic results (LMWV-FACT-DEAD-CODE-CANDIDATE, confidence < 1) — never certain. Bounded; SEI-carrying. No LLM call.", input_schema: scope_page_schema(false), }, ToolDefinition { name: "entity_semantic_search_list", - description: "Rank entities by semantic (embedding cosine) similarity to a `query` string, within an optional `scope`. OPT-IN: semantic search is OFF by default; when disabled or no embedding provider is configured the tool returns result_kind=`not_enabled` with a missing-signal note (never a faked or empty-as-complete result). When enabled it embeds the query and runs a bounded exact cosine scan over the git-ignored `.clarion/embeddings.db` sidecar (built at analyze time), considering only embeddings whose content_hash matches the entity's current hash (stale vectors never surface). Bounded (limit default 20, max 100; page.total/truncated). Each result carries its `sei` and a `score`.", + description: "Rank entities by semantic (embedding cosine) similarity to a `query` string, within an optional `scope`. OPT-IN: semantic search is OFF by default; when disabled or no embedding provider is configured the tool returns result_kind=`not_enabled` with a missing-signal note (never a faked or empty-as-complete result). When enabled it embeds the query and runs a bounded exact cosine scan over the git-ignored `.loomweave/embeddings.db` sidecar (built at analyze time), considering only embeddings whose content_hash matches the entity's current hash (stale vectors never surface). Bounded (limit default 20, max 100; page.total/truncated). Each result carries its `sei` and a `score`.", input_schema: json!({ "type": "object", "properties": { @@ -774,7 +775,7 @@ fn id_confidence_schema() -> Value { /// /// Storage-backed tool calls require [`ServerState::handle_json_rpc`]. The /// `resources/*` and `prompts/*` RPCs are likewise served ONLY by -/// [`ServerState::handle_json_rpc`] (the production `clarion serve` path); a +/// [`ServerState::handle_json_rpc`] (the production `loomweave serve` path); a /// caller using this free function gets a deliberately narrower server, and its /// `initialize` advertises only the `tools` capability it actually serves. #[must_use] @@ -842,7 +843,7 @@ pub struct ServerState { filigree_client: Option>, diagnostics: Option, tool_policy: McpToolPolicy, - /// Supervised `clarion analyze` runs launched via `analyze_start`. + /// Supervised `loomweave analyze` runs launched via `analyze_start`. analyze_runs: crate::analyze_runs::RunRegistry, active_requests: Arc>>, cancelled_requests: Arc>>, @@ -1278,7 +1279,7 @@ impl ServerState { else { return error_response(id, -32602, "invalid resources/read params: missing uri"); }; - if uri != "clarion://context" { + if uri != "loomweave://context" { return error_response(id, -32602, &format!("unknown resource: {uri}")); } let snapshot_json = self.context_snapshot_json().await; @@ -1287,7 +1288,7 @@ impl ServerState { &json!({ "contents": [ { - "uri": "clarion://context", + "uri": "loomweave://context", "mimeType": "application/json", "text": snapshot_json } @@ -1316,7 +1317,7 @@ impl ServerState { match snapshot { Ok(snap) => serde_json::to_string(&snap).unwrap_or_else(|_| fallback()), Err(err) => { - tracing::warn!(error = %err, "clarion://context snapshot failed"); + tracing::warn!(error = %err, "loomweave://context snapshot failed"); fallback() } } @@ -1408,12 +1409,12 @@ impl ServerState { .to_owned() }); let request = ObservationCreateRequest { - summary: format!("Clarion guidance proposal for {entity_id}"), + summary: format!("Loomweave guidance proposal for {entity_id}"), detail, file_path, line: entity.source_line_start, priority: 2, - actor: "clarion".to_owned(), + actor: "loomweave".to_owned(), }; let response = @@ -1505,7 +1506,7 @@ impl ServerState { } }; - let db_path = self.project_root.join(".clarion").join("clarion.db"); + let db_path = self.project_root.join(".loomweave").join("loomweave.db"); let project_root = self.project_root.clone(); let sheet_id = promoted.id.clone(); let write_result = @@ -1522,7 +1523,7 @@ impl ServerState { }, ) .map_err(|err| err.to_string())?; - let Some(sheet) = clarion_storage::get_guidance_sheet(&conn, &promoted.id) + let Some(sheet) = loomweave_storage::get_guidance_sheet(&conn, &promoted.id) .map_err(|err| err.to_string())? else { return Ok(0); @@ -1551,7 +1552,7 @@ impl ServerState { let dismiss_id = observation_id.clone(); let dismissed = tokio::task::spawn_blocking(move || { - client.dismiss_observation(&dismiss_id, "promoted to Clarion guidance sheet") + client.dismiss_observation(&dismiss_id, "promoted to Loomweave guidance sheet") }) .await .is_ok_and(|result| result.is_ok()); @@ -1694,7 +1695,7 @@ struct IssuesForAccumulator { impl IssuesForAccumulator { fn new(entities: &[EntityRow], entity_json_by_id: HashMap) -> Self { - // Map every key Filigree might echo back in `clarion_entity_id` to the + // Map every key Filigree might echo back in `loomweave_entity_id` to the // current locator (`entity.id`). A SEI-bearing entity is queried by SEI // only (see `tool_issues_for`), so the SEI→locator alias is the live // path for those rows; the locator self-mapping covers no-SEI entities @@ -1748,8 +1749,8 @@ impl IssuesForAccumulator { // drift classification. let canonical_entity_id = self .association_aliases - .get(&association.clarion_entity_id) - .map_or(association.clarion_entity_id.as_str(), String::as_str); + .get(&association.loomweave_entity_id) + .map_or(association.loomweave_entity_id.as_str(), String::as_str); let entity_json = self.entity_json_by_id.get(canonical_entity_id).cloned(); match self.entities_by_id.get(canonical_entity_id) { None => { @@ -1777,7 +1778,7 @@ impl IssuesForAccumulator { } None => { self.diagnostics.push(json!({ - "code": "CLA-ENTITY-CONTENT-HASH-MISSING", + "code": "LMWV-ENTITY-CONTENT-HASH-MISSING", "entity_id": entity.id })); self.matched.push(association_json( @@ -2216,7 +2217,7 @@ fn read_stdio_frame(reader: &mut impl std::io::BufRead) -> Result Ok(Some(StdioFrame { body: frame.body, framing: StdioFraming::ContentLength, @@ -2269,7 +2270,7 @@ fn write_stdio_response( ) -> Result<(), McpError> { match framing { StdioFraming::ContentLength => { - clarion_core::plugin::write_frame(writer, response)?; + loomweave_core::plugin::write_frame(writer, response)?; } StdioFraming::JsonLine => { writer.write_all(&response.body)?; @@ -2418,7 +2419,7 @@ fn initialize_result(stateful: bool) -> Value { "protocolVersion": MCP_PROTOCOL_VERSION, "capabilities": capabilities, "serverInfo": { - "name": "clarion", + "name": "loomweave", "version": env!("CARGO_PKG_VERSION") }, "instructions": server_instructions() @@ -2429,8 +2430,8 @@ fn resources_list() -> Value { json!({ "resources": [ { - "uri": "clarion://context", - "name": "Clarion project context", + "uri": "loomweave://context", + "name": "Loomweave project context", "description": "Live entity / subsystem / finding counts and index freshness for this project.", "mimeType": "application/json" } @@ -2442,8 +2443,8 @@ fn prompts_list() -> Value { json!({ "prompts": [ { - "name": "clarion-workflow", - "description": "How to use Clarion's MCP tools to navigate this codebase." + "name": "loomweave-workflow", + "description": "How to use Loomweave's MCP tools to navigate this codebase." } ] }) @@ -2454,17 +2455,17 @@ fn prompts_get(id: &Value, params: Option<&Value>) -> Value { .and_then(Value::as_object) .and_then(|p| p.get("name")) .and_then(Value::as_str); - if name != Some("clarion-workflow") { + if name != Some("loomweave-workflow") { return error_response(id, -32602, "unknown prompt"); } result_response( id, &json!({ - "description": "How to use Clarion's MCP tools to navigate this codebase.", + "description": "How to use Loomweave's MCP tools to navigate this codebase.", "messages": [ { "role": "user", - "content": { "type": "text", "text": CLARION_WORKFLOW_SKILL } + "content": { "type": "text", "text": LOOMWEAVE_WORKFLOW_SKILL } } ] }), @@ -2888,7 +2889,7 @@ fn token_ceiling_envelope(message: &str) -> Value { }, "diagnostics": [ { - "code": "CLA-LLM-TOKEN-CEILING-EXCEEDED", + "code": "LMWV-LLM-TOKEN-CEILING-EXCEEDED", "message": message } ], @@ -3054,7 +3055,7 @@ fn association_json( drift_status: &str, canonical_entity_id: Option<&str>, ) -> Value { - let entity_id = canonical_entity_id.unwrap_or(&association.clarion_entity_id); + let entity_id = canonical_entity_id.unwrap_or(&association.loomweave_entity_id); let mut value = json!({ "issue_id": association.issue_id, "entity_id": entity_id, @@ -3065,12 +3066,12 @@ fn association_json( "attached_by": association.attached_by, "drift_status": drift_status }); - if entity_id != association.clarion_entity_id + if entity_id != association.loomweave_entity_id && let Some(object) = value.as_object_mut() { object.insert( "association_entity_id".to_owned(), - json!(association.clarion_entity_id), + json!(association.loomweave_entity_id), ); } value @@ -3106,7 +3107,7 @@ struct SourceExcerptError { impl SourceExcerptError { fn message(&self) -> String { format!( - "entity {} source content drifted: stored content_hash {} but current file hashes to {}; rerun `clarion analyze` before requesting LLM output", + "entity {} source content drifted: stored content_hash {} but current file hashes to {}; rerun `loomweave analyze` before requesting LLM output", self.entity_id, self.stored_content_hash, self.current_content_hash ) } @@ -3203,7 +3204,7 @@ fn summary_briefing_blocked(entity_json: &Value, reason: &str) -> Value { let remediation = if reason == "unscanned_source" { "Entity source file was not covered by the pre-ingest secret scan. Re-run with scanner coverage for that path or fix the plugin source path before requesting a summary." } else { - "File flagged by pre-ingest secret scan. Fix the secret or whitelist via .clarion/secrets-baseline.yaml. See ADR-013." + "File flagged by pre-ingest secret scan. Fix the secret or whitelist via .loomweave/secrets-baseline.yaml. See ADR-013." }; let entity_id = entity_json .get("id") @@ -3755,7 +3756,7 @@ struct ResolvedSite { byte_end: Option, } -/// One static call Clarion could not bind (kept separate from resolved sites). +/// One static call Loomweave could not bind (kept separate from resolved sites). struct UnboundSite { owner_id: String, callee_expr: String, @@ -4814,8 +4815,8 @@ mod tests { use std::sync::Arc; use std::time::Duration; - use clarion_core::{CachingModel, LlmProvider, LlmProviderError, LlmRequest, LlmResponse}; - use clarion_storage::{ + use loomweave_core::{CachingModel, LlmProvider, LlmProviderError, LlmRequest, LlmResponse}; + use loomweave_storage::{ EntityRow, InferredEdgeCacheKey, ReaderPool, UnresolvedCallSiteRow, pragma, schema, }; use rusqlite::Connection; @@ -4833,12 +4834,12 @@ mod tests { assert_eq!(tools[0].name, "entity_at"); assert_eq!( tools[0].description, - "Return the innermost Clarion entity whose source range contains a file and line, plus an `entity_context` evidence block: match_reason (decorator_range / declaration / body_range / containing_range / no_match) explaining why the line matched, the module→entity containing stack, the matched entity's decl/body/decorator sub-ranges, any same-granularity ambiguity alternatives, and index freshness. Paths are normalized relative to the project root. A blank or comment line that only a module spans reports containing_range — never a fabricated exact match." + "Return the innermost Loomweave entity whose source range contains a file and line, plus an `entity_context` evidence block: match_reason (decorator_range / declaration / body_range / containing_range / no_match) explaining why the line matched, the module→entity containing stack, the matched entity's decl/body/decorator sub-ranges, any same-granularity ambiguity alternatives, and index freshness. Paths are normalized relative to the project root. A blank or comment line that only a module spans reports containing_range — never a fabricated exact match." ); assert_eq!(tools[1].name, "entity_find"); assert_eq!( tools[1].description, - "Search Clarion entities by id, name, short name, and summary text stored on entity rows. Results are paginated and ranked by FTS match where possible. This does not traverse the graph and does not search on-demand summary_cache entries. Pass an optional `kind` (e.g. \"subsystem\", \"function\", \"class\", \"module\") to return only entities of that kind — the way to locate a subsystem without visually filtering results." + "Search Loomweave entities by id, name, short name, and summary text stored on entity rows. Results are paginated and ranked by FTS match where possible. This does not traverse the graph and does not search on-demand summary_cache entries. Pass an optional `kind` (e.g. \"subsystem\", \"function\", \"class\", \"module\") to return only entities of that kind — the way to locate a subsystem without visually filtering results." ); assert_eq!(tools[2].name, "entity_callers_list"); assert_eq!( @@ -4858,12 +4859,12 @@ mod tests { assert_eq!(tools[5].name, "entity_issue_list"); assert_eq!( tools[5].description, - "Return Filigree issues attached to this Clarion entity, optionally including issues attached to contained entities. Filigree is an enrichment source; if unavailable, the tool returns an unavailable envelope instead of failing Clarion. The result carries a result_kind (matched | no_matches | unavailable) so a reachable-but-empty Filigree is distinct from an unreachable one, and a filigree_endpoint block (configured vs resolved URL + resolution_source) so you can see which endpoint — e.g. a live ethereal port — the answer came from. Each matched/drifted entry carries an `issue` object with the issue's title, status, and priority (fetched once per distinct issue, no N+1); `issue` is null when the issue-detail route is unavailable, so the match still resolves without a second hop into Filigree. Includes a `wardline_findings` section (enrich-only) reconciling Wardline findings to the entity by qualname; `result_kind` is matched|no_matches|unavailable." + "Return Filigree issues attached to this Loomweave entity, optionally including issues attached to contained entities. Filigree is an enrichment source; if unavailable, the tool returns an unavailable envelope instead of failing Loomweave. The result carries a result_kind (matched | no_matches | unavailable) so a reachable-but-empty Filigree is distinct from an unreachable one, and a filigree_endpoint block (configured vs resolved URL + resolution_source) so you can see which endpoint — e.g. a live ethereal port — the answer came from. Each matched/drifted entry carries an `issue` object with the issue's title, status, and priority (fetched once per distinct issue, no N+1); `issue` is null when the issue-detail route is unavailable, so the match still resolves without a second hop into Filigree. Includes a `wardline_findings` section (enrich-only) reconciling Wardline findings to the entity by qualname; `result_kind` is matched|no_matches|unavailable." ); assert_eq!(tools[6].name, "entity_neighborhood_get"); assert_eq!( tools[6].description, - "Return the one-hop Clarion neighborhood around an entity: callers, callees, container, contained entities, references, and imports (imports_in = who imports this module, imports_out = what it imports; module-to-module). Default confidence is resolved; ambiguous and inferred calls are opt-in. References and imports are not execution flow. When the entity is a module, references_in/references_out are rolled up over the symbols it contains (references_rolled_up=true) — each neighbor carries a `via` naming the contained symbol the edge touches, so \"who imports this module/contract\" is answered at module altitude rather than reading empty. On references_in each rolled-up neighbor also carries `importer_module` — the importing symbol's containing module — so reverse-import names importing modules, not just symbols. The result carries scope_excludes naming blind spots not searched (e.g. attribute-receiver-calls) so empty sections are never read as guaranteed true negatives." + "Return the one-hop Loomweave neighborhood around an entity: callers, callees, container, contained entities, references, and imports (imports_in = who imports this module, imports_out = what it imports; module-to-module). Default confidence is resolved; ambiguous and inferred calls are opt-in. References and imports are not execution flow. When the entity is a module, references_in/references_out are rolled up over the symbols it contains (references_rolled_up=true) — each neighbor carries a `via` naming the contained symbol the edge touches, so \"who imports this module/contract\" is answered at module altitude rather than reading empty. On references_in each rolled-up neighbor also carries `importer_module` — the importing symbol's containing module — so reverse-import names importing modules, not just symbols. The result carries scope_excludes naming blind spots not searched (e.g. attribute-receiver-calls) so empty sections are never read as guaranteed true negatives." ); assert_eq!(tools[7].name, "subsystem_member_list"); assert_eq!( @@ -4878,7 +4879,7 @@ mod tests { assert_eq!(tools[9].name, "project_status_get"); assert_eq!( tools[9].description, - "Return deterministic Clarion diagnostics: repo root, db path, latest run (id/status/started/completed), entity/subsystem/edge/finding/briefing-blocked counts, index staleness, per-plugin entity counts from the current index, LLM policy (provider/live/cache), and the resolved Filigree endpoint (configured vs resolved URL + resolution source). Answers \"is the graph fresh, plugin-less, LLM-live, Filigree-reachable?\" without shelling out. No LLM call." + "Return deterministic Loomweave diagnostics: repo root, db path, latest run (id/status/started/completed), entity/subsystem/edge/finding/briefing-blocked counts, index staleness, per-plugin entity counts from the current index, LLM policy (provider/live/cache), and the resolved Filigree endpoint (configured vs resolved URL + resolution source). Answers \"is the graph fresh, plugin-less, LLM-live, Filigree-reachable?\" without shelling out. No LLM call." ); assert_eq!(tools[10].name, "entity_summary_preview_cost_get"); assert_eq!( @@ -4888,12 +4889,12 @@ mod tests { assert_eq!(tools[11].name, "entity_source_get"); assert_eq!( tools[11].description, - "Return the exact indexed source span for one entity (its source_line_start..source_line_end, which includes any decorators/signature/docstring the plugin captured) plus a bounded window of surrounding context, as line-numbered lines each flagged in_entity true/false. No LLM call. Lets an agent read and trust the entity without shelling out. source_status reports `ok`, or — instead of a misleading stale snippet — `missing` (file gone), `no_range`/`no_source_path` (entity has no anchor), `binary` (non-UTF-8), or `drifted` (the file no longer matches the indexed content_hash; rerun `clarion analyze`). context_lines defaults to 10." + "Return the exact indexed source span for one entity (its source_line_start..source_line_end, which includes any decorators/signature/docstring the plugin captured) plus a bounded window of surrounding context, as line-numbered lines each flagged in_entity true/false. No LLM call. Lets an agent read and trust the entity without shelling out. source_status reports `ok`, or — instead of a misleading stale snippet — `missing` (file gone), `no_range`/`no_source_path` (entity has no anchor), `binary` (non-UTF-8), or `drifted` (the file no longer matches the indexed content_hash; rerun `loomweave analyze`). context_lines defaults to 10." ); assert_eq!(tools[12].name, "entity_call_site_list"); assert_eq!( tools[12].description, - "Show the actual source sites behind calls/references edges, so an agent can see WHY Clarion believes an edge exists rather than trusting it blind. role=caller (default) returns this entity's outgoing sites (what it calls/references); role=callee returns incoming sites (who calls/references it). Each site carries the file path, 1-based line, byte column, the source line text, edge kind, confidence, and a resolution of resolved | ambiguous (with candidate ids) | unresolved (a static call Clarion could not bind, kept separate so it is never mixed with resolved evidence). Filter by edge kind (`calls`/`references`) and by a best-effort production/test path heuristic (`all`/`production`/`test`; path partitioning is not indexed — the heuristic matches conventional test paths). Output is bounded; truncated flags when the site cap trims. No LLM call." + "Show the actual source sites behind calls/references edges, so an agent can see WHY Loomweave believes an edge exists rather than trusting it blind. role=caller (default) returns this entity's outgoing sites (what it calls/references); role=callee returns incoming sites (who calls/references it). Each site carries the file path, 1-based line, byte column, the source line text, edge kind, confidence, and a resolution of resolved | ambiguous (with candidate ids) | unresolved (a static call Loomweave could not bind, kept separate so it is never mixed with resolved evidence). Filter by edge kind (`calls`/`references`) and by a best-effort production/test path heuristic (`all`/`production`/`test`; path partitioning is not indexed — the heuristic matches conventional test paths). Output is bounded; truncated flags when the site cap trims. No LLM call." ); assert_eq!(tools[13].name, "entity_orientation_pack_get"); assert_eq!( @@ -4903,7 +4904,7 @@ mod tests { assert_eq!(tools[14].name, "analyze_start"); assert_eq!( tools[14].description, - "Start a `clarion analyze` run over this project in the background and return its run handle immediately — do not block on the (possibly many-minute) run. Re-indexes the source tree and refreshes entities/edges/subsystems. Returns run_id, status (`started`), and the progress-file path. Only one analyze may run per project at a time (a cross-process lock enforces it); a second start while one is active is rejected. Poll analyze_status for progress; analyze_cancel to stop. No arguments." + "Start a `loomweave analyze` run over this project in the background and return its run handle immediately — do not block on the (possibly many-minute) run. Re-indexes the source tree and refreshes entities/edges/subsystems. Returns run_id, status (`started`), and the progress-file path. Only one analyze may run per project at a time (a cross-process lock enforces it); a second start while one is active is rejected. Poll analyze_status for progress; analyze_cancel to stop. No arguments." ); assert_eq!(tools[15].name, "analyze_status_get"); assert_eq!( @@ -4975,14 +4976,14 @@ mod tests { response["result"]["protocolVersion"], super::MCP_PROTOCOL_VERSION ); - assert_eq!(response["result"]["serverInfo"]["name"], "clarion"); + assert_eq!(response["result"]["serverInfo"]["name"], "loomweave"); assert!(response["result"]["capabilities"]["tools"].is_object()); // Orientation instructions present and mention the skill + entity model. let instructions = response["result"]["instructions"] .as_str() .expect("initialize result has instructions"); assert!( - instructions.contains("clarion-workflow"), + instructions.contains("loomweave-workflow"), "instructions should point at the skill" ); assert!( @@ -5031,7 +5032,7 @@ mod tests { #[tokio::test] async fn stateful_initialize_advertises_prompts_and_resources() { let dir = tempfile::tempdir().unwrap(); - let db = dir.path().join("clarion.db"); + let db = dir.path().join("loomweave.db"); { let mut conn = rusqlite::Connection::open(&db).unwrap(); pragma::apply_write_pragmas(&conn).unwrap(); @@ -5069,15 +5070,15 @@ mod tests { .as_str() .expect("initialize result has instructions"); assert!( - instructions.contains("clarion-workflow"), + instructions.contains("loomweave-workflow"), "instructions should point at the skill" ); } #[tokio::test] - async fn resources_list_includes_clarion_context() { + async fn resources_list_includes_loomweave_context() { let dir = tempfile::tempdir().unwrap(); - let db = dir.path().join("clarion.db"); + let db = dir.path().join("loomweave.db"); { let mut conn = rusqlite::Connection::open(&db).unwrap(); pragma::apply_write_pragmas(&conn).unwrap(); @@ -5098,15 +5099,15 @@ mod tests { let resources = response["result"]["resources"].as_array().unwrap(); assert!( - resources.iter().any(|r| r["uri"] == "clarion://context"), - "clarion://context not listed: {resources:?}" + resources.iter().any(|r| r["uri"] == "loomweave://context"), + "loomweave://context not listed: {resources:?}" ); } #[tokio::test] async fn resources_read_returns_context_snapshot_json() { let dir = tempfile::tempdir().unwrap(); - let db = dir.path().join("clarion.db"); + let db = dir.path().join("loomweave.db"); { let mut conn = rusqlite::Connection::open(&db).unwrap(); pragma::apply_write_pragmas(&conn).unwrap(); @@ -5128,7 +5129,7 @@ mod tests { "jsonrpc": "2.0", "id": 7, "method": "resources/read", - "params": {"uri": "clarion://context"} + "params": {"uri": "loomweave://context"} })) .await .expect("response"); @@ -5151,7 +5152,7 @@ mod tests { #[tokio::test] async fn resources_read_rejects_unknown_uri() { let dir = tempfile::tempdir().unwrap(); - let db = dir.path().join("clarion.db"); + let db = dir.path().join("loomweave.db"); { let mut conn = rusqlite::Connection::open(&db).unwrap(); pragma::apply_write_pragmas(&conn).unwrap(); @@ -5165,7 +5166,7 @@ mod tests { "jsonrpc": "2.0", "id": 8, "method": "resources/read", - "params": {"uri": "clarion://nope"} + "params": {"uri": "loomweave://nope"} })) .await .expect("response"); @@ -5176,7 +5177,7 @@ mod tests { #[tokio::test] async fn prompts_get_rejects_unknown_name() { let dir = tempfile::tempdir().unwrap(); - let db = dir.path().join("clarion.db"); + let db = dir.path().join("loomweave.db"); { let mut conn = rusqlite::Connection::open(&db).unwrap(); pragma::apply_write_pragmas(&conn).unwrap(); @@ -5201,7 +5202,7 @@ mod tests { #[tokio::test] async fn prompts_get_returns_skill_text() { let dir = tempfile::tempdir().unwrap(); - let db = dir.path().join("clarion.db"); + let db = dir.path().join("loomweave.db"); { let mut conn = rusqlite::Connection::open(&db).unwrap(); pragma::apply_write_pragmas(&conn).unwrap(); @@ -5215,7 +5216,7 @@ mod tests { "jsonrpc": "2.0", "id": 9, "method": "prompts/get", - "params": {"name": "clarion-workflow"} + "params": {"name": "loomweave-workflow"} })) .await .expect("response"); @@ -5223,15 +5224,15 @@ mod tests { .as_str() .unwrap(); assert!( - text.contains("name: clarion-workflow"), + text.contains("name: loomweave-workflow"), "not the skill text" ); } #[tokio::test] - async fn prompts_list_includes_clarion_workflow() { + async fn prompts_list_includes_loomweave_workflow() { let dir = tempfile::tempdir().unwrap(); - let db = dir.path().join("clarion.db"); + let db = dir.path().join("loomweave.db"); { let mut conn = rusqlite::Connection::open(&db).unwrap(); pragma::apply_write_pragmas(&conn).unwrap(); @@ -5250,7 +5251,7 @@ mod tests { .await .expect("response"); let prompts = response["result"]["prompts"].as_array().unwrap(); - assert!(prompts.iter().any(|p| p["name"] == "clarion-workflow")); + assert!(prompts.iter().any(|p| p["name"] == "loomweave-workflow")); } #[test] @@ -5286,7 +5287,7 @@ mod tests { #[tokio::test] async fn server_policy_hides_and_blocks_write_tools_by_default() { let dir = tempfile::tempdir().unwrap(); - let db = dir.path().join("clarion.db"); + let db = dir.path().join("loomweave.db"); { let mut conn = rusqlite::Connection::open(&db).unwrap(); pragma::apply_write_pragmas(&conn).unwrap(); @@ -5335,7 +5336,7 @@ mod tests { #[tokio::test] async fn server_policy_can_advertise_write_tools() { let dir = tempfile::tempdir().unwrap(); - let db = dir.path().join("clarion.db"); + let db = dir.path().join("loomweave.db"); { let mut conn = rusqlite::Connection::open(&db).unwrap(); pragma::apply_write_pragmas(&conn).unwrap(); @@ -5368,7 +5369,7 @@ mod tests { #[tokio::test] async fn stateful_tools_list_filters_write_tools_when_policy_disables_them() { let dir = tempfile::tempdir().unwrap(); - let db = dir.path().join("clarion.db"); + let db = dir.path().join("loomweave.db"); { let mut conn = rusqlite::Connection::open(&db).unwrap(); pragma::apply_write_pragmas(&conn).unwrap(); @@ -5410,7 +5411,7 @@ mod tests { #[tokio::test] async fn stateful_tools_call_rejects_disabled_write_tool() { let dir = tempfile::tempdir().unwrap(); - let db = dir.path().join("clarion.db"); + let db = dir.path().join("loomweave.db"); { let mut conn = rusqlite::Connection::open(&db).unwrap(); pragma::apply_write_pragmas(&conn).unwrap(); @@ -5442,7 +5443,7 @@ mod tests { #[tokio::test] async fn stateful_tools_call_rejects_inferred_confidence_when_write_tools_disabled() { let dir = tempfile::tempdir().unwrap(); - let db = dir.path().join("clarion.db"); + let db = dir.path().join("loomweave.db"); { let mut conn = rusqlite::Connection::open(&db).unwrap(); pragma::apply_write_pragmas(&conn).unwrap(); @@ -5477,7 +5478,7 @@ mod tests { #[tokio::test] async fn stateful_tools_call_rejects_malformed_confidence_arguments() { let dir = tempfile::tempdir().unwrap(); - let db = dir.path().join("clarion.db"); + let db = dir.path().join("loomweave.db"); { let mut conn = rusqlite::Connection::open(&db).unwrap(); pragma::apply_write_pragmas(&conn).unwrap(); @@ -5529,7 +5530,7 @@ mod tests { #[tokio::test] async fn stateful_tools_call_rejects_unknown_arguments_from_strict_schema() { let dir = tempfile::tempdir().unwrap(); - let db = dir.path().join("clarion.db"); + let db = dir.path().join("loomweave.db"); { let mut conn = rusqlite::Connection::open(&db).unwrap(); pragma::apply_write_pragmas(&conn).unwrap(); @@ -5641,7 +5642,7 @@ mod tests { #[test] fn frame_dispatch_decodes_and_reencodes_json_rpc() { - let frame = clarion_core::plugin::Frame { + let frame = loomweave_core::plugin::Frame { body: serde_json::to_vec(&serde_json::json!({ "jsonrpc": "2.0", "id": 10, @@ -5670,7 +5671,7 @@ mod tests { #[test] fn frame_dispatch_returns_none_for_json_rpc_notifications() { - let frame = clarion_core::plugin::Frame { + let frame = loomweave_core::plugin::Frame { body: serde_json::to_vec(&serde_json::json!({ "jsonrpc": "2.0", "method": "notifications/initialized", @@ -5687,9 +5688,9 @@ mod tests { #[test] fn serve_stdio_handles_multiple_content_length_frames() { let mut input = Vec::new(); - clarion_core::plugin::write_frame( + loomweave_core::plugin::write_frame( &mut input, - &clarion_core::plugin::Frame { + &loomweave_core::plugin::Frame { body: serde_json::to_vec(&serde_json::json!({ "jsonrpc": "2.0", "id": 11, @@ -5704,9 +5705,9 @@ mod tests { }, ) .unwrap(); - clarion_core::plugin::write_frame( + loomweave_core::plugin::write_frame( &mut input, - &clarion_core::plugin::Frame { + &loomweave_core::plugin::Frame { body: serde_json::to_vec(&serde_json::json!({ "jsonrpc": "2.0", "id": 12, @@ -5724,21 +5725,21 @@ mod tests { super::serve_stdio(&mut reader, &mut output).unwrap(); let mut response_reader = std::io::BufReader::new(std::io::Cursor::new(output)); - let first = clarion_core::plugin::read_frame( + let first = loomweave_core::plugin::read_frame( &mut response_reader, - clarion_core::plugin::ContentLengthCeiling::new(usize::MAX), + loomweave_core::plugin::ContentLengthCeiling::new(usize::MAX), ) .unwrap(); - let second = clarion_core::plugin::read_frame( + let second = loomweave_core::plugin::read_frame( &mut response_reader, - clarion_core::plugin::ContentLengthCeiling::new(usize::MAX), + loomweave_core::plugin::ContentLengthCeiling::new(usize::MAX), ) .unwrap(); let first_json: serde_json::Value = serde_json::from_slice(&first.body).unwrap(); let second_json: serde_json::Value = serde_json::from_slice(&second.body).unwrap(); assert_eq!(first_json["id"], 11); - assert_eq!(first_json["result"]["serverInfo"]["name"], "clarion"); + assert_eq!(first_json["result"]["serverInfo"]["name"], "loomweave"); assert_eq!(second_json["id"], 12); let tools = second_json["result"]["tools"].as_array().unwrap(); let tool_names: Vec<&str> = tools @@ -5763,7 +5764,7 @@ mod tests { #[test] fn serve_stdio_with_state_ignores_json_rpc_notifications() { let project = tempfile::tempdir().expect("temp project"); - let db_path = project.path().join("clarion.db"); + let db_path = project.path().join("loomweave.db"); let mut conn = Connection::open(&db_path).expect("open sqlite"); pragma::apply_write_pragmas(&conn).expect("write pragmas"); schema::apply_migrations(&mut conn).expect("apply migrations"); @@ -5782,7 +5783,7 @@ mod tests { #[test] fn serve_stdio_with_state_uses_json_line_transport_for_json_line_requests() { let project = tempfile::tempdir().expect("temp project"); - let db_path = project.path().join("clarion.db"); + let db_path = project.path().join("loomweave.db"); let mut conn = Connection::open(&db_path).expect("open sqlite"); pragma::apply_write_pragmas(&conn).expect("write pragmas"); schema::apply_migrations(&mut conn).expect("apply migrations"); @@ -5800,9 +5801,9 @@ mod tests { fn notification_sequence_input(initialize_id: u64, tools_list_id: u64) -> Vec { let mut input = Vec::new(); - clarion_core::plugin::write_frame( + loomweave_core::plugin::write_frame( &mut input, - &clarion_core::plugin::Frame { + &loomweave_core::plugin::Frame { body: serde_json::to_vec(&serde_json::json!({ "jsonrpc": "2.0", "id": initialize_id, @@ -5817,9 +5818,9 @@ mod tests { }, ) .unwrap(); - clarion_core::plugin::write_frame( + loomweave_core::plugin::write_frame( &mut input, - &clarion_core::plugin::Frame { + &loomweave_core::plugin::Frame { body: serde_json::to_vec(&serde_json::json!({ "jsonrpc": "2.0", "method": "notifications/initialized", @@ -5829,9 +5830,9 @@ mod tests { }, ) .unwrap(); - clarion_core::plugin::write_frame( + loomweave_core::plugin::write_frame( &mut input, - &clarion_core::plugin::Frame { + &loomweave_core::plugin::Frame { body: serde_json::to_vec(&serde_json::json!({ "jsonrpc": "2.0", "id": tools_list_id, @@ -5851,14 +5852,14 @@ mod tests { tools_list_id: u64, ) { let mut response_reader = std::io::BufReader::new(std::io::Cursor::new(output)); - let first = clarion_core::plugin::read_frame( + let first = loomweave_core::plugin::read_frame( &mut response_reader, - clarion_core::plugin::ContentLengthCeiling::new(usize::MAX), + loomweave_core::plugin::ContentLengthCeiling::new(usize::MAX), ) .unwrap(); - let second = clarion_core::plugin::read_frame( + let second = loomweave_core::plugin::read_frame( &mut response_reader, - clarion_core::plugin::ContentLengthCeiling::new(usize::MAX), + loomweave_core::plugin::ContentLengthCeiling::new(usize::MAX), ) .unwrap(); let first_json: serde_json::Value = serde_json::from_slice(&first.body).unwrap(); @@ -5867,9 +5868,9 @@ mod tests { assert_eq!(first_json["id"], initialize_id); assert_eq!(second_json["id"], tools_list_id); assert!( - clarion_core::plugin::read_frame( + loomweave_core::plugin::read_frame( &mut response_reader, - clarion_core::plugin::ContentLengthCeiling::new(usize::MAX), + loomweave_core::plugin::ContentLengthCeiling::new(usize::MAX), ) .is_err(), "notifications must not produce JSON-RPC response frames" @@ -5930,7 +5931,7 @@ mod tests { #[tokio::test] async fn inferred_inflight_entry_is_removed_when_leader_future_is_aborted() { let project = tempfile::tempdir().expect("temp project"); - let db_path = project.path().join("clarion.db"); + let db_path = project.path().join("loomweave.db"); let mut conn = Connection::open(&db_path).expect("open sqlite"); pragma::apply_write_pragmas(&conn).expect("write pragmas"); schema::apply_migrations(&mut conn).expect("apply migrations"); @@ -5979,7 +5980,7 @@ mod tests { let content_hash = blake3::hash("def target():\n return 1".as_bytes()) .to_hex() .to_string(); - let db_path = project.path().join("clarion.db"); + let db_path = project.path().join("loomweave.db"); let mut conn = Connection::open(&db_path).expect("open sqlite"); pragma::apply_write_pragmas(&conn).expect("write pragmas"); schema::apply_migrations(&mut conn).expect("apply migrations"); @@ -6178,7 +6179,7 @@ mod tests { // (`entity_json`); with no bindings seeded, `sei` degrades to null, // which is irrelevant to this briefing-block assertion. WAL (ADR-011) // requires a file-backed DB, so use the tempdir rather than :memory:. - let db_path = dir.path().join("clarion.db"); + let db_path = dir.path().join("loomweave.db"); let mut conn = Connection::open(&db_path).expect("open sqlite"); pragma::apply_write_pragmas(&conn).expect("write pragmas"); schema::apply_migrations(&mut conn).expect("apply migrations"); @@ -6212,7 +6213,7 @@ mod tests { let path = dir.path().join("secret.py"); std::fs::write(&path, "API_KEY = 'super-secret-value'\n").unwrap(); - let db_path = dir.path().join("clarion.db"); + let db_path = dir.path().join("loomweave.db"); let mut conn = Connection::open(&db_path).expect("open sqlite"); pragma::apply_write_pragmas(&conn).expect("write pragmas"); schema::apply_migrations(&mut conn).expect("apply migrations"); diff --git a/crates/loomweave-mcp/src/scan_results.rs b/crates/loomweave-mcp/src/scan_results.rs new file mode 100644 index 00000000..553eb8af --- /dev/null +++ b/crates/loomweave-mcp/src/scan_results.rs @@ -0,0 +1 @@ +pub use loomweave_federation::scan_results::*; diff --git a/crates/clarion-mcp/src/snapshot.rs b/crates/loomweave-mcp/src/snapshot.rs similarity index 96% rename from crates/clarion-mcp/src/snapshot.rs rename to crates/loomweave-mcp/src/snapshot.rs index 9e2de8d2..bc58a9ba 100644 --- a/crates/clarion-mcp/src/snapshot.rs +++ b/crates/loomweave-mcp/src/snapshot.rs @@ -1,7 +1,7 @@ //! Shared project snapshot: entity/subsystem/finding counts + index staleness. //! -//! One function, two callers: the `clarion hook session-start` subcommand and -//! the MCP `clarion://context` resource. Infallible by design — every failure +//! One function, two callers: the `loomweave hook session-start` subcommand and +//! the MCP `loomweave://context` resource. Infallible by design — every failure //! folds into the snapshot (zero counts, `Staleness::Unknown`) so the fail-soft //! hook never has to handle an error. Degrade, but don't go quiet: a real query //! failure is `tracing::warn!`-logged before it folds, so a populated index @@ -15,7 +15,7 @@ use std::time::SystemTime; use rusqlite::Connection; use serde::Serialize; -/// Freshness of the `.clarion/` index relative to the source files Clarion +/// Freshness of the `.loomweave/` index relative to the source files Loomweave /// ingested. See the plan's Decision Point (b) for the algorithm. /// /// Freshness combines two passes over the files recorded in @@ -31,11 +31,11 @@ use serde::Serialize; /// when no tracked source actually changed. The watch set is the *direct /// parents* of ingested files, so an addition/removal in any directory that /// is not such a parent goes undetected — always including the project root -/// itself, which is deliberately never watched (`analyze` writes `.clarion/` +/// itself, which is deliberately never watched (`analyze` writes `.loomweave/` /// under it, which would otherwise wedge every check to a permanent Stale). /// 2. **In-place modification** — an ingested file edited since the run. This /// needs one `stat` per file and is bounded by `MAX_MODIFICATION_STAT_FILES` -/// so `clarion hook session-start` stays cheap on large repos +/// so `loomweave hook session-start` stays cheap on large repos /// (clarion-93465ff89e); the structural pass runs first and short-circuits /// the common "repo changed" case before any file is stat-ed. /// @@ -74,7 +74,7 @@ pub enum Staleness { Unknown, } -/// Counts + freshness for one Clarion project, safe to serialize into the MCP +/// Counts + freshness for one Loomweave project, safe to serialize into the MCP /// resource or print from the hook. /// /// Fields are private and read through accessors so the documented invariant — @@ -119,7 +119,7 @@ pub struct ProjectSnapshot { } impl ProjectSnapshot { - /// Whether a readable `.clarion/clarion.db` was found. When `false`, every + /// Whether a readable `.loomweave/loomweave.db` was found. When `false`, every /// count is `0` and `staleness` is [`Staleness::NeverAnalyzed`]. #[must_use] pub fn db_present(&self) -> bool { @@ -234,7 +234,7 @@ pub fn missing_db_snapshot() -> ProjectSnapshot { } /// A degraded snapshot for a database that *is* present but could not be read -/// or serialized (the MCP `clarion://context` reader-pool / serialize-error +/// or serialized (the MCP `loomweave://context` reader-pool / serialize-error /// fallback): `db_present: true`, all counts `0`, [`Staleness::Unknown`], no /// timestamp, and `degraded: true` so a consumer never mistakes the zero counts /// for a genuinely empty index. The single construction site for this case, @@ -260,7 +260,7 @@ fn scalar_count(conn: &Connection, sql: &str, degraded: &mut bool) -> i64 { match conn.query_row(sql, [], |row| row.get::<_, i64>(0)) { Ok(n) => n, Err(err) => { - tracing::warn!(error = %err, sql, "clarion snapshot count query failed; reporting 0"); + tracing::warn!(error = %err, sql, "loomweave snapshot count query failed; reporting 0"); *degraded = true; 0 } @@ -281,7 +281,7 @@ fn latest_completed_run(conn: &Connection, degraded: &mut bool) -> Option Some(s), Err(rusqlite::Error::QueryReturnedNoRows) => None, Err(err) => { - tracing::warn!(error = %err, "clarion latest-completed-run query failed"); + tracing::warn!(error = %err, "loomweave latest-completed-run query failed"); *degraded = true; None } @@ -290,7 +290,7 @@ fn latest_completed_run(conn: &Connection, degraded: &mut bool) -> Option stmt, Err(err) => { - tracing::warn!(error = %err, "clarion staleness source-path query failed"); + tracing::warn!(error = %err, "loomweave staleness source-path query failed"); *degraded = true; return None; } @@ -439,7 +439,7 @@ fn file_modification_drift( tracing::warn!( ingested_files = files.len(), cap = MAX_MODIFICATION_STAT_FILES, - "clarion staleness: ingested-file count exceeds the modification-scan cap; \ + "loomweave staleness: ingested-file count exceeds the modification-scan cap; \ in-place edits beyond the cap may go unnoticed until the next analyze" ); } @@ -460,7 +460,7 @@ fn parse_iso8601_to_systemtime(iso: &str) -> Option { mod tests { use rusqlite::Connection; - use clarion_storage::{pragma, schema}; + use loomweave_storage::{pragma, schema}; use std::time::Duration; @@ -471,11 +471,11 @@ mod tests { // `apply_write_pragmas` enforces ADR-011's WAL journal-mode invariant, which // an in-memory connection cannot satisfy (`journal_mode=memory`). Back the // test db with a file in a `TempDir`, matching the canonical pattern in - // `clarion-storage`'s own integration tests. The `TempDir` is returned so the + // `loomweave-storage`'s own integration tests. The `TempDir` is returned so the // caller keeps it alive for the connection's lifetime. fn migrated_conn() -> (tempfile::TempDir, Connection) { let dir = tempfile::tempdir().unwrap(); - let path = dir.path().join("clarion.db"); + let path = dir.path().join("loomweave.db"); let mut conn = Connection::open(path).unwrap(); pragma::apply_write_pragmas(&conn).unwrap(); schema::apply_migrations(&mut conn).unwrap(); @@ -520,7 +520,7 @@ mod tests { "INSERT INTO findings \ (id, tool, tool_version, run_id, rule_id, kind, severity, entity_id, \ related_entities, message, evidence, properties, supports, supported_by, status, created_at, updated_at) \ - VALUES ('f1','clarion','1.0','run1','R1','defect','WARN','python:module:a', \ + VALUES ('f1','loomweave','1.0','run1','R1','defect','WARN','python:module:a', \ '[]','m','{}','{}','[]','[]','open','2026-01-01T00:00:00.000Z','2026-01-01T00:00:00.000Z')", [], ) @@ -701,7 +701,7 @@ mod tests { #[test] fn no_source_paths_serializes_to_snake_case() { // The new wire value is `"no_source_paths"` (serde rename_all = - // "snake_case"); pin it so the clarion://context / project_status + // "snake_case"); pin it so the loomweave://context / project_status // vocabulary can't drift silently. let json = serde_json::to_value(Staleness::NoSourcePaths).unwrap(); assert_eq!(json, serde_json::Value::String("no_source_paths".into())); diff --git a/crates/clarion-mcp/src/tools/analyze.rs b/crates/loomweave-mcp/src/tools/analyze.rs similarity index 95% rename from crates/clarion-mcp/src/tools/analyze.rs rename to crates/loomweave-mcp/src/tools/analyze.rs index 31c8794a..180d2312 100644 --- a/crates/clarion-mcp/src/tools/analyze.rs +++ b/crates/loomweave-mcp/src/tools/analyze.rs @@ -4,10 +4,10 @@ //! [`crate::ServerState`] via an inherent `impl` block; `lib.rs` keeps the //! shared free-function helpers, the tool catalogue, and the JSON-RPC dispatch. -use clarion_core::McpErrorCode; +use loomweave_core::McpErrorCode; use serde_json::{Value, json}; -use clarion_storage::StorageError; +use loomweave_storage::StorageError; use crate::{ ANALYZE_HEARTBEAT_STALE_SECS, CancelOutcome, LiveRun, ParamError, ServerState, elapsed_seconds, @@ -30,7 +30,9 @@ impl ServerState { Err(err) => { return Ok(tool_error_envelope( McpErrorCode::SpawnFailed, - &format!("cannot resolve the clarion executable to launch analyze: {err}"), + &format!( + "cannot resolve the loomweave executable to launch analyze: {err}" + ), false, )); } @@ -38,7 +40,7 @@ impl ServerState { }; let run_id = uuid::Uuid::new_v4().to_string(); - let runs_dir = self.project_root.join(".clarion").join("runs"); + let runs_dir = self.project_root.join(".loomweave").join("runs"); if let Err(err) = std::fs::create_dir_all(&runs_dir) { return Ok(tool_error_envelope( McpErrorCode::IoError, @@ -58,7 +60,7 @@ impl ServerState { // accumulate dead entries / `runs/*.progress.json` files // (clarion-7e0c21558a). crate::analyze_runs::reap_terminal_runs(&mut registry); - // Reject a concurrent run: a second `clarion analyze` would fail to + // Reject a concurrent run: a second `loomweave analyze` would fail to // acquire the project's cross-process lock anyway, so surface it as a // clear error rather than spawning a doomed child. let already_active = registry @@ -83,7 +85,7 @@ impl ServerState { Err(err) => { return Ok(tool_error_envelope( McpErrorCode::SpawnFailed, - &format!("failed to spawn `clarion analyze`: {err}"), + &format!("failed to spawn `loomweave analyze`: {err}"), false, )); } @@ -216,7 +218,7 @@ impl ServerState { match outcome { CancelOutcome::Cancelled => { - let db_path = self.project_root.join(".clarion").join("clarion.db"); + let db_path = self.project_root.join(".loomweave").join("loomweave.db"); crate::analyze_runs::mark_run_cancelled_in_db(&db_path, &run_id, &now); Ok(success_envelope(json!({ "run_id": run_id, diff --git a/crates/clarion-mcp/src/tools/graph.rs b/crates/loomweave-mcp/src/tools/graph.rs similarity index 98% rename from crates/clarion-mcp/src/tools/graph.rs rename to crates/loomweave-mcp/src/tools/graph.rs index 99f8bd4e..ebbd8425 100644 --- a/crates/clarion-mcp/src/tools/graph.rs +++ b/crates/loomweave-mcp/src/tools/graph.rs @@ -8,10 +8,10 @@ use std::collections::{BTreeSet, HashMap}; -use clarion_core::{EdgeConfidence, McpErrorCode}; +use loomweave_core::{EdgeConfidence, McpErrorCode}; use serde_json::{Value, json}; -use clarion_storage::{ +use loomweave_storage::{ ReferenceDirection, StorageError, ancestor_chain, call_edges_from, call_edges_targeting, child_entity_ids, entities_containing_line, entity_by_id, find_entities, normalize_source_path, subsystem_members, subsystem_of_entity, @@ -428,7 +428,7 @@ impl ServerState { return Ok(issues_unavailable( &endpoint, "entity-not-found", - "Clarion entity was not found", + "Loomweave entity was not found", )); } Err(err) => { @@ -511,12 +511,12 @@ impl ServerState { details.insert(issue_id, detail); } Ok(Err(err)) => { - tracing::warn!(error = %err, "clarion issues_for detail fetch failed; degrading to issue-id-only"); + tracing::warn!(error = %err, "loomweave issues_for detail fetch failed; degrading to issue-id-only"); route_down = true; details.insert(issue_id, None); } Err(err) => { - tracing::warn!(error = %err, "clarion issues_for detail task failed; degrading to issue-id-only"); + tracing::warn!(error = %err, "loomweave issues_for detail task failed; degrading to issue-id-only"); route_down = true; details.insert(issue_id, None); } diff --git a/crates/clarion-mcp/src/tools/mod.rs b/crates/loomweave-mcp/src/tools/mod.rs similarity index 100% rename from crates/clarion-mcp/src/tools/mod.rs rename to crates/loomweave-mcp/src/tools/mod.rs diff --git a/crates/clarion-mcp/src/tools/orientation.rs b/crates/loomweave-mcp/src/tools/orientation.rs similarity index 99% rename from crates/clarion-mcp/src/tools/orientation.rs rename to crates/loomweave-mcp/src/tools/orientation.rs index 0ea06db4..d7a4f0ff 100644 --- a/crates/clarion-mcp/src/tools/orientation.rs +++ b/crates/loomweave-mcp/src/tools/orientation.rs @@ -4,10 +4,10 @@ //! [`crate::ServerState`] via an inherent `impl` block; `lib.rs` keeps the //! shared free-function helpers, the tool catalogue, and the JSON-RPC dispatch. -use clarion_core::{EdgeConfidence, McpErrorCode}; +use loomweave_core::{EdgeConfidence, McpErrorCode}; use serde_json::{Value, json}; -use clarion_storage::{ +use loomweave_storage::{ ReferenceDirection, StorageError, ancestor_chain, call_edges_from, call_edges_targeting, child_entity_ids, entities_containing_line, entity_by_id, has_any_alive_binding, normalize_source_path, @@ -337,7 +337,7 @@ impl ServerState { if core.staleness_stale { warnings.push( "Index is stale: at least one ingested source file is newer than the last \ - analyze run. Re-run `clarion analyze`." + analyze run. Re-run `loomweave analyze`." .to_owned(), ); } diff --git a/crates/clarion-mcp/src/tools/status.rs b/crates/loomweave-mcp/src/tools/status.rs similarity index 98% rename from crates/clarion-mcp/src/tools/status.rs rename to crates/loomweave-mcp/src/tools/status.rs index 2a86cb64..abeec78b 100644 --- a/crates/clarion-mcp/src/tools/status.rs +++ b/crates/loomweave-mcp/src/tools/status.rs @@ -6,10 +6,10 @@ use std::collections::HashMap; -use clarion_core::{LeafSummaryPromptInput, McpErrorCode, build_leaf_summary_prompt}; +use loomweave_core::{LeafSummaryPromptInput, McpErrorCode, build_leaf_summary_prompt}; use serde_json::{Value, json}; -use clarion_storage::{StorageError, contained_entity_ids, entity_by_id, has_any_alive_binding}; +use loomweave_storage::{StorageError, contained_entity_ids, entity_by_id, has_any_alive_binding}; use crate::{ IssuesForRead, ParamError, SUMMARY_MAX_OUTPUT_TOKENS, ServerState, SummaryRead, entity_json, @@ -174,7 +174,7 @@ impl ServerState { &self, _arguments: &serde_json::Map, ) -> std::result::Result { - let db_path = self.project_root.join(".clarion").join("clarion.db"); + let db_path = self.project_root.join(".loomweave").join("loomweave.db"); let root_display = self.project_root.display().to_string(); let project_root = self.project_root.clone(); diff --git a/crates/clarion-mcp/src/tools/summary.rs b/crates/loomweave-mcp/src/tools/summary.rs similarity index 99% rename from crates/clarion-mcp/src/tools/summary.rs rename to crates/loomweave-mcp/src/tools/summary.rs index 15a92b06..215e8d16 100644 --- a/crates/clarion-mcp/src/tools/summary.rs +++ b/crates/loomweave-mcp/src/tools/summary.rs @@ -8,7 +8,7 @@ use std::collections::HashSet; use std::path::Path; use std::sync::Arc; -use clarion_core::{ +use loomweave_core::{ EdgeConfidence, INFERRED_CALLS_PROMPT_VERSION, InferredCallsPromptInput, LEAF_SUMMARY_PROMPT_TEMPLATE_ID, LeafSummaryPromptInput, LlmPurpose, LlmRequest, McpErrorCode, build_inferred_calls_prompt, build_leaf_summary_prompt, @@ -16,7 +16,7 @@ use clarion_core::{ use serde_json::{Value, json}; use tokio::sync::{broadcast, mpsc, oneshot}; -use clarion_storage::{ +use loomweave_storage::{ EntityRow, InferredCallEdgeRecord, InferredEdgeCacheEntry, InferredEdgeCacheKey, StorageError, SummaryCacheEntry, SummaryCacheKey, WriterCmd, call_edges_from, call_edges_targeting, candidate_entities_for_unresolved_sites, entity_by_id, existing_entity_ids, @@ -407,7 +407,7 @@ impl ServerState { return Err(err.with_stats( inferred_usage_stats(&response, true), vec![json!({ - "code": "CLA-LLM-INVALID-JSON", + "code": "LMWV-LLM-INVALID-JSON", "message": message, "usage": llm_usage_json(&response) })], diff --git a/crates/clarion-mcp/src/wardline_reconcile.rs b/crates/loomweave-mcp/src/wardline_reconcile.rs similarity index 96% rename from crates/clarion-mcp/src/wardline_reconcile.rs rename to crates/loomweave-mcp/src/wardline_reconcile.rs index f8f89a42..c7bc3983 100644 --- a/crates/clarion-mcp/src/wardline_reconcile.rs +++ b/crates/loomweave-mcp/src/wardline_reconcile.rs @@ -1,4 +1,4 @@ -//! Reconcile Wardline findings to Clarion entities by qualname (Flow B). +//! Reconcile Wardline findings to Loomweave entities by qualname (Flow B). //! //! `metadata.wardline.qualname` is the pre-composed dotted name, which for a //! function/method entity is byte-identical to the `entity_id`'s segment-3 @@ -6,11 +6,11 @@ //! `fixtures/entity_id.json`; the Wardline-side qualname normalization that //! makes the two byte-identical is pinned in `docs/federation/contracts.md` //! §"Wardline qualname normalization"). Matching is therefore a local string -//! compare against Clarion's own catalog — no oracle. +//! compare against Loomweave's own catalog — no oracle. use crate::filigree::WardlineFinding; -/// A Wardline finding's resolution against a Clarion entity. v1 produces only +/// A Wardline finding's resolution against a Loomweave entity. v1 produces only /// `Exact` (byte-equal qualname) or `None`. `Heuristic` is reserved for a future /// best-effort normalization pass and is never returned yet — kept in the enum /// so the wire shape is stable when it lands. diff --git a/crates/clarion-mcp/tests/analyze_lifecycle.rs b/crates/loomweave-mcp/tests/analyze_lifecycle.rs similarity index 97% rename from crates/clarion-mcp/tests/analyze_lifecycle.rs rename to crates/loomweave-mcp/tests/analyze_lifecycle.rs index ca5c4164..5149accb 100644 --- a/crates/clarion-mcp/tests/analyze_lifecycle.rs +++ b/crates/loomweave-mcp/tests/analyze_lifecycle.rs @@ -13,16 +13,16 @@ use std::os::unix::fs::PermissionsExt; use std::path::{Path, PathBuf}; use std::time::Duration; -use clarion_mcp::{McpToolPolicy, ServerState}; -use clarion_storage::{ReaderPool, pragma, schema}; +use loomweave_mcp::{McpToolPolicy, ServerState}; +use loomweave_storage::{ReaderPool, pragma, schema}; use rusqlite::Connection; use serde_json::{Value, json}; fn open_project() -> (tempfile::TempDir, PathBuf) { let project = tempfile::tempdir().expect("temp project"); - let clarion_dir = project.path().join(".clarion"); - std::fs::create_dir(&clarion_dir).expect("create .clarion"); - let db_path = clarion_dir.join("clarion.db"); + let loomweave_dir = project.path().join(".loomweave"); + std::fs::create_dir(&loomweave_dir).expect("create .loomweave"); + let db_path = loomweave_dir.join("loomweave.db"); let mut conn = Connection::open(&db_path).expect("open sqlite"); pragma::apply_write_pragmas(&conn).expect("write pragmas"); schema::apply_migrations(&mut conn).expect("apply migrations"); @@ -37,7 +37,7 @@ fn state_for(project_root: &Path, db_path: &Path, stub: &Path) -> ServerState { .with_analyze_command(stub.to_path_buf()) } -/// Write an executable stub that stands in for `clarion analyze`: it parses +/// Write an executable stub that stands in for `loomweave analyze`: it parses /// `--progress-file`, spawns a grandchild `sleep` (a stand-in for the plugin / /// pyright subtree) and records its pid, writes one progress snapshot, then /// blocks on the grandchild until the group is killed. diff --git a/crates/clarion-mcp/tests/catalogue_tools.rs b/crates/loomweave-mcp/tests/catalogue_tools.rs similarity index 97% rename from crates/clarion-mcp/tests/catalogue_tools.rs rename to crates/loomweave-mcp/tests/catalogue_tools.rs index f65ece77..cb0dc95a 100644 --- a/crates/clarion-mcp/tests/catalogue_tools.rs +++ b/crates/loomweave-mcp/tests/catalogue_tools.rs @@ -5,18 +5,18 @@ use std::sync::Arc; -use clarion_core::{EmbeddingRecording, RecordingEmbeddingProvider}; -use clarion_mcp::config::SemanticSearchConfig; -use clarion_mcp::{ServerState, list_tools}; -use clarion_storage::{EmbeddingKey, EmbeddingStore, ReaderPool, pragma, schema}; +use loomweave_core::{EmbeddingRecording, RecordingEmbeddingProvider}; +use loomweave_mcp::config::SemanticSearchConfig; +use loomweave_mcp::{ServerState, list_tools}; +use loomweave_storage::{EmbeddingKey, EmbeddingStore, ReaderPool, pragma, schema}; use rusqlite::{Connection, params}; use serde_json::{Value, json}; fn open_project() -> (tempfile::TempDir, std::path::PathBuf, Connection) { let project = tempfile::tempdir().expect("temp project"); - let clarion_dir = project.path().join(".clarion"); - std::fs::create_dir(&clarion_dir).expect("create .clarion"); - let db_path = clarion_dir.join("clarion.db"); + let loomweave_dir = project.path().join(".loomweave"); + std::fs::create_dir(&loomweave_dir).expect("create .loomweave"); + let db_path = loomweave_dir.join("loomweave.db"); let mut conn = Connection::open(&db_path).expect("open sqlite"); pragma::apply_write_pragmas(&conn).expect("write pragmas"); schema::apply_migrations(&mut conn).expect("apply migrations"); @@ -72,7 +72,7 @@ fn insert_finding( conn.execute( "INSERT INTO findings (id, tool, tool_version, run_id, rule_id, kind, severity, entity_id, \ related_entities, message, evidence, properties, supports, supported_by, status, created_at, updated_at) \ - VALUES (?1,'clarion','1.0','run-1','R1',?3,?4,?2,'[]','m','{}','{}','[]','[]',?5, \ + VALUES (?1,'loomweave','1.0','run-1','R1',?3,?4,?2,'[]','m','{}','{}','[]','[]',?5, \ '2026-01-01T00:00:00.000Z','2026-01-01T00:00:00.000Z')", params![id, entity_id, kind, severity, status], ) @@ -222,11 +222,11 @@ async fn entity_sei_is_null_without_binding_and_populated_with_one() { // Bind an alive SEI -> the read-time join populates it. let conn = Connection::open(&db).unwrap(); - insert_alive_sei(&conn, "clarion:eid:deadbeef", "python:function:m.f"); + insert_alive_sei(&conn, "loomweave:eid:deadbeef", "python:function:m.f"); drop(conn); let env = call_tool(&state, "wardline_for", json!({"id": "python:function:m.f"})).await; assert_eq!( - env["result"]["entity"]["sei"], "clarion:eid:deadbeef", + env["result"]["entity"]["sei"], "loomweave:eid:deadbeef", "{env}" ); } @@ -1177,17 +1177,17 @@ async fn guidance_sheet_carries_its_own_sei_not_the_queried_entitys() { r#"{"scope_level":"project","scope_rank":1,"content":"G","authored_at":"2026-01-01", "match_rules":[{"type":"kind","value":"function"}]}"#, ); - insert_alive_sei(&conn, "clarion:eid:entitysei", "python:function:m.f"); - insert_alive_sei(&conn, "clarion:eid:sheetsei", "core:guidance:g"); + insert_alive_sei(&conn, "loomweave:eid:entitysei", "python:function:m.f"); + insert_alive_sei(&conn, "loomweave:eid:sheetsei", "core:guidance:g"); drop(conn); let state = state_for(project.path(), &db); let env = call_tool(&state, "guidance_for", json!({"id": "python:function:m.f"})).await; assert_eq!( - env["result"]["entity"]["sei"], "clarion:eid:entitysei", + env["result"]["entity"]["sei"], "loomweave:eid:entitysei", "{env}" ); assert_eq!( - env["result"]["guidance"][0]["sei"], "clarion:eid:sheetsei", + env["result"]["guidance"][0]["sei"], "loomweave:eid:sheetsei", "{env}" ); } @@ -1330,7 +1330,7 @@ async fn find_dead_code_flags_unreachable_and_spares_live() { let candidate = &env["result"]["dead_code"][0]; assert_eq!( - candidate["rule_id"], "CLA-FACT-DEAD-CODE-CANDIDATE", + candidate["rule_id"], "LMWV-FACT-DEAD-CODE-CANDIDATE", "{env}" ); assert_eq!(candidate["kind"], "fact", "{env}"); @@ -1434,7 +1434,7 @@ async fn search_semantic_ranks_by_cosine_similarity() { drop(conn); let now = "2026-01-01T00:00:00.000Z"; - let store = EmbeddingStore::open_in_clarion_dir(project.path()).expect("open sidecar"); + let store = EmbeddingStore::open_in_loomweave_dir(project.path()).expect("open sidecar"); let mk = |id: &str, hash: &str| EmbeddingKey { entity_id: id.to_owned(), content_hash: hash.to_owned(), diff --git a/crates/clarion-mcp/tests/storage_tools.rs b/crates/loomweave-mcp/tests/storage_tools.rs similarity index 99% rename from crates/clarion-mcp/tests/storage_tools.rs rename to crates/loomweave-mcp/tests/storage_tools.rs index 1f8878ec..5eb3dda2 100644 --- a/crates/clarion-mcp/tests/storage_tools.rs +++ b/crates/loomweave-mcp/tests/storage_tools.rs @@ -5,13 +5,13 @@ use std::{ sync::{Arc, Mutex}, }; -use clarion_core::{ +use loomweave_core::{ CachingModel, INFERRED_CALLS_PROMPT_VERSION, InferredCallsPromptInput, LEAF_SUMMARY_PROMPT_TEMPLATE_ID, LeafSummaryPromptInput, LlmProvider, LlmProviderError, LlmPurpose, LlmRequest, LlmResponse, OpenRouterProvider, OpenRouterProviderConfig, Recording, RecordingProvider, build_inferred_calls_prompt, build_leaf_summary_prompt, }; -use clarion_mcp::{ +use loomweave_mcp::{ DiagnosticsContext, LlmDiagnostics, McpToolPolicy, ServerState, config::{FiligreeConfig, LlmConfig, LlmProviderKind}, filigree::{ @@ -22,7 +22,7 @@ use clarion_mcp::{ filigree_url::{SOURCE_CONFIG, SOURCE_EPHEMERAL_PORT, resolve_filigree_url}, list_tools, }; -use clarion_storage::{ +use loomweave_storage::{ GuidanceSheetInput, ReaderPool, SummaryCacheEntry, SummaryCacheKey, Writer, pragma, schema, upsert_guidance_sheet, upsert_summary_cache, }; @@ -31,9 +31,9 @@ use serde_json::{Value, json}; fn open_project() -> (tempfile::TempDir, std::path::PathBuf) { let project = tempfile::tempdir().expect("temp project"); - let clarion_dir = project.path().join(".clarion"); - std::fs::create_dir(&clarion_dir).expect("create .clarion"); - let db_path = clarion_dir.join("clarion.db"); + let loomweave_dir = project.path().join(".loomweave"); + std::fs::create_dir(&loomweave_dir).expect("create .loomweave"); + let db_path = loomweave_dir.join("loomweave.db"); let mut conn = Connection::open(&db_path).expect("open sqlite"); pragma::apply_write_pragmas(&conn).expect("write pragmas"); schema::apply_migrations(&mut conn).expect("apply migrations"); @@ -855,7 +855,7 @@ impl FiligreeLookup for FakeFiligreeClient { .lock() .unwrap_or_else(std::sync::PoisonError::into_inner); created.push(request.clone()); - let observation_id = format!("clarion-obs-{}", created.len()); + let observation_id = format!("loomweave-obs-{}", created.len()); self.observations .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) @@ -902,7 +902,7 @@ impl FiligreeLookup for FakeFiligreeClient { fn association(issue_id: &str, entity_id: &str, content_hash: &str) -> EntityAssociation { EntityAssociation { issue_id: issue_id.to_owned(), - clarion_entity_id: entity_id.to_owned(), + loomweave_entity_id: entity_id.to_owned(), content_hash_at_attach: content_hash.to_owned(), attached_at: "2026-05-17T00:00:00.000Z".to_owned(), attached_by: "codex".to_owned(), @@ -1301,14 +1301,14 @@ async fn issues_for_queries_sei_before_locator_and_aliases_match_to_current_enti let (project, db_path) = open_project(); seed_alive_sei_binding( &db_path, - "clarion:eid:demo-entry", + "loomweave:eid:demo-entry", "python:function:demo.entry", ); let client = Arc::new(FakeFiligreeClient::default().with_response( - "clarion:eid:demo-entry", + "loomweave:eid:demo-entry", vec![association( "filigree-sei", - "clarion:eid:demo-entry", + "loomweave:eid:demo-entry", &expected_content_hash(project.path(), "python:function:demo.entry"), )], )); @@ -1326,11 +1326,11 @@ async fn issues_for_queries_sei_before_locator_and_aliases_match_to_current_enti let matched = &envelope["result"]["matched"][0]; assert_eq!(matched["issue_id"], "filigree-sei"); assert_eq!(matched["entity_id"], "python:function:demo.entry"); - assert_eq!(matched["association_entity_id"], "clarion:eid:demo-entry"); - assert_eq!(matched["entity"]["sei"], "clarion:eid:demo-entry"); + assert_eq!(matched["association_entity_id"], "loomweave:eid:demo-entry"); + assert_eq!(matched["entity"]["sei"], "loomweave:eid:demo-entry"); assert_eq!( client.calls(), - vec!["clarion:eid:demo-entry".to_owned()], + vec!["loomweave:eid:demo-entry".to_owned()], "SEI should be the only lookup key when it is available" ); } @@ -1376,15 +1376,15 @@ async fn issues_for_flags_drift_for_sei_bound_association() { let (project, db_path) = open_project(); seed_alive_sei_binding( &db_path, - "clarion:eid:demo-entry", + "loomweave:eid:demo-entry", "python:function:demo.entry", ); let current_hash = expected_content_hash(project.path(), "python:function:demo.entry"); let client = Arc::new(FakeFiligreeClient::default().with_response( - "clarion:eid:demo-entry", + "loomweave:eid:demo-entry", vec![association( "filigree-drifted-sei", - "clarion:eid:demo-entry", + "loomweave:eid:demo-entry", "old-hash", )], )); @@ -1401,11 +1401,11 @@ async fn issues_for_flags_drift_for_sei_bound_association() { let drifted = &envelope["result"]["drifted"][0]; assert_eq!(drifted["issue_id"], "filigree-drifted-sei"); assert_eq!(drifted["entity_id"], "python:function:demo.entry"); - assert_eq!(drifted["association_entity_id"], "clarion:eid:demo-entry"); + assert_eq!(drifted["association_entity_id"], "loomweave:eid:demo-entry"); assert_eq!(drifted["content_hash_at_attach"], "old-hash"); assert_eq!(drifted["current_content_hash"], current_hash); assert_eq!(drifted["drift_status"], "drifted"); - assert_eq!(client.calls(), vec!["clarion:eid:demo-entry".to_owned()]); + assert_eq!(client.calls(), vec!["loomweave:eid:demo-entry".to_owned()]); } #[tokio::test] @@ -1914,7 +1914,7 @@ async fn propose_guidance_creates_observation_and_promote_makes_sheet_visible() .await; assert_eq!(proposed["ok"], true); - assert_eq!(proposed["result"]["observation_id"], "clarion-obs-1"); + assert_eq!(proposed["result"]["observation_id"], "loomweave-obs-1"); let created = client.created_observations(); assert_eq!(created.len(), 1); assert!(created[0].summary.contains("python:function:demo.entry")); @@ -1938,7 +1938,7 @@ async fn propose_guidance_creates_observation_and_promote_makes_sheet_visible() let promoted = call_tool( &state, "promote_guidance", - json!({"observation_id": "clarion-obs-1"}), + json!({"observation_id": "loomweave-obs-1"}), ) .await; assert_eq!(promoted["ok"], true); @@ -1948,7 +1948,7 @@ async fn propose_guidance_creates_observation_and_promote_makes_sheet_visible() ); assert_eq!( client.dismissed_observations(), - vec!["clarion-obs-1".to_owned()] + vec!["loomweave-obs-1".to_owned()] ); let visible = call_tool( @@ -2084,7 +2084,9 @@ async fn summary_openrouter_provider_runs_outside_async_runtime() { let mut request = [0_u8; 8192]; let read = stream.read(&mut request).expect("read OpenRouter request"); let request = String::from_utf8_lossy(&request[..read]); - assert!(request.contains(r#""response_format":{"json_schema":{"name":"clarion_summary""#)); + assert!( + request.contains(r#""response_format":{"json_schema":{"name":"loomweave_summary""#) + ); let body = r#"{ "id": "gen-01", "object": "chat.completion", @@ -2116,8 +2118,8 @@ async fn summary_openrouter_provider_runs_outside_async_runtime() { allow_live_provider: true, model_id: "anthropic/claude-sonnet-4.6".to_owned(), endpoint_url: format!("http://{addr}/api/v1"), - referer: "https://github.com/tachyon-beep/clarion".to_owned(), - title: "Clarion Test".to_owned(), + referer: "https://github.com/foundryside-dev/loomweave".to_owned(), + title: "Loomweave Test".to_owned(), timeout_seconds: 30, }) .expect("OpenRouter provider"), @@ -2500,7 +2502,7 @@ async fn summary_token_ceiling_blocks_session_after_expensive_cold_call() { assert_eq!(first["stats_delta"]["token_ceiling_exceeded_total"], 1); assert_eq!( first["diagnostics"][0]["code"], - "CLA-LLM-TOKEN-CEILING-EXCEEDED" + "LMWV-LLM-TOKEN-CEILING-EXCEEDED" ); assert_eq!(provider.invocations().len(), 1); @@ -4702,7 +4704,7 @@ fn insert_finding(conn: &Connection, id: &str, run_id: &str, entity_id: &str) { (id, tool, tool_version, run_id, rule_id, kind, severity, entity_id, \ related_entities, message, evidence, properties, supports, supported_by, \ status, created_at, updated_at) \ - VALUES (?1,'clarion','1.0',?2,'R1','defect','WARN',?3,'[]','m','{}','{}','[]','[]', \ + VALUES (?1,'loomweave','1.0',?2,'R1','defect','WARN',?3,'[]','m','{}','{}','[]','[]', \ 'open','2026-01-01T00:00:00.000Z','2026-01-01T00:00:00.000Z')", params![id, run_id, entity_id], ) @@ -4811,7 +4813,7 @@ async fn project_status_reports_counts_latest_run_and_plugins() { result["db_path"] .as_str() .unwrap() - .ends_with(".clarion/clarion.db") + .ends_with(".loomweave/loomweave.db") ); assert_eq!(result["git_sha"], "abc123status"); // A bare ServerState carries no diagnostics context. @@ -5196,7 +5198,7 @@ async fn issues_for_degrades_when_wardline_fetch_errors() { async fn issues_for_wardline_no_matches_when_no_qualname_reconciles() { // AC (no-fabrication axiom): when Filigree returns findings but NONE // reconcile to the requested entity (they target a different qualname), the - // section is `no_matches` with an empty items array — Clarion never invents + // section is `no_matches` with an empty items array — Loomweave never invents // a match. The findings have qualnames, so omitted_no_qualname stays 0. let (project, db_path) = open_project(); let conn = Connection::open(&db_path).expect("open sqlite"); @@ -5312,7 +5314,7 @@ async fn issues_for_section_item_carries_wardline_metadata_block() { // AC (FIX 1): each item in `wardline_findings.items` includes a `wardline` // key with the full wardline sub-object from `metadata.wardline` — // kind, confidence, suppression, qualname. The block is passed through - // verbatim; Clarion does not selectively strip or rename fields. + // verbatim; Loomweave does not selectively strip or rename fields. let (project, db_path) = open_project(); let conn = Connection::open(&db_path).expect("open sqlite"); conn.execute( diff --git a/crates/loomweave-plugin-fixture/Cargo.toml b/crates/loomweave-plugin-fixture/Cargo.toml new file mode 100644 index 00000000..6d73fbeb --- /dev/null +++ b/crates/loomweave-plugin-fixture/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "loomweave-plugin-fixture" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true + +[lints] +workspace = true + +# The binary artifact is deliberately named OFF the `loomweave-plugin-*` +# discovery glob (see loomweave-core/src/plugin/discovery.rs). A workspace +# build drops this artifact into `target//`; if it matched the glob, +# Phase A's current_exe()-relative discovery would find a manifest-less +# "plugin" next to the running binary and treat it as a fatal misconfiguration +# (breaking `loomweave analyze` from a dev build, and the PATH="" tests). +# Tests stage it under its real `loomweave-plugin-fixture` name via a +# symlink/copy, so `spawn`'s basename check still sees the glob name. +# Do NOT rename this back to `loomweave-plugin-fixture`. +[[bin]] +name = "loomweave-fixture-plugin" +path = "src/main.rs" + +[dependencies] +loomweave-core = { path = "../loomweave-core", version = "1.0.0" } +serde_json.workspace = true + +[target.'cfg(unix)'.dependencies] +nix = { workspace = true, features = ["mman", "signal"] } diff --git a/crates/clarion-plugin-fixture/src/lib.rs b/crates/loomweave-plugin-fixture/src/lib.rs similarity index 100% rename from crates/clarion-plugin-fixture/src/lib.rs rename to crates/loomweave-plugin-fixture/src/lib.rs diff --git a/crates/clarion-plugin-fixture/src/main.rs b/crates/loomweave-plugin-fixture/src/main.rs similarity index 93% rename from crates/clarion-plugin-fixture/src/main.rs rename to crates/loomweave-plugin-fixture/src/main.rs index b71b23f3..c7b26c7b 100644 --- a/crates/clarion-plugin-fixture/src/main.rs +++ b/crates/loomweave-plugin-fixture/src/main.rs @@ -1,20 +1,20 @@ //! Minimal test-fixture plugin binary. //! -//! Speaks the Clarion JSON-RPC 2.0 protocol over `stdin`/`stdout`. Used by +//! Speaks the Loomweave JSON-RPC 2.0 protocol over `stdin`/`stdout`. Used by //! the subprocess integration test (`host_subprocess.rs`) that exercises //! `PluginHost::spawn`. //! //! Fixture identity: -//! - `plugin_id = "fixture"`, kind `"widget"`, rule-ID prefix `"CLA-FIXTURE-"` +//! - `plugin_id = "fixture"`, kind `"widget"`, rule-ID prefix `"LMWV-FIXTURE-"` //! - Responds to every `analyze_file` request with one entity: //! `id = "fixture:widget:demo.sample"`, `kind = "widget"`, //! `qualified_name = "demo.sample"`, `source.file_path` = the path sent in. use std::io::{BufReader, Write}; -use clarion_core::plugin::limits::ContentLengthCeiling; -use clarion_core::plugin::transport::{Frame, read_frame, write_frame}; -use clarion_core::plugin::{ +use loomweave_core::plugin::limits::ContentLengthCeiling; +use loomweave_core::plugin::transport::{Frame, read_frame, write_frame}; +use loomweave_core::plugin::{ AnalyzeFileParams, AnalyzeFileResult, AnalyzeFileStats, InitializeResult, JsonRpcVersion, ResponseEnvelope, ResponsePayload, ShutdownResult, }; @@ -67,7 +67,7 @@ fn main() { match method.as_str() { "initialize" => { let result = InitializeResult { - name: "clarion-plugin-fixture".to_owned(), + name: "loomweave-plugin-fixture".to_owned(), version: "0.1.0".to_owned(), ontology_version: "0.1.0".to_owned(), capabilities: serde_json::json!({}), @@ -75,7 +75,7 @@ fn main() { send_result(&mut writer, id, serde_json::to_value(result).unwrap()); } "analyze_file" => { - if std::env::var_os("CLARION_FIXTURE_EXCEED_RLIMIT_AS").is_some() { + if std::env::var_os("LOOMWEAVE_FIXTURE_EXCEED_RLIMIT_AS").is_some() { #[cfg(unix)] exceed_rlimit_as(); #[cfg(not(unix))] diff --git a/crates/clarion-scanner/Cargo.toml b/crates/loomweave-scanner/Cargo.toml similarity index 92% rename from crates/clarion-scanner/Cargo.toml rename to crates/loomweave-scanner/Cargo.toml index d64555d6..7455afaa 100644 --- a/crates/clarion-scanner/Cargo.toml +++ b/crates/loomweave-scanner/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "clarion-scanner" +name = "loomweave-scanner" version.workspace = true edition.workspace = true license.workspace = true diff --git a/crates/clarion-scanner/src/baseline.rs b/crates/loomweave-scanner/src/baseline.rs similarity index 100% rename from crates/clarion-scanner/src/baseline.rs rename to crates/loomweave-scanner/src/baseline.rs diff --git a/crates/clarion-scanner/src/entropy.rs b/crates/loomweave-scanner/src/entropy.rs similarity index 100% rename from crates/clarion-scanner/src/entropy.rs rename to crates/loomweave-scanner/src/entropy.rs diff --git a/crates/clarion-scanner/src/lib.rs b/crates/loomweave-scanner/src/lib.rs similarity index 98% rename from crates/clarion-scanner/src/lib.rs rename to crates/loomweave-scanner/src/lib.rs index ae87edb8..f276c791 100644 --- a/crates/clarion-scanner/src/lib.rs +++ b/crates/loomweave-scanner/src/lib.rs @@ -98,7 +98,7 @@ pub struct HexDigestError { message: String, } -/// Closed detector type vocabulary supported by Clarion's v0.1 scanner. +/// Closed detector type vocabulary supported by Loomweave's v0.1 scanner. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum DetectSecretsRule { AwsAccessKey, diff --git a/crates/clarion-scanner/src/patterns.rs b/crates/loomweave-scanner/src/patterns.rs similarity index 100% rename from crates/clarion-scanner/src/patterns.rs rename to crates/loomweave-scanner/src/patterns.rs diff --git a/crates/clarion-scanner/tests/scanner.rs b/crates/loomweave-scanner/tests/scanner.rs similarity index 99% rename from crates/clarion-scanner/tests/scanner.rs rename to crates/loomweave-scanner/tests/scanner.rs index 936a8531..492ec626 100644 --- a/crates/clarion-scanner/tests/scanner.rs +++ b/crates/loomweave-scanner/tests/scanner.rs @@ -1,4 +1,4 @@ -use clarion_scanner::{ +use loomweave_scanner::{ Baseline, BaselineError, DetectSecretsRule, EntropyTuning, HashedSecret, Scanner, load_baseline, }; use sha1::{Digest, Sha1}; @@ -477,7 +477,7 @@ results: #[test] fn absent_baseline_file_is_empty() { let dir = tempfile::tempdir().expect("tempdir"); - let baseline = load_baseline(&dir.path().join(".clarion/secrets-baseline.yaml")) + let baseline = load_baseline(&dir.path().join(".loomweave/secrets-baseline.yaml")) .expect("missing baseline is accepted"); assert!(baseline.entries().is_empty()); } @@ -549,7 +549,7 @@ results: result.allowed.len(), 1, "baseline must NOT suppress a drifted hash at the same line — that is \ - the security regression CLA-SEC-SECRET-DETECTED exists to catch", + the security regression LMWV-SEC-SECRET-DETECTED exists to catch", ); assert!(result.suppressed.is_empty()); assert!(result.fired_entries.is_empty()); diff --git a/crates/clarion-storage/Cargo.toml b/crates/loomweave-storage/Cargo.toml similarity index 84% rename from crates/clarion-storage/Cargo.toml rename to crates/loomweave-storage/Cargo.toml index 5a05bd81..a358b6ef 100644 --- a/crates/clarion-storage/Cargo.toml +++ b/crates/loomweave-storage/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "clarion-storage" +name = "loomweave-storage" version.workspace = true edition.workspace = true license.workspace = true @@ -11,7 +11,7 @@ workspace = true [dependencies] blake3.workspace = true -clarion-core = { path = "../clarion-core", version = "1.3.0" } +loomweave-core = { path = "../loomweave-core", version = "1.0.0" } deadpool-sqlite.workspace = true rusqlite.workspace = true serde.workspace = true diff --git a/crates/clarion-storage/migrations/0001_initial_schema.sql b/crates/loomweave-storage/migrations/0001_initial_schema.sql similarity index 97% rename from crates/clarion-storage/migrations/0001_initial_schema.sql rename to crates/loomweave-storage/migrations/0001_initial_schema.sql index 49cdeb2c..2ccd1a92 100644 --- a/crates/clarion-storage/migrations/0001_initial_schema.sql +++ b/crates/loomweave-storage/migrations/0001_initial_schema.sql @@ -1,15 +1,15 @@ -- ============================================================================ --- Clarion migration 0001 — initial schema. +-- Loomweave migration 0001 — initial schema. -- --- Source: docs/clarion/1.0/detailed-design.md §3 (Storage Implementation). +-- Source: docs/loomweave/1.0/detailed-design.md §3 (Storage Implementation). -- Sprint 1 walking skeleton writes only to `entities` and `runs`, but every -- table, FTS5 virtual table, trigger, generated column, and view is created -- here so the full shape is frozen at L1-lock time. See ADR-011 for the -- writer-actor + per-N-files transaction model this schema supports. -- -- Edit-in-place policy (per ADR-024): this migration is editable in place --- as long as no external operator has produced a `.clarion/clarion.db` from --- a published Clarion build. The retirement trigger names exactly that +-- as long as no external operator has produced a `.loomweave/loomweave.db` from +-- a published Loomweave build. The retirement trigger names exactly that -- condition; once it fires, all schema changes stack as 0002_*.sql etc. -- The 2026-05-03 edits (guidance vocabulary rename per ADR-024), the -- 2026-05-18 edits (CHECK constraints on closed-vocabulary TEXT columns per @@ -111,7 +111,7 @@ CREATE TABLE findings ( -- detailed-design.md §3. kind TEXT NOT NULL CHECK (kind IN ('defect', 'fact', 'classification', 'metric', 'suggestion')), - -- ADR-031: closed core-owned vocabulary; values per ADR-017 (Clarion + -- ADR-031: closed core-owned vocabulary; values per ADR-017 (Loomweave -- internal severity, pre-mapping to Filigree wire). severity TEXT NOT NULL CHECK (severity IN ('INFO', 'WARN', 'ERROR', 'CRITICAL', 'NONE')), diff --git a/crates/clarion-storage/migrations/0002_briefing_blocked.sql b/crates/loomweave-storage/migrations/0002_briefing_blocked.sql similarity index 100% rename from crates/clarion-storage/migrations/0002_briefing_blocked.sql rename to crates/loomweave-storage/migrations/0002_briefing_blocked.sql diff --git a/crates/clarion-storage/migrations/0003_wardline_taint_facts.sql b/crates/loomweave-storage/migrations/0003_wardline_taint_facts.sql similarity index 92% rename from crates/clarion-storage/migrations/0003_wardline_taint_facts.sql rename to crates/loomweave-storage/migrations/0003_wardline_taint_facts.sql index c10ab535..15b8e14d 100644 --- a/crates/clarion-storage/migrations/0003_wardline_taint_facts.sql +++ b/crates/loomweave-storage/migrations/0003_wardline_taint_facts.sql @@ -1,7 +1,7 @@ -- Migration 0003: Wardline taint-fact store (SP9, ADR-036). -- Dedicated, Wardline-owned per-entity table. NOT the schema-reserved -- `entities.wardline` column (which `analyze` clobbers with NULL on every --- re-index). `wardline_json` is opaque to Clarion — stored and returned +-- re-index). `wardline_json` is opaque to Loomweave — stored and returned -- verbatim. `scan_id` and `content_hash_at_compute` are queryable columns -- supplied by the caller, not parsed out of the blob. diff --git a/crates/clarion-storage/migrations/0004_sei_prior_index.sql b/crates/loomweave-storage/migrations/0004_sei_prior_index.sql similarity index 100% rename from crates/clarion-storage/migrations/0004_sei_prior_index.sql rename to crates/loomweave-storage/migrations/0004_sei_prior_index.sql diff --git a/crates/clarion-storage/migrations/0005_sei.sql b/crates/loomweave-storage/migrations/0005_sei.sql similarity index 95% rename from crates/clarion-storage/migrations/0005_sei.sql rename to crates/loomweave-storage/migrations/0005_sei.sql index 99de3059..4d4a68d6 100644 --- a/crates/clarion-storage/migrations/0005_sei.sql +++ b/crates/loomweave-storage/migrations/0005_sei.sql @@ -1,7 +1,7 @@ -- Migration 0005 — SEI identity store + lineage event log (Wave 1 / WS1). -- -- Implements ADR-038 (token scheme, signature schema, identity persistence) and --- the Loom SEI conformance standard §3 (matcher state) / §4 (resolution surface). +-- the Weft SEI conformance standard §3 (matcher state) / §4 (resolution surface). -- -- sei_bindings: the durable identity store, keyed by the opaque SEI. It is -- DECOUPLED from the cumulative `entities` table (which is @@ -27,7 +27,7 @@ BEGIN; ALTER TABLE entities ADD COLUMN signature TEXT; CREATE TABLE sei_bindings ( - sei TEXT PRIMARY KEY, -- clarion:eid: (opaque; consumers MUST NOT parse) + sei TEXT PRIMARY KEY, -- loomweave:eid: (opaque; consumers MUST NOT parse) current_locator TEXT, -- current address: the alive binding's entity id body_hash TEXT, -- entities.content_hash at last (re)bind signature TEXT, -- entities.signature at last (re)bind diff --git a/crates/clarion-storage/migrations/0006_wardline_taint_sei.sql b/crates/loomweave-storage/migrations/0006_wardline_taint_sei.sql similarity index 96% rename from crates/clarion-storage/migrations/0006_wardline_taint_sei.sql rename to crates/loomweave-storage/migrations/0006_wardline_taint_sei.sql index dc092218..79501738 100644 --- a/crates/clarion-storage/migrations/0006_wardline_taint_sei.sql +++ b/crates/loomweave-storage/migrations/0006_wardline_taint_sei.sql @@ -11,7 +11,7 @@ -- pre-SEI database). No backfill of existing rows; facts stay physically -- anchored to their `entity_id` (the never-pruned `entities` table preserves -- it). The SEI is opaque to the store — written and matched verbatim, never --- parsed (ADR-038 / Loom SEI standard §4). +-- parsed (ADR-038 / Weft SEI standard §4). -- -- Partial index (WHERE sei IS NOT NULL): the read-by-SEI lookup filters on -- non-null SEIs only, and a partial index keeps the pre-SEI NULL rows out of diff --git a/crates/clarion-storage/migrations/0007_run_analyzed_commit.sql b/crates/loomweave-storage/migrations/0007_run_analyzed_commit.sql similarity index 100% rename from crates/clarion-storage/migrations/0007_run_analyzed_commit.sql rename to crates/loomweave-storage/migrations/0007_run_analyzed_commit.sql diff --git a/crates/clarion-storage/migrations/0008_run_owner_heartbeat.sql b/crates/loomweave-storage/migrations/0008_run_owner_heartbeat.sql similarity index 100% rename from crates/clarion-storage/migrations/0008_run_owner_heartbeat.sql rename to crates/loomweave-storage/migrations/0008_run_owner_heartbeat.sql diff --git a/crates/clarion-storage/src/cache.rs b/crates/loomweave-storage/src/cache.rs similarity index 100% rename from crates/clarion-storage/src/cache.rs rename to crates/loomweave-storage/src/cache.rs diff --git a/crates/clarion-storage/src/commands.rs b/crates/loomweave-storage/src/commands.rs similarity index 99% rename from crates/clarion-storage/src/commands.rs rename to crates/loomweave-storage/src/commands.rs index f9ea6fb5..4080a84a 100644 --- a/crates/clarion-storage/src/commands.rs +++ b/crates/loomweave-storage/src/commands.rs @@ -11,7 +11,7 @@ use tokio::sync::oneshot; -pub use clarion_core::EdgeConfidence; +pub use loomweave_core::EdgeConfidence; use crate::cache::{InferredEdgeCacheEntry, SummaryCacheEntry, SummaryCacheKey}; use crate::error::StorageError; @@ -23,7 +23,7 @@ use crate::wardline_taint::TaintFact; pub type Ack = oneshot::Sender>; /// Run status values. Extended in later WPs; Sprint 1 uses only -/// `SkippedNoPlugins` (from `clarion analyze` without plugins wired) and +/// `SkippedNoPlugins` (from `loomweave analyze` without plugins wired) and /// `Failed` (explicit `FailRun`). #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum RunStatus { diff --git a/crates/clarion-storage/src/embeddings.rs b/crates/loomweave-storage/src/embeddings.rs similarity index 94% rename from crates/clarion-storage/src/embeddings.rs rename to crates/loomweave-storage/src/embeddings.rs index 50c95a2b..f266bdbb 100644 --- a/crates/clarion-storage/src/embeddings.rs +++ b/crates/loomweave-storage/src/embeddings.rs @@ -1,8 +1,8 @@ -//! Git-ignored embeddings sidecar (`.clarion/embeddings.db`) for `WS5b` semantic +//! Git-ignored embeddings sidecar (`.loomweave/embeddings.db`) for `WS5b` semantic //! search (ADR-040). //! //! Embeddings are large and rebuildable, so they must **not** bloat the -//! committed `.clarion/clarion.db` (ADR-005). They live in a separate `SQLite` +//! committed `.loomweave/loomweave.db` (ADR-005). They live in a separate `SQLite` //! file, keyed by `(entity_id, content_hash, model_id)` so they invalidate on //! content change exactly like the summary cache. Because the file is a private, //! rebuildable cache (git-ignored), it carries its own self-contained schema @@ -51,10 +51,10 @@ pub struct EmbeddingStore { conn: Connection, } -/// The conventional sidecar path for a project: `/.clarion/embeddings.db`. +/// The conventional sidecar path for a project: `/.loomweave/embeddings.db`. #[must_use] pub fn embeddings_db_path(project_root: &Path) -> PathBuf { - project_root.join(".clarion").join("embeddings.db") + project_root.join(".loomweave").join("embeddings.db") } impl EmbeddingStore { @@ -67,8 +67,8 @@ impl EmbeddingStore { Ok(Self { conn }) } - /// Open the conventional `/.clarion/embeddings.db` sidecar. - pub fn open_in_clarion_dir(project_root: &Path) -> Result { + /// Open the conventional `/.loomweave/embeddings.db` sidecar. + pub fn open_in_loomweave_dir(project_root: &Path) -> Result { Self::open(&embeddings_db_path(project_root)) } diff --git a/crates/clarion-storage/src/error.rs b/crates/loomweave-storage/src/error.rs similarity index 91% rename from crates/clarion-storage/src/error.rs rename to crates/loomweave-storage/src/error.rs index 4ae75715..5e7b534e 100644 --- a/crates/clarion-storage/src/error.rs +++ b/crates/loomweave-storage/src/error.rs @@ -20,16 +20,16 @@ pub enum StorageError { PragmaInvariant(String), #[error( - "CLA-INFRA-STORAGE-FOREIGN-DB: refusing to open SQLite file with \ - application_id={application_id:#010x}; Clarion databases carry \ - application_id=0x434C524E (\"CLRN\")" + "LMWV-INFRA-STORAGE-FOREIGN-DB: refusing to open SQLite file with \ + application_id={application_id:#010x}; Loomweave databases carry \ + application_id=0x4C4D5756 (\"LMWV\")" )] ForeignDatabase { application_id: u32 }, #[error( - "CLA-INFRA-STORAGE-FUTURE-DB: refusing to open SQLite file with \ + "LMWV-INFRA-STORAGE-FUTURE-DB: refusing to open SQLite file with \ user_version={found} (greater than current schema version {current}); \ - the database was written by a newer Clarion build" + the database was written by a newer Loomweave build" )] FutureUserVersion { found: u32, current: u32 }, @@ -48,7 +48,7 @@ pub enum StorageError { /// A row that exists in storage failed an integrity check on read (e.g. a /// blob that the write path stores byte-verbatim no longer re-parses). - /// This is a server-side fault — Clarion's stored state is damaged, NOT a + /// This is a server-side fault — Loomweave's stored state is damaged, NOT a /// malformed client request — so it must map to 5xx and be logged, never to /// a client 4xx. Reachable only via storage corruption or an out-of-band /// write that bypasses the validated write path. diff --git a/crates/clarion-storage/src/glob.rs b/crates/loomweave-storage/src/glob.rs similarity index 97% rename from crates/clarion-storage/src/glob.rs rename to crates/loomweave-storage/src/glob.rs index e5927762..b116a747 100644 --- a/crates/clarion-storage/src/glob.rs +++ b/crates/loomweave-storage/src/glob.rs @@ -3,7 +3,7 @@ //! //! Lifted into the storage crate so a single implementation backs both the MCP //! catalogue (which historically owned it as `catalogue::glob_match`) and the -//! CLI guidance authoring path — one matcher, no drift. `clarion-mcp` +//! CLI guidance authoring path — one matcher, no drift. `loomweave-mcp` //! re-exports this function so its semantics are unchanged. /// Glob-match `path` against a `**`/`*`/`?` `pattern`, treating `/` as the diff --git a/crates/clarion-storage/src/guidance.rs b/crates/loomweave-storage/src/guidance.rs similarity index 97% rename from crates/clarion-storage/src/guidance.rs rename to crates/loomweave-storage/src/guidance.rs index 3e352614..f7ad2afe 100644 --- a/crates/clarion-storage/src/guidance.rs +++ b/crates/loomweave-storage/src/guidance.rs @@ -2,14 +2,14 @@ //! //! Guidance sheets are entities with `kind = 'guidance'` and id //! `core:guidance:`. They are operator-authored, have **no source file**, -//! and exist **outside any `clarion analyze` run** — so they must NOT go through +//! and exist **outside any `loomweave analyze` run** — so they must NOT go through //! the run-scoped writer actor (`WriterCmd::InsertEntity`), which hard-requires //! a `BeginRun` and a source-file anchor. Instead they insert via a plain, //! non-run-scoped `INSERT INTO entities`, exactly the shape proven by the //! storage schema test `entity_generated_columns_extract_from_properties_json`. //! //! The `properties` JSON this module writes is the contract the read path -//! (`clarion-mcp` `catalogue::inspection::tool_guidance_for` / `rule_match`) +//! (`loomweave-mcp` `catalogue::inspection::tool_guidance_for` / `rule_match`) //! consumes. In particular `match_rules` entries are `{"type": …, …}` objects: //! - `{"type":"path","pattern":""}` //! - `{"type":"tag","value":""}` @@ -114,7 +114,7 @@ impl GuidanceSheet { /// /// This is the **review-cadence/expiry** predicate that mirrors the MCP /// `guidance_for` read path's expiry exclusion and the -/// `CLA-FACT-GUIDANCE-EXPIRED` finding: parse both values to Unix seconds, +/// `LMWV-FACT-GUIDANCE-EXPIRED` finding: parse both values to Unix seconds, /// accepting either `unix:` or RFC3339 timestamps, and compare /// numerically. Fail open: a sheet with no `expires`, an unparseable `expires`, /// or an unparseable clock is never hidden as expired. @@ -150,7 +150,7 @@ fn parse_guidance_timestamp_to_unix_seconds(value: &str) -> Option { /// timestamp has no measurable age and is treated as **not stale**. /// /// NOTE: this is age-based staleness, distinct from the churn-based signal the -/// `CLA-FACT-GUIDANCE-CHURN-STALE` finding surfaces (which aggregates git churn +/// `LMWV-FACT-GUIDANCE-CHURN-STALE` finding surfaces (which aggregates git churn /// over matched entities). Do not conflate the two. #[must_use] pub fn guidance_sheet_is_stale(sheet: &GuidanceSheet, stale_before: &str) -> bool { @@ -167,12 +167,12 @@ const SELECT_COLUMNS: &str = "id, name, short_name, scope_level, properties, \ /// canonical name) follows. const GUIDANCE_ID_PREFIX: &str = "core:guidance:"; -/// Marker wrapping a Clarion guidance proposal inside a Filigree observation's +/// Marker wrapping a Loomweave guidance proposal inside a Filigree observation's /// free-form detail field. The marker lets promotion parse only observations /// that deliberately carry the guidance payload, rather than treating arbitrary /// scratchpad prose as trusted sheet data. -pub const GUIDANCE_PROPOSAL_MARKER: &str = "BEGIN_CLARION_GUIDANCE_PROPOSAL_V1"; -const GUIDANCE_PROPOSAL_END_MARKER: &str = "END_CLARION_GUIDANCE_PROPOSAL_V1"; +pub const GUIDANCE_PROPOSAL_MARKER: &str = "BEGIN_LOOMWEAVE_GUIDANCE_PROPOSAL_V1"; +const GUIDANCE_PROPOSAL_END_MARKER: &str = "END_LOOMWEAVE_GUIDANCE_PROPOSAL_V1"; const PROVENANCE_FILIGREE_PROMOTION: &str = "filigree_promotion"; const GUIDANCE_SCOPE_LEVELS: &[&str] = &[ @@ -233,7 +233,7 @@ impl GuidanceProposal { let json = serde_json::to_string_pretty(self) .map_err(|e| StorageError::InvalidQuery(format!("serialize guidance proposal: {e}")))?; Ok(format!( - "Clarion guidance proposal. Promote with `clarion guidance promote` after review.\n\n\ + "Loomweave guidance proposal. Promote with `loomweave guidance promote` after review.\n\n\ {GUIDANCE_PROPOSAL_MARKER}\n{json}\n{GUIDANCE_PROPOSAL_END_MARKER}\n" )) } @@ -247,18 +247,18 @@ impl GuidanceProposal { pub fn from_observation_detail(detail: &str) -> Result { let start = detail.find(GUIDANCE_PROPOSAL_MARKER).ok_or_else(|| { StorageError::InvalidQuery( - "observation does not contain a Clarion guidance proposal".to_owned(), + "observation does not contain a Loomweave guidance proposal".to_owned(), ) })? + GUIDANCE_PROPOSAL_MARKER.len(); let rest = &detail[start..]; let end = rest.find(GUIDANCE_PROPOSAL_END_MARKER).ok_or_else(|| { StorageError::InvalidQuery( - "Clarion guidance proposal is missing its end marker".to_owned(), + "Loomweave guidance proposal is missing its end marker".to_owned(), ) })?; let raw_json = rest[..end].trim(); let proposal: Self = serde_json::from_str(raw_json).map_err(|e| { - StorageError::InvalidQuery(format!("parse Clarion guidance proposal: {e}")) + StorageError::InvalidQuery(format!("parse Loomweave guidance proposal: {e}")) })?; proposal.validate()?; Ok(proposal) @@ -665,7 +665,7 @@ impl PortableSheet { /// `guides`-edge composition (which `guidance_for` *also* honours), so a `true` /// here means "a match rule fired", not "`guidance_for` would compose this sheet". /// `wardline_group` rules are not evaluable here and never match (the Wardline -/// blob is opaque to Clarion). +/// blob is opaque to Loomweave). /// /// `project_root` is needed to compute the entity's project-relative path for /// `path` rules (the stored `source_file_path` is absolute). @@ -706,7 +706,7 @@ pub fn guidance_sheet_matches_entity( /// every affected entity must be dropped or the new guidance never reaches a /// future prompt (it would otherwise stay inert until the entity's *code* /// changed and its `content_hash` cache key rotated). The CLI authoring path -/// (`clarion guidance create|edit|delete`) calls this on every mutation. +/// (`loomweave guidance create|edit|delete`) calls this on every mutation. /// /// Scan strategy: drive off `SELECT DISTINCT entity_id FROM summary_cache` (the /// only entities that *can* be invalidated), not the whole entity table — this diff --git a/crates/clarion-storage/src/lib.rs b/crates/loomweave-storage/src/lib.rs similarity index 98% rename from crates/clarion-storage/src/lib.rs rename to crates/loomweave-storage/src/lib.rs index ba13d495..c3ff35cc 100644 --- a/crates/clarion-storage/src/lib.rs +++ b/crates/loomweave-storage/src/lib.rs @@ -1,4 +1,4 @@ -//! clarion-storage — `SQLite` layer, writer-actor, reader pool. +//! loomweave-storage — `SQLite` layer, writer-actor, reader pool. //! //! All mutations route through the writer actor (a single `tokio::task` //! owning the sole write `rusqlite::Connection`). Readers come from a diff --git a/crates/clarion-storage/src/pragma.rs b/crates/loomweave-storage/src/pragma.rs similarity index 82% rename from crates/clarion-storage/src/pragma.rs rename to crates/loomweave-storage/src/pragma.rs index 55d8bab9..2e3816f0 100644 --- a/crates/clarion-storage/src/pragma.rs +++ b/crates/loomweave-storage/src/pragma.rs @@ -5,9 +5,9 @@ //! - **Stated basis**: ADR-011 §`SQLite` PRAGMAs fixes the durability + //! concurrency posture (`journal_mode=WAL` + `synchronous=NORMAL` + //! `busy_timeout=5000` + `wal_autocheckpoint=1000` + `foreign_keys=ON`). -//! The `application_id=0x434C524E` ("CLRN") and `user_version` PRAGMAs +//! The `application_id=0x4C4D5756` ("LMWV") and `user_version` PRAGMAs //! close gap STO-02 from `docs/implementation/v1.0-tag-cut/gap-register.md`: -//! they give `.clarion/clarion.db` a self-identifying on-disk header so +//! they give `.loomweave/loomweave.db` a self-identifying on-disk header so //! `file(1)` / `sqlite3 .dbinfo` / a future migration runner can refuse //! foreign or forward-incompatible files. //! - **Override surface**: **recompile-only.** None of these PRAGMAs are @@ -23,13 +23,13 @@ use rusqlite::Connection; use crate::error::{Result, StorageError}; -/// `SQLite` `application_id` header value identifying Clarion databases. +/// `SQLite` `application_id` header value identifying Loomweave databases. /// -/// ASCII "CLRN" — picked so `file(1)` / `sqlite3 .dbinfo` distinguishes a -/// Clarion DB from any other `SQLite` file. Set lazily on first open of a +/// ASCII "LMWV" — picked so `file(1)` / `sqlite3 .dbinfo` distinguishes a +/// Loomweave DB from any other `SQLite` file. Set lazily on first open of a /// fresh (`application_id = 0`) file. Refusing to open a file with any /// other non-zero value is how we close STO-02. -pub const CLARION_APPLICATION_ID: u32 = 0x434C_524E; +pub const LOOMWEAVE_APPLICATION_ID: u32 = 0x4C4D_5756; /// Apply the write-side PRAGMA set: WAL, `synchronous=NORMAL`, `busy_timeout`, /// `wal_autocheckpoint`, `foreign_keys`. Called on the writer's connection once, @@ -37,7 +37,7 @@ pub const CLARION_APPLICATION_ID: u32 = 0x434C_524E; /// /// Also enforces the `application_id` identity check (STO-02): a file whose /// `application_id` is neither `0` (fresh / legacy) nor -/// [`CLARION_APPLICATION_ID`] is rejected with +/// [`LOOMWEAVE_APPLICATION_ID`] is rejected with /// [`StorageError::ForeignDatabase`]. A zero value is upgraded in place /// (`PRAGMA application_id = ...`) so re-opens recognise the file. /// @@ -47,7 +47,7 @@ pub const CLARION_APPLICATION_ID: u32 = 0x434C_524E; /// Returns [`StorageError::PragmaInvariant`] if WAL mode is not /// confirmed after the `PRAGMA journal_mode = WAL` command. /// Returns [`StorageError::ForeignDatabase`] if the file carries a -/// non-zero `application_id` that is not Clarion's. +/// non-zero `application_id` that is not Loomweave's. pub fn apply_write_pragmas(conn: &Connection) -> Result<()> { let mode: String = conn.query_row("PRAGMA journal_mode = WAL", [], |row| row.get(0))?; if !mode.eq_ignore_ascii_case("wal") { @@ -81,7 +81,7 @@ pub fn apply_read_pragmas(conn: &Connection) -> Result<()> { } /// Validate the database identity for read-only surfaces without mutating the -/// header. Accepts legacy zero-id files, accepts Clarion's id, and rejects any +/// header. Accepts legacy zero-id files, accepts Loomweave's id, and rejects any /// other non-zero `application_id`. pub fn validate_application_id_for_read(conn: &Connection) -> Result<()> { let raw: i64 = conn.query_row("PRAGMA application_id", [], |row| row.get(0))?; @@ -92,21 +92,21 @@ pub fn validate_application_id_for_read(conn: &Connection) -> Result<()> { })?; match id { 0 => Ok(()), - id if id == CLARION_APPLICATION_ID => Ok(()), + id if id == LOOMWEAVE_APPLICATION_ID => Ok(()), other => Err(StorageError::ForeignDatabase { application_id: other, }), } } -/// Read `application_id`; on `0` set it to [`CLARION_APPLICATION_ID`]; on -/// [`CLARION_APPLICATION_ID`] continue; on any other value refuse with +/// Read `application_id`; on `0` set it to [`LOOMWEAVE_APPLICATION_ID`]; on +/// [`LOOMWEAVE_APPLICATION_ID`] continue; on any other value refuse with /// [`StorageError::ForeignDatabase`]. /// /// `SQLite` stores `application_id` as a signed 32-bit integer; rusqlite /// surfaces it as `i64`. We read into `i64` and reinterpret via /// `as u32` so values with the high bit set (negative `i32`) still compare -/// against [`CLARION_APPLICATION_ID`] correctly. +/// against [`LOOMWEAVE_APPLICATION_ID`] correctly. fn enforce_application_id(conn: &Connection) -> Result<()> { let raw: i64 = conn.query_row("PRAGMA application_id", [], |row| row.get(0))?; // i64 -> u32: SQLite caps application_id at 32 bits. Truncating cast is @@ -116,11 +116,11 @@ fn enforce_application_id(conn: &Connection) -> Result<()> { match current { 0 => { conn.execute_batch(&format!( - "PRAGMA application_id = {CLARION_APPLICATION_ID};" + "PRAGMA application_id = {LOOMWEAVE_APPLICATION_ID};" ))?; Ok(()) } - id if id == CLARION_APPLICATION_ID => Ok(()), + id if id == LOOMWEAVE_APPLICATION_ID => Ok(()), other => Err(StorageError::ForeignDatabase { application_id: other, }), diff --git a/crates/clarion-storage/src/prior_index.rs b/crates/loomweave-storage/src/prior_index.rs similarity index 99% rename from crates/clarion-storage/src/prior_index.rs rename to crates/loomweave-storage/src/prior_index.rs index 094688f8..9b2b1bbb 100644 --- a/crates/clarion-storage/src/prior_index.rs +++ b/crates/loomweave-storage/src/prior_index.rs @@ -154,7 +154,7 @@ pub fn prior_locators_by_file(conn: &Connection) -> Result, @@ -339,7 +339,7 @@ pub fn resolve_file_catalog_entry( } /// Extract the `briefing_blocked` reason from an entity's `properties` JSON -/// column. Shared with `clarion-mcp` (which makes the same call inline) so +/// column. Shared with `loomweave-mcp` (which makes the same call inline) so /// federation read surfaces enforce the block uniformly. pub fn entity_briefing_block_reason(properties_json: &str) -> Option { // Fail-closed: malformed properties JSON treated as briefing-blocked to prevent secret exposure. @@ -1435,11 +1435,11 @@ pub fn contained_entity_ids( /// (`GET /api/v1/files`) already refuses briefing-blocked entities and omits /// their identity fields. Without this guard the write direction would leak the /// very path/line the read direction is engineered to withhold — e.g. a -/// secret-scanner `CLA-SEC-SECRET-DETECTED` finding on a still-blocked +/// secret-scanner `LMWV-SEC-SECRET-DETECTED` finding on a still-blocked /// secret-bearing file. The filter is safe for the ADR-013 audit trail: an /// operator override (`--allow-unredacted-secrets`) records the file as /// `Overridden`, not `Blocked`, so its anchor entity carries no -/// `briefing_blocked` reason and the `CLA-SEC-UNREDACTED-SECRETS-ALLOWED` audit +/// `briefing_blocked` reason and the `LMWV-SEC-UNREDACTED-SECRETS-ALLOWED` audit /// finding still emits. pub fn findings_for_emit(conn: &Connection, run_id: &str) -> Result> { let mut stmt = conn.prepare( diff --git a/crates/clarion-storage/src/reader.rs b/crates/loomweave-storage/src/reader.rs similarity index 97% rename from crates/clarion-storage/src/reader.rs rename to crates/loomweave-storage/src/reader.rs index 77daab89..47c3f034 100644 --- a/crates/clarion-storage/src/reader.rs +++ b/crates/loomweave-storage/src/reader.rs @@ -56,7 +56,7 @@ impl ReaderPool { /// Open a pool and eagerly validate the backing database at boot. /// /// Like [`Self::open`], but first proves the file exists and is a readable - /// `SQLite` database, so `clarion serve` fails fast on a missing, corrupt, + /// `SQLite` database, so `loomweave serve` fails fast on a missing, corrupt, /// or unreadable DB instead of deferring the error to the first /// [`Self::with_reader`] call. This matters because `deadpool-sqlite` opens /// pool connections lazily *and with `CREATE`* — without this probe, a @@ -65,7 +65,7 @@ impl ReaderPool { /// /// The probe is a throwaway read-only connection running /// `PRAGMA schema_version`, the same cheap corruption check - /// `clarion hook session-start` uses. It also validates the Clarion + /// `loomweave hook session-start` uses. It also validates the Loomweave /// `application_id` and refuses a future `user_version` without mutating /// legacy zero-id files. It runs *before* the pool is built so a bad DB /// never produces a half-live pool. diff --git a/crates/clarion-storage/src/retry.rs b/crates/loomweave-storage/src/retry.rs similarity index 98% rename from crates/clarion-storage/src/retry.rs rename to crates/loomweave-storage/src/retry.rs index 758cbd36..26260365 100644 --- a/crates/clarion-storage/src/retry.rs +++ b/crates/loomweave-storage/src/retry.rs @@ -18,7 +18,7 @@ //! can still see `SQLITE_BUSY` after the timeout elapses; an application //! retry with backoff gives the holder more chances to drain. //! -//! Single-writer, single-process Clarion does not contend today; this helper +//! Single-writer, single-process Loomweave does not contend today; this helper //! exists so the writer is correct the moment cross-process writers land //! (STO-01 / V11-STO-01). diff --git a/crates/clarion-storage/src/runs.rs b/crates/loomweave-storage/src/runs.rs similarity index 100% rename from crates/clarion-storage/src/runs.rs rename to crates/loomweave-storage/src/runs.rs diff --git a/crates/clarion-storage/src/schema.rs b/crates/loomweave-storage/src/schema.rs similarity index 99% rename from crates/clarion-storage/src/schema.rs rename to crates/loomweave-storage/src/schema.rs index 1aaca227..d11d8e32 100644 --- a/crates/clarion-storage/src/schema.rs +++ b/crates/loomweave-storage/src/schema.rs @@ -85,7 +85,7 @@ const _CURRENT_SCHEMA_VERSION_MATCHES_LAST_MIGRATION: () = { /// # Errors /// /// Returns [`StorageError::FutureUserVersion`] if the database was written -/// by a newer Clarion build. +/// by a newer Loomweave build. /// Returns [`StorageError::Migration`] with the failing version on SQL error /// during apply. Returns [`StorageError::Sqlite`] on bookkeeping failures. pub fn apply_migrations(conn: &mut Connection) -> Result<()> { diff --git a/crates/clarion-storage/src/sei.rs b/crates/loomweave-storage/src/sei.rs similarity index 93% rename from crates/clarion-storage/src/sei.rs rename to crates/loomweave-storage/src/sei.rs index 5a9ef82a..ebbd5a0e 100644 --- a/crates/clarion-storage/src/sei.rs +++ b/crates/loomweave-storage/src/sei.rs @@ -1,7 +1,7 @@ //! Stable Entity Identity (SEI) — minting, the deterministic fail-closed //! matcher, binding/lineage state, and resolution (Wave 1 / WS1). //! -//! Implements ADR-038 and the Loom SEI conformance standard §3 (matcher), +//! Implements ADR-038 and the Weft SEI conformance standard §3 (matcher), //! §4 (resolution surface). Identity is a durable opaque surrogate keyed in //! `sei_bindings` (migration 0005), decoupled from the cumulative `entities` //! table. The qualname id (`{plugin}:{kind}:{qualname}`) is demoted to a @@ -9,7 +9,7 @@ //! //! ## Determinism boundary (ADR-038) //! SEI allocation is **stateful**: the matcher carries-or-mints by reading the -//! persisted `sei_bindings`. Clarion's byte-identical-run guarantee covers +//! persisted `sei_bindings`. Loomweave's byte-identical-run guarantee covers //! entity/edge/finding *state* — it does **NOT** extend to identity *values*: //! two from-scratch runs with different `run_id`s mint different SEIs for a //! brand-new entity. That is correct, because in a real re-index the prior @@ -33,14 +33,14 @@ use crate::error::StorageError; /// `resolve(locator)` fail-closed-reject an SEI-shaped input — a colon-count /// check is insufficient, since an SEI carries the same two colons a locator /// does. -pub const SEI_PREFIX: &str = "clarion:eid:"; +pub const SEI_PREFIX: &str = "loomweave:eid:"; /// Number of hex chars retained from the blake3 digest (128 bits of identity). const SEI_HEX_LEN: usize = 32; /// Mint a fresh SEI for `locator` in run `mint_run_id` (REQ-C-02 / ADR-038 §1). /// -/// `clarion:eid:`. +/// `loomweave:eid:`. /// /// The `0x00` separator makes the byte concatenation unambiguous (no /// `locator`/`run_id` pair can alias another). Keyed on `mint_run_id` — never @@ -141,7 +141,7 @@ pub struct NewEntityDescriptor { /// A git-detected rename, expressed in **locator** terms (SEI spec §6: /// `{old_locator, new_locator, …}`). The matcher consumes this typed signal, -/// never "Clarion's git code" (REQ-C-05) — so `legis` can supply a concrete +/// never "Loomweave's git code" (REQ-C-05) — so `legis` can supply a concrete /// `GitRenameSource` later with no change to the matcher. #[derive(Debug, Clone, PartialEq, Eq)] pub struct GitRename { @@ -150,7 +150,7 @@ pub struct GitRename { } /// The typed git-rename seam (REQ-C-05). v1's concrete implementation -/// (`ShellGitRenameSource`) lives in `clarion-cli`, where the qualname/path +/// (`ShellGitRenameSource`) lives in `loomweave-cli`, where the qualname/path /// derivation needed to translate file renames into locator renames already /// exists; `legis` becomes the first external supplier post-v1 with no model /// change. @@ -419,7 +419,7 @@ pub struct SeiLineageRow { /// for the current `content_hash`. Returns `None` when the locator resolves to /// nothing alive. /// -/// Callers MUST reject reserved (`clarion:eid:`-prefixed) inputs *before* +/// Callers MUST reject reserved (`loomweave:eid:`-prefixed) inputs *before* /// calling this (REQ-F-02); this function does not re-check (it is a pure /// lookup), so a mis-routed SEI would simply miss. /// @@ -833,7 +833,12 @@ mod tests { fn matcher_carries_unchanged_locator_with_no_event() { let alive = HashMap::from([( "python:function:m.f".to_owned(), - binding("clarion:eid:0001", "python:function:m.f", "h1", Some("s1")), + binding( + "loomweave:eid:0001", + "python:function:m.f", + "h1", + Some("s1"), + ), )]); let cur = locset(&["python:function:m.f"]); let decision = rebind_or_mint( @@ -846,7 +851,7 @@ mod tests { assert_eq!( decision, SeiDecision::Carry { - sei: "clarion:eid:0001".to_owned(), + sei: "loomweave:eid:0001".to_owned(), event: None, } ); @@ -857,7 +862,12 @@ mod tests { // A changed body on the SAME locator is the content axis, not identity. let alive = HashMap::from([( "python:function:m.f".to_owned(), - binding("clarion:eid:0001", "python:function:m.f", "h1", Some("s1")), + binding( + "loomweave:eid:0001", + "python:function:m.f", + "h1", + Some("s1"), + ), )]); let cur = locset(&["python:function:m.f"]); let decision = rebind_or_mint( @@ -870,7 +880,7 @@ mod tests { assert_eq!( decision, SeiDecision::Carry { - sei: "clarion:eid:0001".to_owned(), + sei: "loomweave:eid:0001".to_owned(), event: None, } ); @@ -883,7 +893,7 @@ mod tests { let alive = HashMap::from([( "python:function:auth.login".to_owned(), binding( - "clarion:eid:0001", + "loomweave:eid:0001", "python:function:auth.login", "h1", Some("s1"), @@ -904,7 +914,7 @@ mod tests { assert_eq!( decision, SeiDecision::Carry { - sei: "clarion:eid:0001".to_owned(), + sei: "loomweave:eid:0001".to_owned(), event: Some(LineageEvent::LocatorChanged), } ); @@ -917,7 +927,7 @@ mod tests { let alive = HashMap::from([( "python:function:auth.login".to_owned(), binding( - "clarion:eid:0001", + "loomweave:eid:0001", "python:function:auth.login", "h1", Some("s1"), @@ -944,7 +954,7 @@ mod tests { // locator, exactly one vanished candidate → carry moved. let alive = HashMap::from([( "a.mod.f".to_owned(), - binding("clarion:eid:0001", "a.mod.f", "h1", Some("s1")), + binding("loomweave:eid:0001", "a.mod.f", "h1", Some("s1")), )]); let cur = locset(&["b.mod.f"]); // a.mod.f vanished let decision = rebind_or_mint( @@ -957,7 +967,7 @@ mod tests { assert_eq!( decision, SeiDecision::Carry { - sei: "clarion:eid:0001".to_owned(), + sei: "loomweave:eid:0001".to_owned(), event: Some(LineageEvent::Moved), } ); @@ -969,11 +979,11 @@ mod tests { let alive = HashMap::from([ ( "a.f".to_owned(), - binding("clarion:eid:0001", "a.f", "h1", Some("s1")), + binding("loomweave:eid:0001", "a.f", "h1", Some("s1")), ), ( "a.g".to_owned(), - binding("clarion:eid:0002", "a.g", "h1", Some("s1")), + binding("loomweave:eid:0002", "a.g", "h1", Some("s1")), ), ]); let cur = locset(&["b.new"]); // both a.f and a.g vanished @@ -993,7 +1003,7 @@ mod tests { // new locator (it has not vanished). let alive = HashMap::from([( "a.f".to_owned(), - binding("clarion:eid:0001", "a.f", "h1", Some("s1")), + binding("loomweave:eid:0001", "a.f", "h1", Some("s1")), )]); let cur = locset(&["a.f", "b.copy"]); // a.f STILL present let decision = rebind_or_mint( @@ -1011,7 +1021,7 @@ mod tests { // A null signature cannot satisfy the move predicate (fail-closed). let alive = HashMap::from([( "a.f".to_owned(), - binding("clarion:eid:0001", "a.f", "h1", None), + binding("loomweave:eid:0001", "a.f", "h1", None), )]); let cur = locset(&["b.f"]); let decision = rebind_or_mint(&entity("b.f", Some("h1"), None), &alive, &cur, &[], "run-2"); @@ -1044,28 +1054,28 @@ mod tests { let alive = HashMap::from([ ( "still.here".to_owned(), - binding("clarion:eid:0001", "still.here", "h1", Some("s1")), + binding("loomweave:eid:0001", "still.here", "h1", Some("s1")), ), ( "renamed.old".to_owned(), - binding("clarion:eid:0002", "renamed.old", "h2", Some("s2")), + binding("loomweave:eid:0002", "renamed.old", "h2", Some("s2")), ), ( "deleted.gone".to_owned(), - binding("clarion:eid:0003", "deleted.gone", "h3", Some("s3")), + binding("loomweave:eid:0003", "deleted.gone", "h3", Some("s3")), ), ]); let current = locset(&["still.here", "renamed.new"]); let rematched = locset(&["renamed.old"]); // its SEI was carried let orphans = orphaned_bindings(&alive, ¤t, &rematched); - assert_eq!(orphans, vec!["clarion:eid:0003".to_owned()]); + assert_eq!(orphans, vec!["loomweave:eid:0003".to_owned()]); } #[test] fn orphans_empty_when_nothing_vanished() { let alive = HashMap::from([( "still.here".to_owned(), - binding("clarion:eid:0001", "still.here", "h1", Some("s1")), + binding("loomweave:eid:0001", "still.here", "h1", Some("s1")), )]); let current = locset(&["still.here"]); let orphans = orphaned_bindings(&alive, ¤t, &HashSet::new()); @@ -1093,23 +1103,26 @@ mod tests { #[test] fn alive_snapshot_excludes_orphaned_and_keys_by_locator() { let conn = migrated_conn(); - insert_binding(&conn, &binding("clarion:eid:0001", "a.f", "h1", Some("s1"))); + insert_binding( + &conn, + &binding("loomweave:eid:0001", "a.f", "h1", Some("s1")), + ); insert_binding( &conn, &SeiBinding { status: BindingStatus::Orphaned, - ..binding("clarion:eid:0002", "a.g", "h2", Some("s2")) + ..binding("loomweave:eid:0002", "a.g", "h2", Some("s2")) }, ); let snap = alive_bindings_snapshot(&conn).unwrap(); assert_eq!(snap.len(), 1); - assert_eq!(snap["a.f"].sei, "clarion:eid:0001"); + assert_eq!(snap["a.f"].sei, "loomweave:eid:0001"); assert_eq!( alive_binding_for_locator(&conn, "a.f") .unwrap() .unwrap() .sei, - "clarion:eid:0001" + "loomweave:eid:0001" ); assert!(alive_binding_for_locator(&conn, "a.g").unwrap().is_none()); } @@ -1118,7 +1131,10 @@ mod tests { fn has_any_alive_binding_reflects_state() { let conn = migrated_conn(); assert!(!has_any_alive_binding(&conn).unwrap()); - insert_binding(&conn, &binding("clarion:eid:0001", "a.f", "h1", Some("s1"))); + insert_binding( + &conn, + &binding("loomweave:eid:0001", "a.f", "h1", Some("s1")), + ); assert!(has_any_alive_binding(&conn).unwrap()); } @@ -1141,25 +1157,25 @@ mod tests { fn upsert_binding_carry_moves_locator_in_place_without_collision() { let conn = migrated_conn(); // Mint at the old locator. - upsert_sei_binding(&conn, &record("clarion:eid:0001", "auth.login", "r1")).unwrap(); + upsert_sei_binding(&conn, &record("loomweave:eid:0001", "auth.login", "r1")).unwrap(); // Carry the SAME sei to a new locator (rename) — REPLACE-by-PK moves the // row in place; the alive partial index sees exactly one alive row. let carried = SeiBindingRecord { current_locator: Some("authn.login".to_owned()), updated_run_id: "r2".to_owned(), updated_at: "t1".to_owned(), - ..record("clarion:eid:0001", "authn.login", "r1") + ..record("loomweave:eid:0001", "authn.login", "r1") }; upsert_sei_binding(&conn, &carried).unwrap(); // Old locator no longer resolves; new one does; born_run_id preserved. assert!(resolve_locator(&conn, "auth.login").unwrap().is_none()); assert_eq!( resolve_locator(&conn, "authn.login").unwrap().unwrap().sei, - "clarion:eid:0001" + "loomweave:eid:0001" ); let born: String = conn .query_row( - "SELECT born_run_id FROM sei_bindings WHERE sei = 'clarion:eid:0001'", + "SELECT born_run_id FROM sei_bindings WHERE sei = 'loomweave:eid:0001'", [], |r| r.get(0), ) @@ -1170,32 +1186,32 @@ mod tests { #[test] fn orphan_flips_status_and_frees_the_locator() { let conn = migrated_conn(); - upsert_sei_binding(&conn, &record("clarion:eid:0001", "gone.f", "r1")).unwrap(); - orphan_sei_binding(&conn, "clarion:eid:0001", "r2", "t1").unwrap(); + upsert_sei_binding(&conn, &record("loomweave:eid:0001", "gone.f", "r1")).unwrap(); + orphan_sei_binding(&conn, "loomweave:eid:0001", "r2", "t1").unwrap(); // resolve(locator) no longer finds it alive. assert!(resolve_locator(&conn, "gone.f").unwrap().is_none()); // resolve_sei reports not-alive. assert!(matches!( - resolve_sei(&conn, "clarion:eid:0001").unwrap(), + resolve_sei(&conn, "loomweave:eid:0001").unwrap(), SeiLookupResult::NotAlive { .. } )); // A NEW binding may now take the freed locator (partial index excludes // the orphaned row). - upsert_sei_binding(&conn, &record("clarion:eid:0002", "gone.f", "r2")).unwrap(); + upsert_sei_binding(&conn, &record("loomweave:eid:0002", "gone.f", "r2")).unwrap(); assert_eq!( resolve_locator(&conn, "gone.f").unwrap().unwrap().sei, - "clarion:eid:0002" + "loomweave:eid:0002" ); } #[test] fn lineage_appends_in_order_and_resolve_sei_returns_it_when_orphaned() { let conn = migrated_conn(); - upsert_sei_binding(&conn, &record("clarion:eid:0001", "a.f", "r1")).unwrap(); + upsert_sei_binding(&conn, &record("loomweave:eid:0001", "a.f", "r1")).unwrap(); append_sei_lineage( &conn, &SeiLineageEntry { - sei: "clarion:eid:0001".to_owned(), + sei: "loomweave:eid:0001".to_owned(), event: LineageEvent::Born, old_locator: None, new_locator: Some("a.f".to_owned()), @@ -1207,7 +1223,7 @@ mod tests { append_sei_lineage( &conn, &SeiLineageEntry { - sei: "clarion:eid:0001".to_owned(), + sei: "loomweave:eid:0001".to_owned(), event: LineageEvent::Orphaned, old_locator: Some("a.f".to_owned()), new_locator: None, @@ -1216,16 +1232,16 @@ mod tests { }, ) .unwrap(); - orphan_sei_binding(&conn, "clarion:eid:0001", "r2", "t1").unwrap(); + orphan_sei_binding(&conn, "loomweave:eid:0001", "r2", "t1").unwrap(); - let events: Vec = sei_lineage(&conn, "clarion:eid:0001") + let events: Vec = sei_lineage(&conn, "loomweave:eid:0001") .unwrap() .into_iter() .map(|r| r.event) .collect(); assert_eq!(events, vec!["born".to_owned(), "orphaned".to_owned()]); - match resolve_sei(&conn, "clarion:eid:0001").unwrap() { + match resolve_sei(&conn, "loomweave:eid:0001").unwrap() { SeiLookupResult::NotAlive { lineage } => assert_eq!(lineage.len(), 2), SeiLookupResult::Alive(rec) => panic!("expected NotAlive, got Alive({rec:?})"), } diff --git a/crates/clarion-storage/src/unresolved.rs b/crates/loomweave-storage/src/unresolved.rs similarity index 100% rename from crates/clarion-storage/src/unresolved.rs rename to crates/loomweave-storage/src/unresolved.rs diff --git a/crates/clarion-storage/src/wardline_taint.rs b/crates/loomweave-storage/src/wardline_taint.rs similarity index 96% rename from crates/clarion-storage/src/wardline_taint.rs rename to crates/loomweave-storage/src/wardline_taint.rs index 69501b9a..6fc83e91 100644 --- a/crates/clarion-storage/src/wardline_taint.rs +++ b/crates/loomweave-storage/src/wardline_taint.rs @@ -1,7 +1,7 @@ //! Wardline taint-fact store (SP9, ADR-036). Dedicated per-entity table; //! `wardline_json` is opaque (stored/returned verbatim). Resolution is the //! exact tier: Wardline pre-composes its dotted qualname to byte-match -//! Clarion's `canonical_qualified_name`, so resolution is a direct existence +//! Loomweave's `canonical_qualified_name`, so resolution is a direct existence //! lookup of `python:function:`. Heuristic tier is Flow B B.2. use std::collections::{HashMap, HashSet}; @@ -11,7 +11,7 @@ use rusqlite::{Connection, params}; use crate::query::existing_entity_ids; use crate::{Result, StorageError}; -/// Resolution of a Wardline qualname against Clarion's entity catalog. +/// Resolution of a Wardline qualname against Loomweave's entity catalog. /// /// Exact tier only at 1.1. The Heuristic tier is Flow B B.2 /// (clarion-ca2d26ffbe), which extends THIS enum (e.g. a @@ -49,12 +49,12 @@ impl Resolution { /// Build the candidate entity id for a Wardline pre-composed qualname. /// Taint facts are function/method-scoped (request §3); methods are -/// `python:function:` in Clarion's ontology (ADR-022, fixture-confirmed). +/// `python:function:` in Loomweave's ontology (ADR-022, fixture-confirmed). fn function_candidate(qualname: &str) -> String { format!("python:function:{qualname}") } -/// Resolve one pre-composed Wardline qualname to a Clarion entity id (exact +/// Resolve one pre-composed Wardline qualname to a Loomweave entity id (exact /// tier). Returns `Exact` with the id when the entity exists, else `None`. pub fn resolve_wardline_qualname(conn: &Connection, qualname: &str) -> Result { let resolved = resolve_wardline_qualnames(conn, std::slice::from_ref(&qualname.to_owned()))?; @@ -87,7 +87,7 @@ pub fn resolve_wardline_qualnames( .collect()) } -/// A single taint fact to persist. `wardline_json` is opaque to Clarion. +/// A single taint fact to persist. `wardline_json` is opaque to Loomweave. #[derive(Debug, Clone, PartialEq, Eq)] pub struct TaintFact { pub entity_id: String, @@ -585,13 +585,13 @@ mod tests { "python:function:a.b.c", r#"{"v":1}"#, "t", - Some("clarion:eid:abc123"), + Some("loomweave:eid:abc123"), ), ) .unwrap(); let rows = get_taint_facts(&conn, &["python:function:a.b.c".to_owned()]).unwrap(); assert_eq!(rows.len(), 1); - assert_eq!(rows[0].sei.as_deref(), Some("clarion:eid:abc123")); + assert_eq!(rows[0].sei.as_deref(), Some("loomweave:eid:abc123")); } #[test] @@ -603,7 +603,7 @@ mod tests { let conn = migrated_conn(); insert_entity(&conn, "python:function:old.name", None); insert_entity(&conn, "python:function:new.name", None); - let sei = "clarion:eid:stable"; + let sei = "loomweave:eid:stable"; upsert_taint_fact( &conn, &mk_fact( @@ -637,7 +637,7 @@ mod tests { // dead locator otherwise — the whole point of T3.4). let conn = migrated_conn(); insert_entity(&conn, "python:function:old.name", None); - let sei = "clarion:eid:stable"; + let sei = "loomweave:eid:stable"; upsert_taint_fact( &conn, &mk_fact("python:function:old.name", r#"{"gen":1}"#, "t", Some(sei)), @@ -659,7 +659,7 @@ mod tests { ) .unwrap(); assert!( - get_taint_facts_by_sei(&conn, &["clarion:eid:nope".to_owned()]) + get_taint_facts_by_sei(&conn, &["loomweave:eid:nope".to_owned()]) .unwrap() .is_empty(), "an unknown SEI matches nothing" @@ -680,7 +680,7 @@ mod tests { let conn = migrated_conn(); insert_entity(&conn, "python:function:first", None); insert_entity(&conn, "python:function:second", None); - let sei = "clarion:eid:tie"; + let sei = "loomweave:eid:tie"; let same_ts = "2026-03-01T00:00:00.000Z"; upsert_taint_fact( &conn, @@ -714,7 +714,7 @@ mod tests { fn by_sei_dedups_duplicate_input_seis() { let conn = migrated_conn(); insert_entity(&conn, "python:function:a.b.c", None); - let sei = "clarion:eid:dup"; + let sei = "loomweave:eid:dup"; upsert_taint_fact( &conn, &mk_fact("python:function:a.b.c", r#"{"v":1}"#, "t", Some(sei)), @@ -727,10 +727,15 @@ mod tests { #[test] fn seis_for_locators_returns_only_alive_bindings() { let conn = migrated_conn(); - insert_binding(&conn, "clarion:eid:alive", "python:function:live", "alive"); insert_binding( &conn, - "clarion:eid:dead", + "loomweave:eid:alive", + "python:function:live", + "alive", + ); + insert_binding( + &conn, + "loomweave:eid:dead", "python:function:gone", "orphaned", ); @@ -745,7 +750,7 @@ mod tests { .unwrap(); assert_eq!( map.get("python:function:live").map(String::as_str), - Some("clarion:eid:alive") + Some("loomweave:eid:alive") ); assert!( !map.contains_key("python:function:gone"), diff --git a/crates/clarion-storage/src/writer.rs b/crates/loomweave-storage/src/writer.rs similarity index 97% rename from crates/clarion-storage/src/writer.rs rename to crates/loomweave-storage/src/writer.rs index 0755c1c5..004d58a3 100644 --- a/crates/clarion-storage/src/writer.rs +++ b/crates/loomweave-storage/src/writer.rs @@ -119,7 +119,7 @@ impl Writer { /// Convenience: send a command and await its ack. /// - /// Intended for use by `clarion analyze` (Task 7) and later WP + /// Intended for use by `loomweave analyze` (Task 7) and later WP /// consumers; Sprint 1 integration tests use a local test helper /// rather than this method. Kept as part of the L3 lock-in surface /// so callers have a stable entry point when they arrive. @@ -508,7 +508,7 @@ fn insert_entity( state.in_tx = true; } validate_entity_source_file_anchor(conn, entity)?; - // ON CONFLICT(id) DO UPDATE makes `clarion analyze` idempotent across runs: + // ON CONFLICT(id) DO UPDATE makes `loomweave analyze` idempotent across runs: // a re-walk that produces the same entity updates the existing row instead // of raising UNIQUE. `created_at` and `first_seen_commit` are preserved // (the entity was first seen on its original run); `updated_at` and @@ -589,10 +589,10 @@ fn insert_entity( fn enforce_entity_kind_contract(entity: &EntityRecord) -> Result<()> { if entity.plugin_id != "core" - && clarion_core::plugin::manifest::RESERVED_ENTITY_KINDS.contains(&entity.kind.as_str()) + && loomweave_core::plugin::manifest::RESERVED_ENTITY_KINDS.contains(&entity.kind.as_str()) { return Err(StorageError::WriterProtocol(format!( - "CLA-INFRA-RESERVED-ENTITY-KIND: entity kind {kind:?} is reserved by core; \ + "LMWV-INFRA-RESERVED-ENTITY-KIND: entity kind {kind:?} is reserved by core; \ plugin_id={plugin_id:?} cannot insert entity {id:?}", kind = entity.kind, plugin_id = entity.plugin_id, @@ -623,7 +623,7 @@ fn validate_source_file_anchor( ) .map_err(|err| match err { rusqlite::Error::QueryReturnedNoRows => StorageError::WriterProtocol(format!( - "CLA-INFRA-SOURCE-FILE-MISSING: {context} {source_file_id:?} \ + "LMWV-INFRA-SOURCE-FILE-MISSING: {context} {source_file_id:?} \ does not reference an existing entity" )), other => StorageError::Sqlite(other), @@ -631,7 +631,7 @@ fn validate_source_file_anchor( if !SOURCE_FILE_ANCHOR_KINDS.contains(&kind.as_str()) { let allowed = SOURCE_FILE_ANCHOR_KINDS; return Err(StorageError::WriterProtocol(format!( - "CLA-INFRA-SOURCE-FILE-KIND-CONTRACT: {context} {source_file_id:?} \ + "LMWV-INFRA-SOURCE-FILE-KIND-CONTRACT: {context} {source_file_id:?} \ MUST reference a source-anchor entity with kind in {allowed:?}; \ got kind={kind:?}" ))); @@ -649,7 +649,7 @@ fn validate_entity_source_file_anchor(conn: &Connection, entity: &EntityRecord) } let allowed = SOURCE_FILE_ANCHOR_KINDS; return Err(StorageError::WriterProtocol(format!( - "CLA-INFRA-SOURCE-FILE-KIND-CONTRACT: InsertEntity source_file_id {source_file_id:?} \ + "LMWV-INFRA-SOURCE-FILE-KIND-CONTRACT: InsertEntity source_file_id {source_file_id:?} \ MUST reference a source-anchor entity with kind in {allowed:?}; \ got kind={:?}", entity.kind @@ -680,9 +680,9 @@ pub fn known_scan_time_edge_kinds() -> impl Iterator { /// `docs/implementation/sprint-2/b3-contains-edges.md` §3 Q5 and ADR-026 /// decision 3, extended by ADR-028. Returns a /// [`StorageError::WriterProtocol`] whose message embeds -/// `CLA-INFRA-EDGE-CONFIDENCE-CONTRACT` (per-kind confidence mismatch), -/// `CLA-INFRA-EDGE-SOURCE-RANGE-CONTRACT` (structural/anchored mismatch), or -/// `CLA-INFRA-EDGE-UNKNOWN-KIND` (kind not in the ontology), +/// `LMWV-INFRA-EDGE-CONFIDENCE-CONTRACT` (per-kind confidence mismatch), +/// `LMWV-INFRA-EDGE-SOURCE-RANGE-CONTRACT` (structural/anchored mismatch), or +/// `LMWV-INFRA-EDGE-UNKNOWN-KIND` (kind not in the ontology), /// so the surrounding `runs.stats.failure_reason` carries the code. fn enforce_edge_contract(edge: &EdgeRecord) -> Result<()> { let has_start = edge.source_byte_start.is_some(); @@ -691,7 +691,7 @@ fn enforce_edge_contract(edge: &EdgeRecord) -> Result<()> { if STRUCTURAL_EDGE_KINDS.contains(&edge.kind.as_str()) { if edge.confidence != EdgeConfidence::Resolved { return Err(StorageError::WriterProtocol(format!( - "CLA-INFRA-EDGE-CONFIDENCE-CONTRACT: structural edge kind {kind:?} \ + "LMWV-INFRA-EDGE-CONFIDENCE-CONTRACT: structural edge kind {kind:?} \ MUST carry confidence=resolved; got confidence={confidence:?} \ for ({from} -> {to})", kind = edge.kind, @@ -702,7 +702,7 @@ fn enforce_edge_contract(edge: &EdgeRecord) -> Result<()> { } if has_any_range { return Err(StorageError::WriterProtocol(format!( - "CLA-INFRA-EDGE-SOURCE-RANGE-CONTRACT: edge kind {kind:?} \ + "LMWV-INFRA-EDGE-SOURCE-RANGE-CONTRACT: edge kind {kind:?} \ MUST have NULL source_byte_start/end; got start={start:?} end={end:?} \ for ({from} -> {to})", kind = edge.kind, @@ -715,7 +715,7 @@ fn enforce_edge_contract(edge: &EdgeRecord) -> Result<()> { } else if ANCHORED_EDGE_KINDS.contains(&edge.kind.as_str()) { if edge.confidence == EdgeConfidence::Inferred { return Err(StorageError::WriterProtocol(format!( - "CLA-INFRA-EDGE-CONFIDENCE-CONTRACT: inferred-tier edges are \ + "LMWV-INFRA-EDGE-CONFIDENCE-CONTRACT: inferred-tier edges are \ query-time-only at scan time; got confidence=inferred for \ anchored edge kind {kind:?} ({from} -> {to})", kind = edge.kind, @@ -725,7 +725,7 @@ fn enforce_edge_contract(edge: &EdgeRecord) -> Result<()> { } if !has_start || !has_end { return Err(StorageError::WriterProtocol(format!( - "CLA-INFRA-EDGE-SOURCE-RANGE-CONTRACT: edge kind {kind:?} \ + "LMWV-INFRA-EDGE-SOURCE-RANGE-CONTRACT: edge kind {kind:?} \ MUST have Some source_byte_start AND source_byte_end; got \ start={start:?} end={end:?} for ({from} -> {to})", kind = edge.kind, @@ -737,7 +737,7 @@ fn enforce_edge_contract(edge: &EdgeRecord) -> Result<()> { } } else { return Err(StorageError::WriterProtocol(format!( - "CLA-INFRA-EDGE-UNKNOWN-KIND: edge kind {kind:?} not in the writer \ + "LMWV-INFRA-EDGE-UNKNOWN-KIND: edge kind {kind:?} not in the writer \ ontology; known kinds: {structural:?} + {anchored:?}", kind = edge.kind, structural = STRUCTURAL_EDGE_KINDS, @@ -1209,7 +1209,7 @@ fn commit_run( /// B.3 §5 parent-id consistency check (dual-encoding enforcement, ADR-026 /// decision 2). Runs inside the open write transaction at `CommitRun` time. /// Returns `Ok(None)` when consistent; `Ok(Some(msg))` carrying the -/// `CLA-INFRA-PARENT-CONTAINS-MISMATCH` finding code when not. +/// `LMWV-INFRA-PARENT-CONTAINS-MISMATCH` finding code when not. fn parent_contains_mismatch(conn: &Connection) -> Result> { // Direction 1: every entity.parent_id has a matching contains edge. if let Some((eid, parent, ce_from)) = conn @@ -1237,7 +1237,7 @@ fn parent_contains_mismatch(conn: &Connection) -> Result> { })? { return Ok(Some(format!( - "CLA-INFRA-PARENT-CONTAINS-MISMATCH: entity {eid:?} declares \ + "LMWV-INFRA-PARENT-CONTAINS-MISMATCH: entity {eid:?} declares \ parent_id={parent:?} but no matching `contains` edge exists \ (closest contains.from_id={ce_from:?})" ))); @@ -1267,7 +1267,7 @@ fn parent_contains_mismatch(conn: &Connection) -> Result> { })? { return Ok(Some(format!( - "CLA-INFRA-PARENT-CONTAINS-MISMATCH: contains edge \ + "LMWV-INFRA-PARENT-CONTAINS-MISMATCH: contains edge \ ({from:?} -> {to:?}) has no matching child parent_id \ (child.parent_id={parent:?})" ))); diff --git a/crates/clarion-storage/tests/guidance_write.rs b/crates/loomweave-storage/tests/guidance_write.rs similarity index 98% rename from crates/clarion-storage/tests/guidance_write.rs rename to crates/loomweave-storage/tests/guidance_write.rs index 377004cf..c05f8a51 100644 --- a/crates/clarion-storage/tests/guidance_write.rs +++ b/crates/loomweave-storage/tests/guidance_write.rs @@ -10,13 +10,13 @@ use rusqlite::{Connection, params}; use serde_json::{Value, json}; -use clarion_storage::{ +use loomweave_storage::{ GuidanceSheetInput, delete_guidance_sheet, get_guidance_sheet, guidance_sheet_matches_entity, insert_guidance_sheet, list_guidance_sheets, pragma, schema, upsert_guidance_sheet, }; fn open_fresh(tempdir: &tempfile::TempDir) -> Connection { - let path = tempdir.path().join("clarion.db"); + let path = tempdir.path().join("loomweave.db"); let mut conn = Connection::open(&path).expect("open"); pragma::apply_write_pragmas(&conn).expect("pragmas"); schema::apply_migrations(&mut conn).expect("apply migrations"); @@ -350,7 +350,7 @@ fn matcher_evaluates_kind_tag_and_entity_rules() { let nomatch = sheet_with_rules(&conn, "n", &json!([{"type":"kind","value":"class"}])); let wardline = sheet_with_rules(&conn, "w", &json!([{"type":"wardline_group","group":"x"}])); - let m = |s: &clarion_storage::GuidanceSheet| { + let m = |s: &loomweave_storage::GuidanceSheet| { guidance_sheet_matches_entity(&conn, s, "python:function:pkg.mod.f", project_root).unwrap() }; assert!(m(&kind_sheet)); @@ -365,7 +365,7 @@ fn sheet_with_rules( conn: &Connection, slug: &str, rules: &Value, -) -> clarion_storage::GuidanceSheet { +) -> loomweave_storage::GuidanceSheet { let props = json!({ "content": "x", "scope_level": "module", @@ -403,7 +403,7 @@ fn cache_row_count(conn: &Connection, entity_id: &str) -> i64 { #[test] fn invalidate_summaries_drops_matched_and_keeps_unmatched() { - use clarion_storage::invalidate_summaries_for_sheet; + use loomweave_storage::invalidate_summaries_for_sheet; let tempdir = tempfile::tempdir().unwrap(); let conn = open_fresh(&tempdir); @@ -517,7 +517,7 @@ fn upsert_accepts_valid_guidance_id() { #[test] fn invalidate_summaries_for_no_rule_sheet_is_noop() { - use clarion_storage::invalidate_summaries_for_sheet; + use loomweave_storage::invalidate_summaries_for_sheet; let tempdir = tempfile::tempdir().unwrap(); let conn = open_fresh(&tempdir); @@ -543,7 +543,7 @@ fn invalidate_summaries_includes_guides_edge_targets() { // FINDING 3: a sheet that applies SOLELY via a `guides` edge (NO match_rules) // must still invalidate the guided entity's cached summary. The `guidance_for` // read path composes match_rules OR guides edges, so invalidation must too. - use clarion_storage::invalidate_summaries_for_sheet; + use loomweave_storage::invalidate_summaries_for_sheet; let tempdir = tempfile::tempdir().unwrap(); let conn = open_fresh(&tempdir); @@ -586,7 +586,7 @@ fn invalidate_summaries_includes_guides_edge_targets() { fn invalidate_summaries_dedups_rule_and_guides_match() { // FINDING 3: an entity matched by BOTH a match_rule AND a guides edge is // invalidated exactly once (count is 1, not 2). - use clarion_storage::invalidate_summaries_for_sheet; + use loomweave_storage::invalidate_summaries_for_sheet; let tempdir = tempfile::tempdir().unwrap(); let conn = open_fresh(&tempdir); diff --git a/crates/clarion-storage/tests/llm_cache.rs b/crates/loomweave-storage/tests/llm_cache.rs similarity index 98% rename from crates/clarion-storage/tests/llm_cache.rs rename to crates/loomweave-storage/tests/llm_cache.rs index 4146d466..2df92591 100644 --- a/crates/clarion-storage/tests/llm_cache.rs +++ b/crates/loomweave-storage/tests/llm_cache.rs @@ -2,14 +2,14 @@ use rusqlite::Connection; -use clarion_storage::{ +use loomweave_storage::{ InferredEdgeCacheEntry, InferredEdgeCacheKey, SummaryCacheEntry, SummaryCacheKey, inferred_edge_cache_lookup, pragma, schema, summary_cache_lookup, touch_inferred_edge_cache, touch_summary_cache, upsert_inferred_edge_cache, upsert_summary_cache, }; fn open_fresh(tempdir: &tempfile::TempDir) -> Connection { - let path = tempdir.path().join("clarion.db"); + let path = tempdir.path().join("loomweave.db"); let mut conn = Connection::open(&path).expect("open"); pragma::apply_write_pragmas(&conn).expect("pragmas"); schema::apply_migrations(&mut conn).expect("apply migrations"); diff --git a/crates/clarion-storage/tests/query_helpers.rs b/crates/loomweave-storage/tests/query_helpers.rs similarity index 99% rename from crates/clarion-storage/tests/query_helpers.rs rename to crates/loomweave-storage/tests/query_helpers.rs index a076cc38..aaa76e46 100644 --- a/crates/clarion-storage/tests/query_helpers.rs +++ b/crates/loomweave-storage/tests/query_helpers.rs @@ -2,8 +2,8 @@ use std::path::Path; -use clarion_core::EdgeConfidence; -use clarion_storage::{ +use loomweave_core::EdgeConfidence; +use loomweave_storage::{ ModuleDependencyEdge, ReferenceDirection, SubsystemMember, call_edges_from, call_edges_targeting, child_entity_ids, contained_entity_ids, containing_module_id, entity_at_line, entity_briefing_block_reason, entity_by_id, find_entities, findings_for_emit, @@ -15,7 +15,7 @@ use clarion_storage::{ use rusqlite::{Connection, params}; fn open_fresh(tempdir: &tempfile::TempDir) -> Connection { - let path = tempdir.path().join("clarion.db"); + let path = tempdir.path().join("loomweave.db"); let mut conn = Connection::open(path).expect("open sqlite"); pragma::apply_write_pragmas(&conn).expect("write pragmas"); schema::apply_migrations(&mut conn).expect("apply migrations"); @@ -1434,7 +1434,7 @@ fn insert_finding( confidence_basis, entity_id, related_entities, message, evidence, properties, supports, supported_by, status, created_at, updated_at ) VALUES ( - ?1, 'clarion', '1.0.0', ?2, ?3, ?4, ?5, 0.9, + ?1, 'loomweave', '1.0.0', ?2, ?3, ?4, ?5, 0.9, 'ast_match', ?6, ?7, 'msg', '{}', '{}', '[]', '[]', 'open', strftime('%Y-%m-%dT%H:%M:%fZ', 'now'), @@ -1482,7 +1482,7 @@ fn findings_for_emit_joins_entity_path_and_preserves_nullable_location() { &conn, "core:finding:run-1:defect", "run-1", - "CLA-PY-STRUCTURE-001", + "LMWV-PY-STRUCTURE-001", "defect", "WARN", "python:function:auth.tokens.refresh", @@ -1492,7 +1492,7 @@ fn findings_for_emit_joins_entity_path_and_preserves_nullable_location() { &conn, "core:finding:run-1:weak-modularity", "run-1", - "CLA-FACT-CLUSTERING-WEAK-MODULARITY", + "LMWV-FACT-CLUSTERING-WEAK-MODULARITY", "fact", "INFO", "core:subsystem:abcd", @@ -1505,7 +1505,7 @@ fn findings_for_emit_joins_entity_path_and_preserves_nullable_location() { // Ordered by finding id: "defect" sorts before "weak-modularity". let defect = &rows[0]; assert_eq!(defect.id, "core:finding:run-1:defect"); - assert_eq!(defect.rule_id, "CLA-PY-STRUCTURE-001"); + assert_eq!(defect.rule_id, "LMWV-PY-STRUCTURE-001"); assert_eq!(defect.kind, "defect"); assert_eq!(defect.severity, "WARN"); assert_eq!(defect.entity_id, "python:function:auth.tokens.refresh"); @@ -1548,7 +1548,7 @@ fn findings_for_emit_scopes_to_run_id() { &conn, "f-run1", "run-1", - "CLA-PY-X", + "LMWV-PY-X", "defect", "WARN", "python:function:demo.f", @@ -1558,7 +1558,7 @@ fn findings_for_emit_scopes_to_run_id() { &conn, "f-run2", "run-2", - "CLA-PY-X", + "LMWV-PY-X", "defect", "WARN", "python:function:demo.f", @@ -1594,7 +1594,7 @@ fn findings_for_emit_excludes_briefing_blocked_anchors() { // A secret-scanner anchor for a file the operator OVERRODE // (`--allow-unredacted-secrets`): recorded as Overridden, so its anchor - // carries no briefing_blocked reason. The CLA-SEC-UNREDACTED-SECRETS-ALLOWED + // carries no briefing_blocked reason. The LMWV-SEC-UNREDACTED-SECRETS-ALLOWED // audit finding on it must still reach Filigree (ADR-013 audit trail). insert_entity_with_range( &conn, @@ -1625,7 +1625,7 @@ fn findings_for_emit_excludes_briefing_blocked_anchors() { &conn, "core:finding:run-1:a-defect", "run-1", - "CLA-PY-STRUCTURE-001", + "LMWV-PY-STRUCTURE-001", "defect", "WARN", "python:function:auth.tokens.refresh", @@ -1635,7 +1635,7 @@ fn findings_for_emit_excludes_briefing_blocked_anchors() { &conn, "core:finding:run-1:b-override-allowed", "run-1", - "CLA-SEC-UNREDACTED-SECRETS-ALLOWED", + "LMWV-SEC-UNREDACTED-SECRETS-ALLOWED", "defect", "ERROR", "core:file:allowed.env", @@ -1645,7 +1645,7 @@ fn findings_for_emit_excludes_briefing_blocked_anchors() { &conn, "core:finding:run-1:c-secret-detected", "run-1", - "CLA-SEC-SECRET-DETECTED", + "LMWV-SEC-SECRET-DETECTED", "defect", "ERROR", "core:file:secret.env", diff --git a/crates/clarion-storage/tests/reader_pool.rs b/crates/loomweave-storage/tests/reader_pool.rs similarity index 94% rename from crates/clarion-storage/tests/reader_pool.rs rename to crates/loomweave-storage/tests/reader_pool.rs index 339d282d..16cc3330 100644 --- a/crates/clarion-storage/tests/reader_pool.rs +++ b/crates/loomweave-storage/tests/reader_pool.rs @@ -4,10 +4,10 @@ use std::sync::Arc; use rusqlite::Connection; -use clarion_storage::{ReaderPool, pragma, schema}; +use loomweave_storage::{ReaderPool, pragma, schema}; fn prepared_db(dir: &tempfile::TempDir) -> std::path::PathBuf { - let path = dir.path().join("clarion.db"); + let path = dir.path().join("loomweave.db"); let mut conn = Connection::open(&path).expect("open"); pragma::apply_write_pragmas(&conn).expect("write pragmas"); schema::apply_migrations(&mut conn).expect("migrate"); @@ -111,7 +111,7 @@ async fn pool_queues_when_exhausted_and_proceeds_after_release() { while !*r { r = gate_for_hold.cv_released.wait(r).unwrap(); } - Ok::<_, clarion_storage::StorageError>(()) + Ok::<_, loomweave_storage::StorageError>(()) }) .await }); @@ -194,14 +194,14 @@ async fn reader_error_propagates_and_connection_returns_to_pool() { let _: i64 = conn.query_row("SELECT 1", [], |row| row.get(0))?; // Deliberate invalid SQL to force an error in the closure. conn.query_row("SELECT * FROM non_existent_table", [], |row| row.get(0)) - .map_err(clarion_storage::StorageError::from) + .map_err(loomweave_storage::StorageError::from) }) .await; assert!(err_result.is_err(), "expected closure error to propagate"); assert!(matches!( err_result.unwrap_err(), - clarion_storage::StorageError::Sqlite(_) + loomweave_storage::StorageError::Sqlite(_) )); // Second call on the same pool must succeed — proves the connection @@ -232,7 +232,7 @@ async fn reader_panic_is_caught_as_pool_interact_and_pool_remains_usable() { assert!(panic_result.is_err(), "expected panic to surface as error"); assert!(matches!( panic_result.unwrap_err(), - clarion_storage::StorageError::PoolInteract(_) + loomweave_storage::StorageError::PoolInteract(_) )); // Pool remains usable — deadpool recycles the poisoned connection or @@ -270,7 +270,7 @@ async fn open_validated_fails_fast_on_missing_db() { // Eager validation rejects a missing file up front. (ReaderPool is not // Debug, so we match rather than unwrap_err / format the Result.) match ReaderPool::open_validated(&missing, 2) { - Err(clarion_storage::StorageError::Sqlite(_)) => {} + Err(loomweave_storage::StorageError::Sqlite(_)) => {} Err(other) => panic!("expected a Sqlite error for a missing DB, got {other:?}"), Ok(_) => panic!("open_validated must reject a missing DB"), } @@ -287,11 +287,11 @@ async fn open_validated_fails_on_corrupt_db() { let dir = tempfile::tempdir().unwrap(); let path = dir.path().join("corrupt.db"); // A non-empty, non-SQLite file: open succeeds lazily but the header read - // inside the probe fails — the same surface clarion hook session-start hits. + // inside the probe fails — the same surface loomweave hook session-start hits. std::fs::write(&path, b"this is definitely not a sqlite database").unwrap(); match ReaderPool::open_validated(&path, 2) { - Err(clarion_storage::StorageError::Sqlite(_)) => {} + Err(loomweave_storage::StorageError::Sqlite(_)) => {} Err(other) => panic!("expected a Sqlite error for a corrupt DB, got {other:?}"), Ok(_) => panic!("open_validated must reject a corrupt DB"), } @@ -311,7 +311,7 @@ async fn open_validated_rejects_foreign_application_id() { } match ReaderPool::open_validated(&path, 2) { - Err(clarion_storage::StorageError::ForeignDatabase { application_id }) => { + Err(loomweave_storage::StorageError::ForeignDatabase { application_id }) => { assert_eq!(application_id, 0x7AFE_BABE); } Err(other) => panic!("expected ForeignDatabase, got {other:?}"), @@ -333,7 +333,7 @@ async fn open_validated_rejects_future_user_version() { } match ReaderPool::open_validated(&path, 2) { - Err(clarion_storage::StorageError::FutureUserVersion { found, current }) => { + Err(loomweave_storage::StorageError::FutureUserVersion { found, current }) => { assert_eq!(found, schema::CURRENT_SCHEMA_VERSION + 1); assert_eq!(current, schema::CURRENT_SCHEMA_VERSION); } diff --git a/crates/clarion-storage/tests/schema_apply.rs b/crates/loomweave-storage/tests/schema_apply.rs similarity index 97% rename from crates/clarion-storage/tests/schema_apply.rs rename to crates/loomweave-storage/tests/schema_apply.rs index 42894f22..b94679ec 100644 --- a/crates/clarion-storage/tests/schema_apply.rs +++ b/crates/loomweave-storage/tests/schema_apply.rs @@ -6,10 +6,10 @@ use rusqlite::{Connection, params}; -use clarion_storage::{Writer, error::StorageError, pragma, schema}; +use loomweave_storage::{Writer, error::StorageError, pragma, schema}; fn open_fresh(tempdir: &tempfile::TempDir) -> Connection { - let path = tempdir.path().join("clarion.db"); + let path = tempdir.path().join("loomweave.db"); let mut conn = Connection::open(&path).expect("open"); pragma::apply_write_pragmas(&conn).expect("pragmas"); schema::apply_migrations(&mut conn).expect("apply migrations"); @@ -883,26 +883,26 @@ fn migration_0005_partial_unique_index_allows_one_alive_binding_per_locator() { }; // One alive binding for a locator is fine. - insert("clarion:eid:aaaa", "python:function:m.f", "alive").expect("first alive"); + insert("loomweave:eid:aaaa", "python:function:m.f", "alive").expect("first alive"); // A SECOND alive binding for the same locator violates the partial unique index. - insert("clarion:eid:bbbb", "python:function:m.f", "alive") + insert("loomweave:eid:bbbb", "python:function:m.f", "alive") .expect_err("second alive binding on the same locator must be rejected"); // An orphaned binding may share the former locator (audit history retained). - insert("clarion:eid:cccc", "python:function:m.f", "orphaned") + insert("loomweave:eid:cccc", "python:function:m.f", "orphaned") .expect("orphaned may share locator"); // Two alive bindings with NULL locator do not collide (partial index excludes NULLs). - insert("clarion:eid:dddd", "", "alive").expect("setup distinct locator"); + insert("loomweave:eid:dddd", "", "alive").expect("setup distinct locator"); conn.execute( "INSERT INTO sei_bindings \ (sei, current_locator, body_hash, signature, status, born_run_id, updated_run_id, updated_at) \ - VALUES ('clarion:eid:eeee', NULL, 'h', NULL, 'alive', 'r0', 'r0', 't0')", + VALUES ('loomweave:eid:eeee', NULL, 'h', NULL, 'alive', 'r0', 'r0', 't0')", [], ) .expect("null locator alive #1"); conn.execute( "INSERT INTO sei_bindings \ (sei, current_locator, body_hash, signature, status, born_run_id, updated_run_id, updated_at) \ - VALUES ('clarion:eid:ffff', NULL, 'h', NULL, 'alive', 'r0', 'r0', 't0')", + VALUES ('loomweave:eid:ffff', NULL, 'h', NULL, 'alive', 'r0', 'r0', 't0')", [], ) .expect("null locator alive #2 must not collide"); @@ -916,14 +916,14 @@ fn migration_0005_check_constraints_reject_bad_vocab() { conn.execute( "INSERT INTO sei_bindings \ (sei, current_locator, body_hash, signature, status, born_run_id, updated_run_id, updated_at) \ - VALUES ('clarion:eid:aaaa', 'l', 'h', NULL, 'bogus', 'r0', 'r0', 't0')", + VALUES ('loomweave:eid:aaaa', 'l', 'h', NULL, 'bogus', 'r0', 'r0', 't0')", [], ) .expect_err("sei_bindings.status must reject out-of-vocabulary values"); conn.execute( "INSERT INTO sei_lineage (sei, event, old_locator, new_locator, run_id, recorded_at) \ - VALUES ('clarion:eid:aaaa', 'bogus_event', NULL, NULL, 'r0', 't0')", + VALUES ('loomweave:eid:aaaa', 'bogus_event', NULL, NULL, 'r0', 't0')", [], ) .expect_err("sei_lineage.event must reject out-of-vocabulary values"); @@ -967,7 +967,7 @@ fn insert_finding( "INSERT INTO findings (id, tool, tool_version, run_id, rule_id, kind, severity, \ entity_id, related_entities, message, evidence, properties, supports, \ supported_by, status, created_at, updated_at) \ - VALUES ('f1', 'clarion', '0.1', 'r1', 'CLA-FACT-TODO', ?1, ?2, ?3, '[]', \ + VALUES ('f1', 'loomweave', '0.1', 'r1', 'LMWV-FACT-TODO', ?1, ?2, ?3, '[]', \ 'm', '{}', '{}', '[]', '[]', ?4, \ strftime('%Y-%m-%dT%H:%M:%fZ', 'now'), strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))", params![kind, severity, entity_id, status], @@ -1106,7 +1106,7 @@ fn runs_status_check_accepts_all_documented_values() { } // ---------------------------------------------------------------------------- -// STO-02 (gap-register.md): the writer must self-identify Clarion databases +// STO-02 (gap-register.md): the writer must self-identify Loomweave databases // via SQLite's `application_id` header and refuse forward-incompatible // `user_version` values. These tests pin the open-time contract. // ---------------------------------------------------------------------------- @@ -1145,7 +1145,7 @@ fn open_refuses_db_with_foreign_application_id() { .expect("set foreign application_id"); } let err = spawn_writer_and_drain(path).expect_err( - "Writer::spawn must refuse a SQLite file carrying a non-Clarion application_id", + "Writer::spawn must refuse a SQLite file carrying a non-Loomweave application_id", ); assert!( matches!( @@ -1163,7 +1163,7 @@ fn open_refuses_db_from_future_user_version() { let tempdir = tempfile::tempdir().unwrap(); let path = tempdir.path().join("future.db"); // Open the file via the normal writer path first so it carries the - // Clarion application_id and the v1 schema (the migration runner sets + // Loomweave application_id and the v1 schema (the migration runner sets // user_version=1 on apply). Then bump user_version past current and // re-open via the writer — must refuse. { @@ -1255,7 +1255,7 @@ fn open_sets_application_id_on_legacy_db() { .unwrap(); } - // First open via the writer should set the Clarion application_id. + // First open via the writer should set the Loomweave application_id. spawn_writer_and_drain(path.clone()) .expect("Writer::spawn must accept a legacy (application_id=0) file"); { @@ -1267,8 +1267,8 @@ fn open_sets_application_id_on_legacy_db() { let observed = raw as u32; assert_eq!( observed, - pragma::CLARION_APPLICATION_ID, - "writer must stamp Clarion application_id on a legacy DB" + pragma::LOOMWEAVE_APPLICATION_ID, + "writer must stamp Loomweave application_id on a legacy DB" ); } diff --git a/crates/clarion-storage/tests/sei_conformance_oracle.rs b/crates/loomweave-storage/tests/sei_conformance_oracle.rs similarity index 96% rename from crates/clarion-storage/tests/sei_conformance_oracle.rs rename to crates/loomweave-storage/tests/sei_conformance_oracle.rs index 31ffa5f5..852aef92 100644 --- a/crates/clarion-storage/tests/sei_conformance_oracle.rs +++ b/crates/loomweave-storage/tests/sei_conformance_oracle.rs @@ -1,5 +1,5 @@ -//! SEI conformance oracle (Loom SEI standard §8) — the shared, fixtures-based -//! conformance suite, exercised against a reference Clarion (this crate's real +//! SEI conformance oracle (Weft SEI standard §8) — the shared, fixtures-based +//! conformance suite, exercised against a reference Loomweave (this crate's real //! matcher + identity store + resolution surface). Wave 1 / WS1 (ADR-038). //! //! The six §8 scenarios, each asserted end-to-end through `rebind_or_mint`, @@ -19,7 +19,7 @@ use std::collections::{HashMap, HashSet}; use rusqlite::{Connection, params}; -use clarion_storage::{ +use loomweave_storage::{ GitRename, LineageEvent, NewEntityDescriptor, SeiBindingRecord, SeiLineageEntry, SeiLookupResult, alive_bindings_snapshot, append_sei_lineage, has_any_alive_binding, is_reserved_sei, mint_sei, orphan_sei_binding, orphaned_bindings, rebind_or_mint, @@ -86,8 +86,8 @@ fn apply_run( for d in ordered { let decision = rebind_or_mint(&d, &alive, ¤t_locators, git_renames, run_id); let (sei, event, is_carry) = match decision { - clarion_storage::SeiDecision::Carry { sei, event } => (sei, event, true), - clarion_storage::SeiDecision::Mint { sei } => (sei, Some(LineageEvent::Born), false), + loomweave_storage::SeiDecision::Carry { sei, event } => (sei, event, true), + loomweave_storage::SeiDecision::Mint { sei } => (sei, Some(LineageEvent::Born), false), }; // Dedup carries of the same SEI (fail-closed re-mint). let (sei, event) = if is_carry && !claimed.insert(sei.clone()) { @@ -378,7 +378,7 @@ fn oracle_delete_orphans_and_reports_not_alive() { // ── §8.6 — capability-absent → consumer degrades ───────────────────────────── #[test] fn oracle_capability_absent_degrades_gracefully() { - // A fresh DB before any SEI run models a pre-SEI / capability-absent Clarion: + // A fresh DB before any SEI run models a pre-SEI / capability-absent Loomweave: // the migration exists, but no bindings have been minted. A consumer MUST be // able to detect this and degrade — never crash. let conn = fresh_db(); @@ -392,7 +392,7 @@ fn oracle_capability_absent_degrades_gracefully() { .unwrap() .is_none() ); - match resolve_sei(&conn, "clarion:eid:deadbeefdeadbeefdeadbeefdeadbeef").unwrap() { + match resolve_sei(&conn, "loomweave:eid:deadbeefdeadbeefdeadbeefdeadbeef").unwrap() { SeiLookupResult::NotAlive { lineage } => assert!(lineage.is_empty()), SeiLookupResult::Alive(_) => panic!("unknown SEI must resolve not-alive, not alive"), } diff --git a/crates/clarion-storage/tests/writer_actor.rs b/crates/loomweave-storage/tests/writer_actor.rs similarity index 96% rename from crates/clarion-storage/tests/writer_actor.rs rename to crates/loomweave-storage/tests/writer_actor.rs index 30e017af..a051f29f 100644 --- a/crates/clarion-storage/tests/writer_actor.rs +++ b/crates/loomweave-storage/tests/writer_actor.rs @@ -7,7 +7,7 @@ use std::sync::atomic::Ordering; use rusqlite::Connection; use tokio::sync::oneshot; -use clarion_storage::{ +use loomweave_storage::{ InferredCallEdgeRecord, InferredEdgeCacheEntry, InferredEdgeCacheKey, ReaderPool, SummaryCacheEntry, SummaryCacheKey, UnresolvedCallSiteRecord, Writer, commands::{EdgeConfidence, EdgeRecord, EntityRecord, FindingRecord, RunStatus, WriterCmd}, @@ -15,7 +15,7 @@ use clarion_storage::{ }; fn prepared_db(dir: &tempfile::TempDir) -> std::path::PathBuf { - let path = dir.path().join("clarion.db"); + let path = dir.path().join("loomweave.db"); let mut conn = Connection::open(&path).unwrap(); pragma::apply_write_pragmas(&conn).unwrap(); schema::apply_migrations(&mut conn).unwrap(); @@ -292,8 +292,8 @@ async fn assert_edge_rejected_with_counter( async fn send( tx: &tokio::sync::mpsc::Sender, - build: impl FnOnce(oneshot::Sender>) -> WriterCmd, -) -> Result { + build: impl FnOnce(oneshot::Sender>) -> WriterCmd, +) -> Result { let (ack_tx, ack_rx) = oneshot::channel(); tx.send(build(ack_tx)).await.unwrap(); ack_rx.await.unwrap() @@ -356,7 +356,7 @@ async fn upsert_wardline_taint_fact_persists() { let tx = writer.sender(); send::<()>(&tx, |ack| WriterCmd::UpsertWardlineTaintFact { - fact: Box::new(clarion_storage::TaintFact { + fact: Box::new(loomweave_storage::TaintFact { entity_id: "python:function:a.b.c".to_owned(), wardline_json: r#"{"v":1}"#.to_owned(), scan_id: Some("scan-1".to_owned()), @@ -514,7 +514,7 @@ async fn insert_inferred_edges_materializes_and_skips_static_duplicates() { .await .unwrap(); - let stats = send::(&tx, |ack| { + let stats = send::(&tx, |ack| { WriterCmd::InsertInferredEdges { cache_entry: Box::new(inferred_cache_entry()), edges: vec![ @@ -696,7 +696,7 @@ async fn insert_entity_replaces_entity_tags_for_same_plugin_entity() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn insert_entity_is_idempotent_across_runs() { - // Regression: `clarion analyze` re-runs against an unchanged corpus + // Regression: `loomweave analyze` re-runs against an unchanged corpus // must not crash with `UNIQUE constraint failed: entities.id`. The // insert path UPSERTs on `id`, preserving `created_at`/`first_seen_commit` // and updating the rest from the new run. WS-D smoke gate. @@ -940,7 +940,7 @@ async fn resume_run_errors_when_run_id_unknown() { .await .expect_err("resuming an unknown run id must error"); assert!( - matches!(err, clarion_storage::StorageError::WriterProtocol(ref m) if m.contains("never-begun")), + matches!(err, loomweave_storage::StorageError::WriterProtocol(ref m) if m.contains("never-begun")), "error names the missing run id: {err}" ); @@ -1137,11 +1137,11 @@ async fn non_core_plugin_cannot_insert_reserved_entity_kind() { let err = result.expect_err("reserved entity kind from non-core plugin must fail"); assert!( - matches!(err, clarion_storage::StorageError::WriterProtocol(_)), + matches!(err, loomweave_storage::StorageError::WriterProtocol(_)), "expected WriterProtocol, got {err:?}" ); assert!( - err.to_string().contains("CLA-INFRA-RESERVED-ENTITY-KIND"), + err.to_string().contains("LMWV-INFRA-RESERVED-ENTITY-KIND"), "error should carry reserved-kind code; got {err:#}" ); @@ -1178,10 +1178,10 @@ async fn writer_inserts_fact_findings() { send::<()>(&tx, |ack| WriterCmd::InsertFinding { finding: Box::new(FindingRecord { id: "finding-1".to_owned(), - tool: "clarion".to_owned(), + tool: "loomweave".to_owned(), tool_version: "0.1.0".to_owned(), run_id: "run-finding".to_owned(), - rule_id: "CLA-FACT-CLUSTERING-WEAK-MODULARITY".to_owned(), + rule_id: "LMWV-FACT-CLUSTERING-WEAK-MODULARITY".to_owned(), kind: "fact".to_owned(), severity: "INFO".to_owned(), confidence: Some(0.9), @@ -1249,7 +1249,7 @@ async fn writer_inserts_fact_findings() { assert_eq!( row, ( - "CLA-FACT-CLUSTERING-WEAK-MODULARITY".to_owned(), + "LMWV-FACT-CLUSTERING-WEAK-MODULARITY".to_owned(), "fact".to_owned(), "INFO".to_owned(), "open".to_owned(), @@ -1275,10 +1275,10 @@ async fn insert_finding_is_idempotent_on_resume() { let finding = |message: &str, created_at: &str, updated_at: &str| FindingRecord { id: "core:finding:run-resume:weak-modularity".to_owned(), - tool: "clarion".to_owned(), + tool: "loomweave".to_owned(), tool_version: "1.0.0".to_owned(), run_id: "run-resume".to_owned(), - rule_id: "CLA-FACT-CLUSTERING-WEAK-MODULARITY".to_owned(), + rule_id: "LMWV-FACT-CLUSTERING-WEAK-MODULARITY".to_owned(), kind: "fact".to_owned(), severity: "INFO".to_owned(), confidence: Some(0.9), @@ -1427,8 +1427,8 @@ async fn entity_source_file_id_rejects_non_source_anchor_entity() { .await; let err = result.expect_err("source_file_id must point at a source-anchor entity"); assert!( - format!("{err:?}").contains("CLA-INFRA-SOURCE-FILE-KIND-CONTRACT"), - "expected CLA-INFRA-SOURCE-FILE-KIND-CONTRACT in error; got {err:?}" + format!("{err:?}").contains("LMWV-INFRA-SOURCE-FILE-KIND-CONTRACT"), + "expected LMWV-INFRA-SOURCE-FILE-KIND-CONTRACT in error; got {err:?}" ); drop(tx); @@ -1511,8 +1511,8 @@ async fn module_entity_rejected_as_source_file_id() { .await .expect_err("module entity must not be accepted as a source_file_id anchor"); assert!( - format!("{result:?}").contains("CLA-INFRA-SOURCE-FILE-KIND-CONTRACT"), - "expected CLA-INFRA-SOURCE-FILE-KIND-CONTRACT in error; got {result:?}" + format!("{result:?}").contains("LMWV-INFRA-SOURCE-FILE-KIND-CONTRACT"), + "expected LMWV-INFRA-SOURCE-FILE-KIND-CONTRACT in error; got {result:?}" ); drop(tx); @@ -1523,10 +1523,10 @@ async fn module_entity_rejected_as_source_file_id() { #[test] fn python_plugin_edge_kinds_are_accepted_by_writer_contract() { let manifest = - clarion_core::parse_manifest(include_bytes!("../../../plugins/python/plugin.toml")) + loomweave_core::parse_manifest(include_bytes!("../../../plugins/python/plugin.toml")) .expect("production Python plugin manifest should parse"); let writer_kinds: std::collections::BTreeSet<&'static str> = - clarion_storage::known_scan_time_edge_kinds().collect(); + loomweave_storage::known_scan_time_edge_kinds().collect(); let missing: Vec<&str> = manifest .ontology .edge_kinds @@ -1802,7 +1802,7 @@ async fn insert_entity_without_begin_run_is_protocol_violation() { let err = result.expect_err("InsertEntity without BeginRun should fail"); assert!( - matches!(err, clarion_storage::StorageError::WriterProtocol(_)), + matches!(err, loomweave_storage::StorageError::WriterProtocol(_)), "expected WriterProtocol, got {err:?}" ); @@ -1829,7 +1829,7 @@ async fn commit_run_without_begin_run_is_protocol_violation() { let err = result.expect_err("CommitRun without BeginRun should fail"); assert!( - matches!(err, clarion_storage::StorageError::WriterProtocol(_)), + matches!(err, loomweave_storage::StorageError::WriterProtocol(_)), "expected WriterProtocol, got {err:?}" ); assert!( @@ -1859,7 +1859,7 @@ async fn fail_run_without_begin_run_is_protocol_violation() { let err = result.expect_err("FailRun without BeginRun should fail"); assert!( - matches!(err, clarion_storage::StorageError::WriterProtocol(_)), + matches!(err, loomweave_storage::StorageError::WriterProtocol(_)), "expected WriterProtocol, got {err:?}" ); assert!( @@ -1892,7 +1892,7 @@ async fn commit_run_with_stale_run_id_is_protocol_violation() { let err = result.expect_err("stale CommitRun run_id should fail"); assert!( - matches!(err, clarion_storage::StorageError::WriterProtocol(_)), + matches!(err, loomweave_storage::StorageError::WriterProtocol(_)), "expected WriterProtocol, got {err:?}" ); assert!( @@ -1924,7 +1924,7 @@ async fn fail_run_with_stale_run_id_is_protocol_violation() { let err = result.expect_err("stale FailRun run_id should fail"); assert!( - matches!(err, clarion_storage::StorageError::WriterProtocol(_)), + matches!(err, loomweave_storage::StorageError::WriterProtocol(_)), "expected WriterProtocol, got {err:?}" ); assert!( @@ -1965,7 +1965,7 @@ async fn double_begin_run_is_protocol_violation() { let err = result.expect_err("second BeginRun should fail"); assert!( - matches!(err, clarion_storage::StorageError::WriterProtocol(_)), + matches!(err, loomweave_storage::StorageError::WriterProtocol(_)), "expected WriterProtocol, got {err:?}" ); @@ -2047,7 +2047,7 @@ async fn round_trip_insert_persists_contains_edge() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn contains_edge_with_byte_offsets_rejected_by_per_kind_contract() { // ADR-026 decision 3 / B.3 Q5: contains edges MUST have NULL source range. - // Writer rejects with CLA-INFRA-EDGE-SOURCE-RANGE-CONTRACT. + // Writer rejects with LMWV-INFRA-EDGE-SOURCE-RANGE-CONTRACT. let dir = tempfile::tempdir().unwrap(); let path = prepared_db(&dir); let (writer, handle) = Writer::spawn(path.clone(), 50, 256).unwrap(); @@ -2090,8 +2090,8 @@ async fn contains_edge_with_byte_offsets_rejected_by_per_kind_contract() { let err = result.expect_err("contains edge with byte offsets should be rejected"); let msg = format!("{err:?}"); assert!( - msg.contains("CLA-INFRA-EDGE-SOURCE-RANGE-CONTRACT"), - "expected CLA-INFRA-EDGE-SOURCE-RANGE-CONTRACT in error; got: {msg}" + msg.contains("LMWV-INFRA-EDGE-SOURCE-RANGE-CONTRACT"), + "expected LMWV-INFRA-EDGE-SOURCE-RANGE-CONTRACT in error; got: {msg}" ); drop(tx); @@ -2159,8 +2159,8 @@ async fn calls_edge_without_byte_offsets_rejected_by_per_kind_contract() { .await; let err = result.expect_err("calls edge without byte offsets should be rejected"); assert!( - format!("{err:?}").contains("CLA-INFRA-EDGE-SOURCE-RANGE-CONTRACT"), - "expected CLA-INFRA-EDGE-SOURCE-RANGE-CONTRACT in error; got {err:?}" + format!("{err:?}").contains("LMWV-INFRA-EDGE-SOURCE-RANGE-CONTRACT"), + "expected LMWV-INFRA-EDGE-SOURCE-RANGE-CONTRACT in error; got {err:?}" ); drop(tx); @@ -2219,8 +2219,8 @@ async fn unknown_edge_kind_rejected_strictly() { .await; let err = result.expect_err("unknown edge kind should be rejected"); assert!( - format!("{err:?}").contains("CLA-INFRA-EDGE-UNKNOWN-KIND"), - "expected CLA-INFRA-EDGE-UNKNOWN-KIND in error; got {err:?}" + format!("{err:?}").contains("LMWV-INFRA-EDGE-UNKNOWN-KIND"), + "expected LMWV-INFRA-EDGE-UNKNOWN-KIND in error; got {err:?}" ); assert_eq!( writer.dropped_edges_total.load(Ordering::Relaxed), @@ -2525,7 +2525,7 @@ async fn replace_edges_for_source_file_removes_only_stale_anchored_edges() { async fn parent_id_without_matching_contains_edge_rejects_run() { // B.3 §3 Q2 / §5: parent_id and contains edges are dual encodings of // the same fact. Mismatch at CommitRun time rejects the run with - // CLA-INFRA-PARENT-CONTAINS-MISMATCH and rolls back the transaction. + // LMWV-INFRA-PARENT-CONTAINS-MISMATCH and rolls back the transaction. let dir = tempfile::tempdir().unwrap(); let path = prepared_db(&dir); let (writer, handle) = Writer::spawn(path.clone(), 50, 256).unwrap(); @@ -2567,8 +2567,8 @@ async fn parent_id_without_matching_contains_edge_rejects_run() { .await; let err = result.expect_err("CommitRun should reject parent-id mismatch"); assert!( - format!("{err:?}").contains("CLA-INFRA-PARENT-CONTAINS-MISMATCH"), - "expected CLA-INFRA-PARENT-CONTAINS-MISMATCH in error; got {err:?}" + format!("{err:?}").contains("LMWV-INFRA-PARENT-CONTAINS-MISMATCH"), + "expected LMWV-INFRA-PARENT-CONTAINS-MISMATCH in error; got {err:?}" ); drop(tx); @@ -2643,8 +2643,8 @@ async fn orphan_contains_edge_with_no_matching_parent_id_rejects_run() { .await; let err = result.expect_err("CommitRun should reject orphan contains edge"); assert!( - format!("{err:?}").contains("CLA-INFRA-PARENT-CONTAINS-MISMATCH"), - "expected CLA-INFRA-PARENT-CONTAINS-MISMATCH; got {err:?}" + format!("{err:?}").contains("LMWV-INFRA-PARENT-CONTAINS-MISMATCH"), + "expected LMWV-INFRA-PARENT-CONTAINS-MISMATCH; got {err:?}" ); drop(tx); @@ -2687,8 +2687,8 @@ async fn flush_run_batch_rejects_parent_contains_mismatch_before_commit() { let result = send::<()>(&tx, |ack| WriterCmd::FlushRunBatch { ack }).await; let err = result.expect_err("FlushRunBatch should reject parent mismatch"); assert!( - format!("{err:?}").contains("CLA-INFRA-PARENT-CONTAINS-MISMATCH"), - "expected CLA-INFRA-PARENT-CONTAINS-MISMATCH in error; got {err:?}" + format!("{err:?}").contains("LMWV-INFRA-PARENT-CONTAINS-MISMATCH"), + "expected LMWV-INFRA-PARENT-CONTAINS-MISMATCH in error; got {err:?}" ); send::<()>(&tx, |ack| WriterCmd::FailRun { @@ -2824,7 +2824,7 @@ async fn structural_contains_ambiguous_confidence_rejected() { "python:function:demo.caller", EdgeConfidence::Ambiguous, ), - "CLA-INFRA-EDGE-CONFIDENCE-CONTRACT", + "LMWV-INFRA-EDGE-CONFIDENCE-CONTRACT", ) .await; @@ -2852,7 +2852,7 @@ async fn structural_contains_inferred_confidence_rejected() { "python:function:demo.caller", EdgeConfidence::Inferred, ), - "CLA-INFRA-EDGE-CONFIDENCE-CONTRACT", + "LMWV-INFRA-EDGE-CONFIDENCE-CONTRACT", ) .await; @@ -2880,7 +2880,7 @@ async fn second_structural_kind_inferred_confidence_rejected() { "python:function:demo.caller", EdgeConfidence::Inferred, ), - "CLA-INFRA-EDGE-CONFIDENCE-CONTRACT", + "LMWV-INFRA-EDGE-CONFIDENCE-CONTRACT", ) .await; @@ -2907,7 +2907,7 @@ async fn anchored_calls_inferred_confidence_rejected_at_scan_time() { "python:function:demo.callee", EdgeConfidence::Inferred, ), - "CLA-INFRA-EDGE-CONFIDENCE-CONTRACT", + "LMWV-INFRA-EDGE-CONFIDENCE-CONTRACT", ) .await; @@ -2947,8 +2947,8 @@ async fn anchored_references_missing_or_partial_byte_offsets_rejected() { let err = result.expect_err("references edge with incomplete range should be rejected"); let msg = format!("{err:?}"); assert!( - msg.contains("CLA-INFRA-EDGE-SOURCE-RANGE-CONTRACT"), - "{label}: expected CLA-INFRA-EDGE-SOURCE-RANGE-CONTRACT in error; got {msg}" + msg.contains("LMWV-INFRA-EDGE-SOURCE-RANGE-CONTRACT"), + "{label}: expected LMWV-INFRA-EDGE-SOURCE-RANGE-CONTRACT in error; got {msg}" ); assert_eq!( writer.dropped_edges_total.load(Ordering::Relaxed), @@ -2980,7 +2980,7 @@ async fn anchored_references_inferred_confidence_rejected_at_scan_time() { "python:function:demo.callee", EdgeConfidence::Inferred, ), - "CLA-INFRA-EDGE-CONFIDENCE-CONTRACT", + "LMWV-INFRA-EDGE-CONFIDENCE-CONTRACT", ) .await; diff --git a/docs/README.md b/docs/README.md index d39fd6b2..9f09fe14 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,20 +2,20 @@ This `docs/` tree is organized by reader intent: -- [suite/](./suite/README.md) — Loom-wide doctrine and onboarding. -- [clarion/](./clarion/README.md) — Clarion product documentation, versioned spec set, reviews, and ADRs. -- [operator/](./operator/README.md) — practical configuration notes for running Clarion. +- [suite/](./suite/README.md) — Weft-wide doctrine and onboarding. +- [loomweave/](./loomweave/README.md) — Loomweave product documentation, versioned spec set, reviews, and ADRs. +- [operator/](./operator/README.md) — practical configuration notes for running Loomweave. ## Recommended entry points - New to the suite: [suite/briefing.md](./suite/briefing.md) -- Evaluating the Loom doctrine: [suite/loom.md](./suite/loom.md) -- Starting Clarion: [clarion/1.0/README.md](./clarion/1.0/README.md) +- Evaluating the Weft doctrine: [suite/weft.md](./suite/weft.md) +- Starting Loomweave: [loomweave/1.0/README.md](./loomweave/1.0/README.md) - Configuring OpenRouter: [operator/openrouter.md](./operator/openrouter.md) ## Canonical vs supporting docs - Canonical suite docs live in [suite/](./suite/README.md). -- Canonical Clarion design docs live in [clarion/1.0/](./clarion/1.0/README.md). -- Architecture decisions live in [clarion/adr/](./clarion/adr/README.md). +- Canonical Loomweave design docs live in [loomweave/1.0/](./loomweave/1.0/README.md). +- Architecture decisions live in [loomweave/adr/](./loomweave/adr/README.md). - Supporting reviews, scope memos, sprint plans, and agent handoffs are archived under [implementation/](./implementation/README.md) — non-normative. diff --git a/docs/clarion/adr/ADR-016-observation-transport.md b/docs/clarion/adr/ADR-016-observation-transport.md deleted file mode 100644 index 1fa45d28..00000000 --- a/docs/clarion/adr/ADR-016-observation-transport.md +++ /dev/null @@ -1,128 +0,0 @@ -# ADR-016: Observation Transport — MCP-Spawn (v0.1), Filigree HTTP Endpoint (v0.2) - -**Status**: Accepted -**Date**: 2026-04-18 -**Deciders**: qacona@gmail.com -**Context**: Filigree's observation API is MCP-only today; v0.1 scope commitment deferred the HTTP endpoint to v0.2 - -## Summary - -Clarion emits observations (LLM-proposed guidance, unknown-vocabulary candidates, `knowledge_basis` signals) to Filigree. In v0.1, Clarion spawns a `filigree mcp` subprocess and uses Filigree's existing MCP `create_observation` tool as the transport. In v0.2, Filigree adds `POST /api/v1/observations`; Clarion's observation emit path migrates to HTTP and the subprocess-spawn path retires. The Q1 scope commitment (`v0.1-scope-commitments.md`) explicitly defers the HTTP endpoint to v0.2 — not because it is technically difficult, but because Filigree-side work is already loaded with `registry_backend` surgery (ADR-014) on the v0.1 timeline. MCP-spawn works against the existing Filigree MCP server without Filigree-side change. - -## Context - -Filigree's observation API exists as an MCP tool (`mcp_tools/observations.py` — `create_observation`, `list_observations`, `promote_observation`). There is no HTTP endpoint. The original Clarion design (system-design §9) framed `POST /api/v1/observations` as the preferred transport with MCP-spawn as a fallback when Filigree hadn't shipped the endpoint yet. The 2026-04-17 panel's scope-commitment review re-litigated which Filigree-side work belongs in v0.1 and which belongs in v0.2. - -The Q1 decision (`v0.1-scope-commitments.md`) committed: - -> **v0.1 scope**: Minimal-core + `registry_backend`. Deferred to v0.2: Wardline→Filigree SARIF bridge, **observation HTTP transport**, summary cache beyond in-memory, HTTP write API. - -That commitment reverses the original "preferred / fallback" framing. In v0.1, the HTTP endpoint does not exist; the capability probe has nothing to detect; MCP-spawn is the v0.1 path, not a fallback. This ADR records that commitment and names the v0.2 retirement trigger. - -Clarion is a Rust binary. Spawning `filigree mcp` as a subprocess and speaking MCP over stdio is a known shape (it's exactly the plugin transport, ADR-002). The engineering cost is real but bounded: Rust MCP client crate + process supervision + stdio framing. The alternative — adding `POST /api/v1/observations` to Filigree — is also real engineering (new Flask/FastAPI route, MCP-to-HTTP parity, schema validation, auth plumbing) and competes with `registry_backend` work on the Filigree v0.1 timeline. - -## Decision - -### v0.1 transport — `filigree mcp` subprocess - -Clarion's `clarion analyze` and `clarion serve` both spawn a `filigree mcp` subprocess and use it for observation emission. - -- **Lifecycle**: one subprocess per `clarion analyze` invocation, spawned at Phase 0 alongside the capability probe. Kept alive for the duration of the run; terminated at the final phase boundary. `clarion serve` spawns its own subprocess at startup, kept alive for the lifetime of the serve process. -- **Transport**: stdio JSON-RPC 2.0 with Content-Length framing (same framing as plugin transport, ADR-002). Clarion reuses the framing implementation. -- **MCP tools called**: `create_observation(entity_id, text, source, file_path?, line?)` and `promote_observation(obs_id, issue_template?)` (the latter only from `clarion serve`'s MCP tool surface). -- **Supervision**: process crashes emit `CLA-INFRA-FILIGREE-MCP-CRASHED`; crash-loop circuit breaker (>3 crashes in 60s, same threshold as plugins) disables observation emission for the run. Observations emitted after disable are written to `runs//deferred_observations.jsonl` for manual replay. -- **Filigree binary discovery**: `clarion.yaml:integrations.filigree.binary_path` (default: `filigree` on `PATH`). Not found → `CLA-INFRA-FILIGREE-BINARY-MISSING`; falls back to `--no-filigree` mode (observations written only to `deferred_observations.jsonl`). - -### v0.2 retirement — `POST /api/v1/observations` - -When Filigree ships the HTTP endpoint: - -- Capability probe (system-design §11 step 3) detects it via `HEAD /api/v1/observations` returning 200. -- Clarion's observation emit path switches to HTTP. The `filigree mcp` subprocess is no longer spawned for observation emission. -- The deferred-observations replay mechanism remains for `--no-filigree` runs. -- `clarion serve`'s MCP tools (`emit_observation`, `promote_observation`) continue to exist on Clarion's side — they are consult-agent-facing, not Filigree-facing. They translate internally to the new HTTP emit path. -- The `CLA-INFRA-FILIGREE-OBS-VIA-MCP` finding (previously listed in the compat-report fallback table) is removed from v0.2 — MCP-via-subprocess is no longer the expected v0.1 degradation. - -### Retirement trigger - -Filigree publishes `POST /api/v1/observations` with a schema parallel to the MCP `create_observation` tool. Clarion's capability probe `HEAD /api/v1/observations` returning 200 is the specific switch-over signal. Filigree's CHANGELOG is the authoritative announcement; Clarion's compat-report signals presence. - -### Consult-tool emit path (unchanged by transport flip) - -Clarion's own MCP tool `emit_observation(id, text)` (exposed on `clarion serve`'s MCP surface for consult-mode agents) is transport-independent. In v0.1 it writes to the `filigree mcp` subprocess; in v0.2 it writes via HTTP. The MCP tool's contract to consult agents doesn't change. - -## Alternatives Considered - -### Alternative 1: Filigree HTTP endpoint in v0.1 - -Add `POST /api/v1/observations` to Filigree's v0.1 release; Clarion emits via HTTP from day one. - -**Pros**: simpler Clarion-side — one transport instead of two; no subprocess management; no Filigree binary path dependency. Matches the original design's "preferred" framing. - -**Cons**: Filigree-side engineering competes with `registry_backend` schema surgery (ADR-014) on the v0.1 timeline. The HTTP endpoint is not technically difficult but is not free — route handler, schema validation, MCP-to-HTTP tool parity, auth, tests. Q1 scope commitment explicitly deferred this to v0.2 so that v0.1 Filigree-side work could focus on the one surgery (`registry_backend`) that cannot be worked around. - -**Why rejected**: Q1 commitment locks the v0.1 scope. The MCP-spawn workaround is functional and bounded; Clarion ships on time, Filigree-side work focuses on `registry_backend`, and v0.2 adds the HTTP endpoint cleanly. - -### Alternative 2: TCP MCP transport instead of subprocess spawn - -Clarion connects to a running `filigree serve` over TCP (Filigree's MCP-over-TCP mode) rather than spawning a subprocess. - -**Pros**: no per-run subprocess spawn cost; works with teams running a central Filigree instance; no `filigree` binary path dependency on each dev host. - -**Cons**: Filigree's MCP-over-TCP is less mature than stdio; adds TLS/auth/retry plumbing to the transport layer that stdio avoids. Clarion's v0.1 is explicitly local-first (CON-LOCAL-01) — spawning a subprocess matches that posture better than dialing a network service. The scope-commitment memo's "Q1 minimal-core" posture doesn't include managing a persistent Filigree daemon. - -**Why rejected**: TCP transport's surface area doesn't pay off at v0.1 scale. Subprocess spawn is the lower-cost option consistent with the local-first posture. - -### Alternative 3: Coerce observations into the finding pipeline (ADR-004 path) - -Emit observations as a class of finding (`kind: suggestion`, `scan_source: clarion-obs`) through `POST /api/v1/scan-results`. No separate transport. - -**Pros**: reuses the already-committed finding transport (ADR-004); zero new transport surface. - -**Cons**: observations have a different lifecycle than findings. Filigree observations auto-expire after 14 days (`mcp_tools/observations.py` lifetime policy); findings persist until explicitly closed. Forcing observations through the finding pipeline misrepresents their semantic: operators would see "observations" under the findings UI with no clean signal that they're ephemeral. Filigree's `promote_observation` tool (promotes an observation to an issue) has no finding-path analogue; that workflow breaks. - -**Why rejected**: observations and findings are semantically distinct; the transport shouldn't collapse the distinction. - -### Alternative 4: Defer observations entirely to v0.2 - -v0.1 does not emit observations. Observation generators (LLM-proposed guidance, vocabulary candidates) queue signals locally; v0.2 adds both the HTTP endpoint and the emit path. - -**Pros**: zero transport code in v0.1; no subprocess, no Filigree-binary dependency. - -**Cons**: observations are a load-bearing feedback channel in v0.1. `propose_guidance` (ADR-009) produces observations, not sheets — the guidance-promotion gate *requires* observations to exist for an operator to review. `CLA-FACT-VOCABULARY-CANDIDATE` and similar signals are observation-shaped. Deferring the transport means deferring the feature. - -**Why rejected**: the v0.1 feature set includes observation *emission*; deferring *transport* would defer the feature by proxy. - -## Consequences - -### Positive - -- v0.1 ships without requiring Filigree-side HTTP endpoint work. Filigree's v0.1 engineering focus is `registry_backend` (ADR-014); observations ride on existing MCP infrastructure. -- The spawn approach works today — `filigree mcp` + `create_observation` already exist. Clarion's side is the new code; Filigree's side is zero change. -- Retirement path is explicit: v0.2 HTTP endpoint lands, capability probe detects, Clarion's emit path switches. No ambiguity about when or how. -- Consult-tool contract (`emit_observation` MCP tool on `clarion serve`) is transport-independent. v0.1 → v0.2 migration is invisible to consult agents. - -### Negative - -- Subprocess management in Clarion — spawn, stdio I/O, process supervision, crash-loop handling, termination at shutdown. All real code paths that ADR-002's plugin supervision already has; Clarion reuses that implementation but it's still surface to test. -- `filigree` binary must be on `PATH` (or explicitly configured) for observation emission. Fresh-install developer workstations need the Filigree CLI installed for Clarion to emit observations; otherwise fallback to `deferred_observations.jsonl`. Mitigation: the SUITE-COMPAT-REPORT finding (system-design §11) names the binary-missing case explicitly. -- One extra subprocess per `clarion analyze` run (+ one persistent for `clarion serve`). A few MB RSS each. Tolerable at v0.1 scale; not free. -- `deferred_observations.jsonl` replay is an explicit operator step (`clarion observations replay`); observations generated during a degraded run are not self-healing. Mitigation: the deferred-observations file is a first-class part of the `runs//` structure, not a hidden error log. - -### Neutral - -- The stale system-design §9 and §11 passages framing HTTP as "preferred" and MCP-spawn as "fallback" are updated alongside this ADR's acceptance — the capability-probe fallback table row for "`/api/v1/observations` absent" becomes a v0.2 feature-presence check, not a degradation indicator. -- The `CLA-INFRA-FILIGREE-OBS-VIA-MCP` finding listed in the original design is re-scoped: it was framed as a degradation marker; under this ADR it marks the *expected* v0.1 state and retires alongside the transport in v0.2. - -## Related Decisions - -- [ADR-002](./ADR-002-plugin-transport-json-rpc.md) — the subprocess + stdio + Content-Length framing shape is shared with plugin transport. Clarion reuses ADR-002's implementation for the `filigree mcp` subprocess. -- [ADR-014](./ADR-014-filigree-registry-backend.md) — the scope-commitment counterweight. `registry_backend` is the one Filigree-side surgery v0.1 commits to; deferring the observation HTTP endpoint is what makes that focus possible. -- ADR-009 (pending) — the structured-briefing / propose-guidance decision produces observations via the transport defined here. - -## References - -- [Clarion v0.1 scope commitments — Q1](../../implementation/v0.1-scope-plans/v0.1-scope-commitments.md) — observation HTTP transport explicitly deferred to v0.2. -- [Clarion v0.1 system design §9 (Observation transport)](../v0.1/system-design.md) (lines 916-921) — the passage reversed by this ADR; updated in the same commit. -- [Clarion v0.1 system design §11 (Capability negotiation)](../v0.1/system-design.md) (lines 1122-1141) — observation HTTP presence moves from v0.1 fallback trigger to v0.2 feature-flag detection. -- [Clarion v0.1 detailed design §9.1 Filigree prerequisites](../v0.1/detailed-design.md) (lines 1333-1351) — `POST /api/v1/observations` moved from "Required for v0.1 ship" to "Nice-to-have (v0.2+)". diff --git a/docs/clarion/adr/ADR-017-severity-and-dedup.md b/docs/clarion/adr/ADR-017-severity-and-dedup.md deleted file mode 100644 index 13f785b5..00000000 --- a/docs/clarion/adr/ADR-017-severity-and-dedup.md +++ /dev/null @@ -1,166 +0,0 @@ -# ADR-017: Severity Mapping, Rule-ID Round-Trip, and Dedup Policy - -**Status**: Accepted -**Date**: 2026-04-18 -**Deciders**: qacona@gmail.com -**Context**: Clarion's internal finding vocabulary does not match Filigree's wire vocabulary, and Filigree's dedup key does not match Clarion's entity-centric mental model. Both need a committed mapping. - -## Summary - -Clarion's internal severity vocabulary is `{INFO, WARN, ERROR, CRITICAL}` for defects plus `NONE` for facts; Filigree's wire vocabulary is `{critical, high, medium, low, info}`. The mapping is one-to-one in the forward direction; round-trip fidelity is preserved by copying the internal value into `metadata.clarion.internal_severity` so read-back can recover it. Rule IDs are namespaced by emitter (`CLA-PY-*`, `CLA-INFRA-*`, `CLA-FACT-*`, `CLA-SEC-*`, plus Wardline's own namespaces) and round-trip byte-for-byte — Filigree does not enforce the namespace, but ADR-022's grammar check does at the Clarion-plugin boundary. v0.1 dedup relies on Filigree's existing key (`file_id`, `scan_source`, `rule_id`, `coalesce(line_start, -1)`) plus `mark_unseen=true` on ingest; entity moves within a file transition old-position findings to `unseen_in_latest` rather than overwriting them. Server-side per-entity dedup is deferred to v0.2 (NG-21). - -## Context - -Integration reconnaissance (`integration-recon.md` §2.3, §2.4) verified three concrete mismatches between Clarion's design and Filigree's production intake: - -1. **Severity vocabulary.** Clarion's internal enum (`INFO`, `WARN`, `ERROR`, `CRITICAL`, `NONE`) is a defect-plus-fact split; Filigree's intake takes `{critical, high, medium, low, info}` lowercase and coerces unknowns to `"info"` with a `warnings[]` response (`db_schema.py:133-148`). A one-way emit loses information; a round-trip loses the `NONE`-is-a-fact distinction entirely. - -2. **Rule-ID namespacing.** Filigree stores `rule_id` as a free-text string (`db_schema.py:154-155`) — no enum enforcement. Clarion emits several rule-ID prefix conventions (`CLA-PY-*` for Python-plugin structural findings, `CLA-INFRA-*` for pipeline failures, `CLA-FACT-*` for factual findings, `CLA-SEC-*` for security findings); Wardline emits its own (`PY-WL-*`, `SUP-*`, `SCN-*`, `TOOL-ERROR`). Without a committed mapping and a grammar rule (ADR-022), prefixes drift. The panel's self-sufficiency review (`04-self-sufficiency.md` Issue 7) found exactly this: `requirements.md` REQ-ANALYZE-06 uses `CLA-PY-PARSE-ERROR` but `detailed-design.md` §10 uses `CLA-INFRA-PARSE-ERROR` for the same finding. - -3. **Dedup key.** Filigree's server-side dedup key is `(file_id, scan_source, rule_id, coalesce(line_start, -1))` (`db_schema.py:156-157`). Clarion's mental model is entity-centric — the same rule on the same entity is a single finding, even if the entity moves. The dedup key does not carry `entity_id`, so two `clarion analyze` runs that see the same entity at different line numbers produce two findings rather than one. - -Each mismatch has an operational consequence: lost severity round-trip (operators can't recover Clarion's internal gradation from Filigree's storage), inconsistent rule IDs (triage dashboards have to know both spellings of the same error), and dedup blow-up on every entity move. - -## Decision - -### Severity mapping (emit) - -Clarion's emit path uses this one-to-one mapping: - -| Clarion internal | Filigree wire | Notes | -|---|---|---| -| `CRITICAL` | `critical` | Direct | -| `ERROR` | `high` | Direct | -| `WARN` | `medium` | Direct | -| `INFO` | `info` | Direct | -| `NONE` (facts) | `info` | With `metadata.clarion.kind = "fact"` to distinguish from `INFO` defects | - -Every emitted finding also sets `metadata.clarion.internal_severity` to the pre-mapping value (one of the five Clarion internal values). This is the round-trip anchor. - -### Severity read-back (consume) - -Clarion's consume path (reading its own findings back from Filigree): - -1. If `metadata.clarion.internal_severity` is present → use that value. Lossless round-trip. -2. Else if `severity` is present → reverse-map the wire value to Clarion's internal vocabulary using the table above (reversed). `critical`→`CRITICAL`, `high`→`ERROR`, `medium`→`WARN`, `low`→`INFO` (Filigree-produced only; Clarion itself never emits `low`), `info`→`INFO` if `metadata.clarion.kind != "fact"`, else `NONE`. -3. Else → default `INFO`, emit `CLA-INFRA-FINDING-SEVERITY-MISSING` on the consume path. - -The read-back path handles Wardline-sourced findings (no `metadata.clarion.internal_severity`) and Filigree-authored findings (no Clarion metadata) without conflating them with Clarion-authored ones. - -### SARIF-level mapping (translator input) - -The `clarion sarif import` translator (ADR-015) maps SARIF `result.level` into Clarion internal severity before the emit table above applies: - -| SARIF level | Clarion internal | Filigree wire (via emit table) | -|---|---|---| -| `error` | `ERROR` | `high` | -| `warning` | `WARN` | `medium` | -| `note` | `INFO` | `info` | -| `none` | `INFO` | `info` (with `CLA-INFRA-SARIF-LEVEL-NONE` translator warning — SARIF `none` is rare and usually indicates a tool-side bug) | - -### Rule-ID namespacing - -Clarion rule IDs are namespaced by emitter and purpose: - -- `CLA-PY-*` — Python-plugin structural and rule findings (`CLA-PY-STRUCTURE-001`, `CLA-PY-UNRESOLVED-IMPORT`, etc.). Emittable only by the Python plugin. **Includes parse errors**: `CLA-PY-PARSE-ERROR` is the canonical ID for a Python source file the plugin cannot parse. -- `CLA-FACT-*` — factual observations, emittable by any plugin or the core (`CLA-FACT-TODO`, `CLA-FACT-ENTRYPOINT`, `CLA-FACT-CONDITIONAL-IMPORT`). -- `CLA-INFRA-*` — pipeline and infrastructure failures; core-only. Reserved namespace per ADR-022. -- `CLA-SEC-*` — security-domain findings; emitted by the core's security-gate subsystem (`CLA-SEC-SECRET-DETECTED`, `CLA-SEC-UNREDACTED-SECRETS-ALLOWED`). - -Wardline namespaces (passed through verbatim): `PY-WL-*`, `SUP-*`, `SCN-*`, `TOOL-ERROR`. Future-tool namespaces reserved: `COV-*` (coverage), `SEC-*` (standalone security scanner). ADR-022 enforces the grammar at the Clarion plugin boundary; Filigree performs no enforcement. - -**Issue 7 fix**: `CLA-PY-PARSE-ERROR` is correct; `detailed-design.md` §10's use of `CLA-INFRA-PARSE-ERROR` is the bug. A parse error is Python-plugin-emitted (the plugin tries to parse and fails); it is not a pipeline failure. The namespace rule is "emitter + purpose." This ADR's authoring is the occasion to fix that drift; the detailed-design §10 error surfaces table updates alongside ADR-017's acceptance. - -### Rule-ID round-trip - -Filigree stores `rule_id` byte-for-byte. Clarion's consult tools re-namespace on display: findings with `scan_source="wardline"` surface as "Wardline/PY-WL-001-GOVERNED-DEFAULT"; findings with `scan_source="clarion"` surface unprefixed. This is a rendering convention, not a storage convention. - -### Dedup policy - -Filigree's server-side dedup key is `(file_id, scan_source, rule_id, coalesce(line_start, -1))`. Clarion's emit path: - -- **Every batch**: POST with `mark_unseen=true`. On a rule + file + line tuple that has not appeared in this run but appeared in a prior run, Filigree transitions the prior finding to `unseen_in_latest`. New findings at different line numbers insert as new rows. -- **Resume path** (`clarion analyze --resume`): POST with `mark_unseen=false`. Re-using the same `run_id` on resume must not trigger "no longer seen" transitions for findings the current run has not yet re-visited. -- **Prune policy**: `clarion analyze --prune-unseen` removes `unseen_in_latest` findings older than 30 days (configurable via `clarion.yaml:findings.prune_unseen_days`). Operators running this in CI keep the findings store bounded without losing recent history. - -**Entity move within a file**: producing `unseen_in_latest` at the old line and a fresh finding at the new line is the v0.1 behaviour. It is a known coarseness — operators see the same finding twice briefly (old position marked `unseen_in_latest`, new position active) until `--prune-unseen` runs. The v0.2 improvement (NG-21) is server-side per-entity dedup: an optional `entity_id` extension field on Filigree's dedup key, so `(file_id, scan_source, rule_id, entity_id)` tracks the logical finding across position changes. - -### Rule-ID synthesis (explicit non-option) - -Clarion does **not** synthesise rule IDs with entity IDs (e.g., `CLA-PY-STRUCTURE-001__python:class:foo`). That would defeat Filigree's cross-run triage — every rule-on-every-entity becomes unique, so "acknowledge all STRUCTURE-001 findings" no longer works as a triage operation. - -## Alternatives Considered - -### Alternative 1: Rule-ID synthesis with entity IDs - -Concatenate entity IDs into rule IDs to sidestep Filigree's dedup key mismatch entirely. - -**Pros**: eliminates the entity-move dedup problem at the ID level; no `mark_unseen=true` workaround needed. - -**Cons**: destroys the triage utility of rule IDs. Filigree's UI groups by rule ID to let operators acknowledge a whole class of findings at once; synthesised IDs make every finding unique, so the group-acknowledge operation stops working. Every Filigree query that filters by `rule_id` breaks. - -**Why rejected**: the cost (losing triage) is worse than the problem (transient `unseen_in_latest` findings that prune after 30 days). - -### Alternative 2: Client-side dedup in Clarion - -Clarion reads back its own findings before POSTing, deduplicates client-side, and only emits deltas. - -**Pros**: no Filigree-side changes; Clarion controls the dedup semantics fully. - -**Cons**: introduces read-back coupling on the emit path — Clarion now depends on Filigree being reachable *and* complete before it can emit. A new `clarion analyze` run cannot start emitting until the prior run's findings are all readable. Shifts cross-run identity tracking to Clarion's side, which has no database of Filigree-stored findings; every dedup operation requires a HTTP read. Net worse than the current workaround. - -**Why rejected**: creates a synchronous dependency on Filigree state during emit. - -### Alternative 3: Push Filigree-side per-entity dedup in v0.1 - -Add `entity_id` as an optional fifth column in Filigree's dedup key in v0.1. - -**Pros**: cleanest outcome — dedup matches the entity-centric mental model. No `unseen_in_latest` noise on entity moves. - -**Cons**: Filigree-side schema migration. Existing `file_records` + findings need the migration, existing dedup-query paths need updating, and the `(file_id, scan_source, rule_id, coalesce(line_start, -1))` index must either be supplemented or replaced. Within-scope (same author owns Filigree) but real v0.1 engineering time on top of the registry-backend work (ADR-014). - -**Why deferred**: the v0.1 `mark_unseen=true` workaround is bounded (`--prune-unseen` controls unbounded growth) and well-understood. NG-21 names the v0.2 improvement; deferral is a scope-commitment decision, not a design compromise. - -### Alternative 4: Drop Clarion's internal severity; use Filigree's wire vocabulary directly - -Clarion's internal records store `{critical, high, medium, low, info}` directly. No mapping needed. - -**Pros**: zero mapping surface; round-trip is trivial (no metadata dance). - -**Cons**: loses the `CRITICAL`/`ERROR` distinction in Clarion's own queries. `CRITICAL` means "production-impacting" in Clarion's register; `ERROR` means "design-smell or bug." Filigree's `critical`/`high` mapping preserves a *wire* distinction but the values are operator-facing triage labels, not Clarion's internal semantics. Collapsing them means Clarion's internal query "what critical findings exist?" now returns triage-labelled `critical` findings, not Clarion-semantics `CRITICAL` findings. - -**Why rejected**: the internal vocabulary is load-bearing for Clarion's own reasoning; the mapping surface is small. - -## Consequences - -### Positive - -- Round-trip fidelity for Clarion-emitted findings. An operator who re-reads Clarion's findings from Filigree recovers the internal severity exactly. -- Rule-ID namespacing is now enforceable at the Clarion boundary (ADR-022 grammar check) and round-trips cleanly to Filigree (byte-for-byte). The `CLA-INFRA-PARSE-ERROR` vs `CLA-PY-PARSE-ERROR` drift noted in the panel's self-sufficiency review has a committed resolution. -- Dedup policy is explicit and bounded. `mark_unseen=true` + 30-day prune gives operators a predictable findings-store size. The v0.2 NG-21 upgrade path is named. -- SARIF translator severity is locked; operators running `clarion sarif import wardline.sarif.baseline.json` see Wardline's `error`/`warning`/`note` land at the correct Filigree wire values. - -### Negative - -- Entity moves within a file produce transient duplicates (old-position `unseen_in_latest`, new-position active) until pruning. Operators reading Filigree UI during an active `clarion analyze` see both. Mitigation: the two-value window is expected and documented; `--prune-unseen` run in CI keeps the window bounded. -- The severity mapping is asymmetric by necessity (`NONE`/`INFO` both map to `info` on the wire) — recovering `NONE` on read-back relies on `metadata.clarion.kind` presence. If another emitter writes `info` without that metadata field, Clarion's read-back treats it as `INFO`. This is the correct default (unknown info-severity findings are defects by default) but operators writing custom emitters into Filigree should know the convention. -- Rule-ID namespace enforcement is at the Clarion boundary, not at Filigree. A Wardline bug that emits `CLA-PY-*` IDs (shouldn't happen, but could under maintenance error) lands in Filigree without complaint. Mitigation: ADR-022 + cross-tool fixture tests (`detailed-design.md` §9.3) catch this class of error at release time. - -### Neutral - -- `scan_run_id` lifecycle (create at Phase 0, complete at last batch, don't complete on resume) is already specified in detailed-design §7. This ADR doesn't re-decide it but cites it; the `mark_unseen=true` policy sits inside that lifecycle. -- SARIF property-bag preservation (ADR-019) is complementary: the severity mapping here and the property-bag mapping there together define the full SARIF → Filigree translation for the ADR-015 translator. - -## Related Decisions - -- [ADR-004](./ADR-004-finding-exchange-format.md) — Filigree-native intake is the target format; this ADR fills in the severity and rule-ID mapping that the format needs. -- [ADR-015](./ADR-015-wardline-filigree-emission.md) — the SARIF translator applies the severity mapping defined here. Rule-ID round-trip for Wardline's `PY-WL-*` / `SUP-*` / `SCN-*` namespaces happens through this ADR's byte-for-byte policy. -- ADR-019 (pending) — SARIF property-bag preservation complements this ADR. Where ADR-019 governs `metadata._properties.*` for extension keys, this ADR governs severity + rule-ID values. -- [ADR-022](./ADR-022-core-plugin-ontology.md) — grammar enforcement on rule-ID prefixes is implemented at manifest acceptance and RPC time; this ADR's namespace choices populate the allowed set. - -## References - -- [Clarion v0.1 detailed design §7](../v0.1/detailed-design.md) (lines 1163-1200) — the canonical mapping tables; this ADR formalises what's already there and fixes Issue 7. -- [Clarion v0.1 integration reconnaissance §2.3, §2.4](../../implementation/v0.1-reviews/pre-restructure/integration-recon.md) — empirical evidence for severity vocabulary and dedup key on Filigree's side (`db_schema.py` line numbers). -- [Panel self-sufficiency review — Issue 7](../../implementation/v0.1-reviews/panel-2026-04-17/04-self-sufficiency.md) (lines 132-134) — rule-ID namespace inconsistency this ADR resolves. -- [Clarion v0.1 requirements — REQ-FINDING-02, NG-21](../v0.1/requirements.md) — rule-ID namespace rule; v0.2 server-side per-entity dedup deferral. diff --git a/docs/clarion/adr/ADR-018-identity-reconciliation.md b/docs/clarion/adr/ADR-018-identity-reconciliation.md deleted file mode 100644 index 7b14ef3e..00000000 --- a/docs/clarion/adr/ADR-018-identity-reconciliation.md +++ /dev/null @@ -1,204 +0,0 @@ -# ADR-018: Identity Reconciliation — Clarion Translates; Wardline Owns Its Qualnames - -**Status**: Accepted (Revision 3, 2026-06-05 — direct REGISTRY import asterisk retired; Python plugin reads Wardline's NG-25 descriptor without importing Wardline.) -**Date**: 2026-04-18 -**Deciders**: qacona@gmail.com -**Context**: three independent identity schemes exist across Clarion, Wardline, and Wardline's exception register; one-way translation is the federation-compatible answer - -## Summary - -Clarion maintains the translation layer between its own `EntityId` scheme (ADR-003) and Wardline's qualnames / exception-register locations. Wardline is authoritative for its own qualnames and does **not** adopt Clarion's ID format. The Python plugin imports `wardline.core.registry.REGISTRY` directly at startup with a `REGISTRY_VERSION` pin; skew is handled with three graduated responses (exact / additive / major-bump) rather than hard failure. The HTTP read API exposes an `entities/resolve?scheme=…&value=…` oracle so siblings ask in their own scheme instead of embedding Clarion's ID format. The direct-import pattern is an **initialization coupling** (`loom.md` §5 asterisk 2) scoped to the Wardline-aware plugin specifically; the Clarion core and any non-Wardline-aware plugin do not depend on Wardline being importable. - -## Context - -Three identity schemes coexist and none are byte-equal for the same underlying symbol (`detailed-design.md:557-561`): - -| Scheme | Example | Owner | Format | -|---|---|---|---| -| Clarion `EntityId` | `python:class:auth.tokens::TokenManager` | Clarion | `{plugin_id}:{kind}:{canonical_qualified_name}` | -| Wardline `module` + `qualified_name` | `src/auth/tokens.py` + `TokenManager.verify` | Wardline `FingerprintEntry` | Source file path plus Python's bare `__qualname__`; not byte-equal to Clarion's dotted-module-prefixed `qualified_name` | -| Wardline exception-register `location` | `src/wardline/scanner/engine.py::ScanEngine._scan_file` | `wardline.exceptions.json` | `{file_path}::{qualname}` | - -Any cross-tool query — "which Filigree findings attach to this Clarion entity?", "which Wardline exception covers this qualname?" — has to bridge at least two of these. The ADR-003 decision to use symbolic canonical names produces Clarion's scheme but leaves the translation question open. - -The Wardline integration adds a second dimension: Clarion's Python plugin depends on Wardline's decorator vocabulary (`REGISTRY`) to detect annotations correctly. Two approaches existed before this ADR — heuristic name-matching against a hardcoded list, or a direct Python import of the vocabulary. The design picked direct import (`system-design.md:949`) with `REGISTRY_VERSION` pinning, but the *why* (and the retirement conditions) weren't formalised. - -The Loom doctrine is deliberate about identity (`loom.md` §6): Loom is **not** an identity-reconciliation service. "When cross-scheme translation is needed — e.g. Wardline qualname → Clarion entity ID — the product that *cares* does the translation, because that product is the one whose authority needs it." Clarion cares because Clarion owns the catalog that makes Wardline's qualnames meaningful to anything other than Wardline itself. - -The 2026-04-17 panel's doctrine synthesis (`11-doctrine-panel-synthesis.md`) flagged the direct-import pattern as an explicit federation asterisk needing a retirement condition, not a quiet dependency. `loom.md` §5 now carries that asterisk: initialization coupling scoped to the Wardline-aware plugin specifically; non-Wardline-aware plugins and the Clarion core remain Wardline-independent. - -## Decision - -### Translation direction and authority - -- **Inbound translation only.** Clarion translates Wardline qualnames, exception-register locations, and SARIF logical locations into `EntityId`s. Clarion does not push its ID scheme outbound — Wardline's emissions remain in Wardline's format, and Clarion's translation layer maps them at ingest. -- **Wardline is authoritative for its own qualnames.** `FingerprintEntry.qualified_name` and its paired `module` path are Wardline's contract; Clarion reads them and maps them. A future Wardline refactor that changes either field's format is a Wardline-side decision Clarion adapts to; the inverse is not true. -- **Reverse mapping is recorded, not pushed.** `WardlineMeta.wardline_qualname` on each Clarion entity property is the reverse lookup cache. It lives on Clarion's entity, not on Wardline's side, and is re-computed on every `clarion analyze`. - -### 2026-05-18 amendment: asymmetric qualname storage is canonical - -Sprint 1 verification proved that the two products encode the same Python symbol -with different field boundaries: - -- Clarion's Python plugin emits `qualified_name = - "{dotted_module}.{python.__qualname__}"` on each entity, e.g. - `auth.tokens.TokenManager.verify`. -- Wardline's `FingerprintEntry` stores `module` as the source file path and - `qualified_name` as Python's bare `__qualname__`, e.g. - `src/auth/tokens.py` plus `TokenManager.verify`. - -Direct string equality between Clarion's `qualified_name` and Wardline's -`qualified_name` is therefore forbidden for joins. Wardline-to-Clarion -translation composes Clarion's dotted module name from Wardline's `module` -using the same rules as the Python plugin's `module_dotted_name()` (`src/` -prefix stripped, `.py` removed, and `__init__.py` collapsed), then appends -Wardline's bare `qualified_name`. Clarion-to-Wardline translation must prefer -the recorded `WardlineMeta` / translator output; naively splitting on dots is -unsafe because dotted class chains and `` markers are part of Python's -`__qualname__`, not the module path. - -### 2026-05-29 amendment: Wardline emits the dotted qualname pre-composed - -The 2026-05-29 Wardline ↔ Loom integration brief (§4.A) resolves the asymmetric-storage clash **in Clarion's favor at the emission boundary**. Under the generic Wardline rebuild's native Filigree emitter ([ADR-015](./ADR-015-wardline-filigree-emission.md) Revision 2), each emitted finding carries `metadata.wardline.qualname` as the **combined dotted `module.qualified_name`** (e.g. `auth.tokens.TokenManager.issue`) — Clarion's L7 form, not Wardline's `(file-path module, bare qualname)` storage pair. - -Two consequences: - -1. **A clarified entry point.** This is a fifth reconciliation surface alongside the four below: Clarion reads `metadata.wardline.qualname` off a **Filigree finding** (via the federation read path / `issues_for` enrichment), not off a Wardline state file. The composition rule reverses — the 2026-05-18 amendment's "Clarion composes the dotted module name from Wardline's `module`" becomes "**Wardline emits it pre-composed; Clarion matches by direct qualname equality**" (`find_entity` by qualname). The state-file entry points (1–3) and their composition rule remain unchanged for any path that still reads Wardline's on-disk artifacts (e.g. historical SARIF baselines). - -2. **The normalization contract is now Wardline's, at the emission boundary.** Because Wardline now performs the dotting Clarion used to do, byte-equality depends on Wardline replicating `module_dotted_name()` **exactly**: `src/` prefix stripped, `.py` removed, `__init__.py` collapsed (and the analogous handling for namespace-package / non-`src` layouts), while preserving `` markers and dotted class chains in `__qualname__` verbatim — those are part of the qualname, not the module path, and must not be re-dotted or stripped. If Wardline's composition diverges, reconciliation degrades silently to `resolution_confidence: heuristic | none` on exactly the nested-class and closure entities where it is least recoverable. Clarion exposes the canonical rules via `module_dotted_name()` and the `GET /api/v1/entities/resolve?scheme=wardline_qualname` oracle; a divergence shows up there as a non-`exact` resolution. This contract is the open ask-back to Wardline recorded in ADR-015 Revision 2; it is an enrichment-quality concern and does not gate the (Wardline, Filigree) transport path. - -### Translation entry points (v0.1) - -1. **`wardline.fingerprint.json`**: for each `FingerprintEntry`, compute `(module, qualified_name) → EntityId` using Wardline's `module_file_map` (from `ScanContext`). Write `WardlineMeta.wardline_qualname = qualified_name` on the resolved Clarion entity. -2. **`wardline.exceptions.json`**: parse `location` as `file_path::qualname` (split on the first `::`); same mapping rule. Unresolvable entries emit `CLA-INFRA-WARDLINE-EXCEPTION-UNRESOLVED` and persist as dangling records with `entity_id: null`. -3. **`wardline.sarif.baseline.json`**: use `location.logicalLocations[].fullyQualifiedName` when present, or `partialFingerprints` as a fallback. Unresolvable SARIF results carry `metadata.clarion.unresolved: true` through translation. -4. **`GET /api/v1/entities/resolve?scheme=&value=`**: HTTP read-API oracle. Schemes accepted: `wardline_qualname`, `wardline_exception_location`, `file_path`, `sarif_logical_location`. Response carries `resolution_confidence` (`exact | heuristic | none`) plus candidates for non-exact matches. 404-like misses return 200 with `resolution_confidence: "none"` to distinguish "Clarion doesn't know" from "Clarion is down." - -### Direct REGISTRY import with REGISTRY_VERSION pin - -The Python plugin imports `wardline.core.registry.REGISTRY` at startup. The `wardline` package is a dependency declared in the plugin's pipx venv. Skew behaviour (REQ-INTEG-WARDLINE-01, NFR-COMPAT-02): - -| Installed `REGISTRY_VERSION` vs pinned | Behaviour | -|---|---| -| Exact match | Normal operation | -| Additive-newer (same major, same or higher minor) | Proceed with warning; decorators in the installed REGISTRY beyond the pin detected with `confidence_basis: clarion_augmentation`. Emit `CLA-INFRA-WARDLINE-REGISTRY-ADDITIVE-SKEW` | -| Major-bump or older | Fall back to hardcoded registry mirror (`wardline_registry_v.py`). Findings carry `confidence_basis: mirror_only`. Emit `CLA-INFRA-WARDLINE-REGISTRY-MIRRORED` | -| Wardline package not installable in plugin venv | Mirror mode from startup (same `MIRRORED` emission); `--no-wardline` declares the intent explicitly | - -Pin policy: `REGISTRY_VERSION` is updated at Clarion release time alongside the hardcoded mirror. A Wardline minor bump between Clarion releases degrades to additive-skew (safe); a major bump degrades to mirror mode (safe but lossy). - -### Why plugin-level, not core-level - -The REGISTRY import is a property of the Wardline-aware plugin specifically, not of the Clarion core (`loom.md` §5 asterisk 2). The Rust core has no import path to Wardline; it's the Python plugin's startup that walks `sys.path` to load `wardline.core.registry`. The asterisk is named in `loom.md` §5 with an explicit retirement condition: when Wardline publishes a YAML/JSON descriptor export of its REGISTRY (NG-25, v0.2), non-Python plugins can consume it without a Python import, and the initialization coupling retires to a plain file-descriptor read. - -This preserves the federation test: removing Wardline breaks Wardline-derived annotation detection but does not prevent the Clarion core from starting, does not prevent non-Wardline-aware plugins from running, and does not alter the meaning of Clarion's own catalog entries. - -### Revision 3 (2026-06-05): direct-import asterisk retired - -Wardline now publishes the NG-25 trust-vocabulary descriptor as `vocabulary.yaml` -and through `wardline vocab`. Clarion's Python plugin consumes that descriptor -instead of importing `wardline.core.registry.REGISTRY`. Resolution is -project-local `.wardline/vocabulary.yaml` first, then the installed Wardline -distribution data file `wardline/core/vocabulary.yaml`; both paths are plain -file reads and neither imports `wardline`, `wardline.core`, or -`wardline.core.registry`. - -The plugin records source-observed decorator facts on Clarion entities as -Wardline metadata and `wardline:*` tags. Wardline remains authoritative for the -vocabulary and policy semantics; Clarion stores only what it parsed from source -against the descriptor. Missing or invalid descriptors continue to degrade -honestly: normal structural extraction proceeds, `capabilities.wardline` reports -the degraded state, and no Wardline entity metadata is emitted. - -This closes the `loom.md` §5 initialization-coupling asterisk on the Clarion -side. The identity translation rules and `REGISTRY_VERSION`/descriptor-version -skew posture remain bilateral compatibility concerns; the load-bearing change is -that plugin startup no longer requires Wardline to be importable. - -## Alternatives Considered - -### Alternative 1: Wardline adopts Clarion's entity-ID scheme - -Wardline changes its `FingerprintEntry.qualified_name` to carry Clarion's `{plugin_id}:{kind}:{canonical_qualified_name}` format directly. - -**Pros**: zero translation layer; one identity scheme across the suite; cross-tool queries are string-equal comparisons. - -**Cons**: Wardline can no longer stand alone — its internal data carries a format whose meaning depends on Clarion's ID-generation conventions. `loom.md` §6 prohibition "no identity reconciliation service" is violated: the scheme *is* a reconciliation service, embedded in one product's data. If Clarion changes its kind vocabulary or qualname normalisation, Wardline's historical data silently reinterprets. Wardline must re-analyze every commit Clarion has ever indexed to keep its IDs coherent. - -**Why rejected**: it imports Clarion's authority into Wardline. The solo-use failure mode ("Filigree + Wardline without Clarion") breaks — Wardline cannot emit stable IDs without knowing Clarion's ID conventions. - -### Alternative 2: Wardline qualnames become the suite-wide canonical identity - -Clarion adopts Wardline's qualname format as its entity ID. - -**Pros**: Wardline already computes them deterministically; the existing format is proven in production. - -**Cons**: Python-specific (`TokenManager.verify` has no Java or Rust analogue that Wardline could produce); entity kinds Wardline doesn't scan — `file`, `subsystem`, `guidance` — have no qualname at all. Clarion's multi-language roadmap breaks the moment a Java plugin emits an entity with no qualname-shaped identity. - -**Why rejected**: specialisation to one language + missing coverage for core-owned kinds. Same `loom.md` §6 violation inverted — now Wardline's authority is imported into Clarion. - -### Alternative 3: Loom identity reconciliation service - -A neutral Loom-level service maintains a translation table that every product queries. - -**Pros**: symmetric; no product "owns" identity. - -**Cons**: violates `loom.md` §6 ("Loom is not an identity reconciliation service") categorically, and §5 pipeline-coupling (the (Wardline, Filigree) pair composes only through a Loom mediator). Introduces the stealth-monolith pattern Loom exists to prevent. - -**Why rejected**: categorical doctrine violation. The test for adding Loom-level services ("if the proposal introduces something that would need to be running or present for the suite to work, it violates federation") fails immediately. - -### Alternative 4: Heuristic-only reconciliation (no REGISTRY import; string matching) - -Clarion's plugin does not import Wardline's REGISTRY. Decorator detection uses a hardcoded list of known Wardline decorator names, updated in Clarion releases. - -**Pros**: no initialization coupling. Clarion's plugin starts without Wardline present. - -**Cons**: every Wardline decorator addition creates a drift window — the decorator lands in Wardline, but Clarion's plugin doesn't learn about it until the next Clarion release. For a v0.1 suite where Clarion and Wardline ship on independent cadences (and are the same author), the drift window is real and silent. The whole point of the REGISTRY is to be the shared vocabulary; refusing to import it re-creates the drift the REGISTRY was supposed to eliminate. - -**Why rejected**: trades a named, retirement-conditioned initialization coupling (this ADR) for silent vocabulary drift. The named coupling is strictly preferable. - -### Alternative 5: File-descriptor read of a YAML/JSON REGISTRY descriptor - -Wardline exports its REGISTRY as a declarative descriptor file (`wardline_registry.yaml` or similar). Clarion's plugin reads the descriptor at startup instead of importing Python. - -**Pros**: language-neutral — works for non-Python plugins. No Python import dependency. Cleaner federation shape (plain file-descriptor consumption). - -**Cons**: Wardline has no such descriptor export today. Adding it is within-scope but is Wardline-side work the v0.1 Clarion plan does not commit to. NG-25 already names it as a v0.2 Wardline prerequisite ("YAML/JSON descriptor of REGISTRY enables non-Python plugins"), and the asterisk's retirement condition in `loom.md` §5 is exactly this. - -**Why rejected for v0.1**: premature. v0.1 ships Python-only, so the Python-import path is sufficient and cheaper. The descriptor is the documented v0.2 path; the asterisk retires when it lands. - -## Consequences - -### Positive - -- Translation is load-bearing for exactly one product (Clarion) in exactly one direction (inbound). Wardline and Filigree don't know translation happens. -- `REGISTRY_VERSION` pin with graduated skew response (exact / additive / mirror) means install skew degrades gracefully rather than hard-failing. An operator with a half-updated dev environment still gets useful output. -- The `entities/resolve` HTTP oracle exposes translation to siblings without requiring them to embed Clarion's ID format. Wardline's v0.2 HTTP client uses it; Filigree MCP calls already use its spiritual equivalent. -- Reverse mapping (`WardlineMeta.wardline_qualname`) enables "what Wardline qualname corresponds to this entity?" without re-running the reconciliation, and it lives on Clarion's data, not Wardline's. - -### Negative - -- Initialization coupling is named but not eliminated. Clarion's Python plugin cannot start without `wardline` installed in its venv; the coupling retires when NG-25 (YAML/JSON REGISTRY descriptor) lands in v0.2. The asterisk lives in `loom.md` §5 with that condition. An operator who installs Clarion without Wardline gets a plugin that runs in mirror mode from startup, which is noisy but functional; a misinstalled venv (wrong Python, missing dep) produces a clear startup failure with `CLA-INFRA-WARDLINE-REGISTRY-MIRRORED` as the first signal. -- Heuristic reconciliation (file moves, symbol renames not tracked by EntityAlias — ADR-003 names the v0.1 limitation) produces `resolution_confidence: heuristic` or `none` results. Operators see `CLA-INFRA-WARDLINE-EXCEPTION-UNRESOLVED` and run `clarion analyze --repair-aliases` manually. -- Three-scheme translation is quadratic in failure modes — the `resolve` oracle has to know every scheme, and every scheme has its own unresolvable case. Tractable at v0.1 scale (four schemes) but bears watching as siblings grow. - -### Neutral - -- The translation layer lives entirely in Clarion — plugin-side for REGISTRY import and qualname mapping, core-side for the `entities/resolve` oracle. No Wardline or Filigree changes are required by this ADR. -- The v0.2 YAML/JSON REGISTRY descriptor simplifies this ADR rather than replacing it: the import path becomes a file read, but the translation layer and the `REGISTRY_VERSION` pin semantics are unchanged. - -## Related Decisions - -- [ADR-002](./ADR-002-plugin-transport-json-rpc.md) — the subprocess transport is where the plugin's startup REGISTRY import happens. ADR-021's path jail does not apply (the import walks `sys.path`, not plugin-emitted paths). -- [ADR-003](./ADR-003-entity-id-scheme.md) — Clarion's `EntityId` format is the target of every translation entry point here. The v0.1 limitation ADR-003 names (symbol renames without file move) is the specific case this ADR's heuristic fallback covers. -- [ADR-015](./ADR-015-wardline-filigree-emission.md) — the SARIF translator is translation entry point 3. ADR-015 decides the translator's Wardline role (v0.1 bridge, retiring in v0.2); this ADR's translation rules apply for as long as the translator exists for any SARIF source. -- [ADR-021](./ADR-021-plugin-authority-hybrid.md) — the plugin's REGISTRY import is an import, not a file read, so ADR-021's path jail does not apply. The `RLIMIT_AS` cap does apply; operators with unusually large Wardline REGISTRY installs see it first. -- [ADR-022](./ADR-022-core-plugin-ontology.md) — identity translation is plugin-side; the core does not embed Wardline's qualname format. A future non-Wardline-aware plugin follows ADR-022's rules without any Wardline coupling. - -## References - -- [Clarion v0.1 requirements — REQ-INTEG-WARDLINE-01, NFR-COMPAT-02](../v0.1/requirements.md) (lines 599, 889) — REGISTRY pin and skew behaviour. -- [Clarion v0.1 system design §2 (Direct REGISTRY import), §9 (state-file ingest), §9 (Entity resolve oracle)](../v0.1/system-design.md) — import pattern, ingest paths, HTTP oracle. -- [Clarion v0.1 detailed design §2 (Identity reconciliation across the suite)](../v0.1/detailed-design.md) (lines 553-571) — three-scheme translation table; ingest-path rules. -- [Loom doctrine §5 (v0.1 asterisks), §6 (What Loom is NOT)](../../suite/loom.md) — initialization-coupling asterisk; "no identity reconciliation service" categorical. -- [Panel doctrine synthesis](../../implementation/v0.1-reviews/panel-2026-04-17/11-doctrine-panel-synthesis.md) — the asterisks framing originated here. diff --git a/docs/clarion/adr/ADR-036-wardline-taint-fact-store.md b/docs/clarion/adr/ADR-036-wardline-taint-fact-store.md deleted file mode 100644 index f52a2046..00000000 --- a/docs/clarion/adr/ADR-036-wardline-taint-fact-store.md +++ /dev/null @@ -1,128 +0,0 @@ -# ADR-036: Clarion as Wardline Taint-Fact Store — A Named, Read+Write HTTP Surface - -**Status**: Accepted -**Date**: 2026-05-31 -**Deciders**: qacona@gmail.com -**Context**: Wardline SP9 requested a persistent per-entity taint/provenance store held by Clarion and keyed by Clarion entity (`wardline/docs/integration/2026-05-30-wardline-clarion-taint-store-requirements.md`). The design response that this ADR ratifies is `docs/superpowers/specs/2026-05-30-clarion-wardline-taint-store-design.md`; the outward-facing confirmation to Wardline is `docs/federation/2026-05-30-clarion-wardline-taint-store-response.md`. - -## Summary - -Wardline's `explain_taint` (and the later overlay-scan + N-hop chain work) re-runs taint analysis on every call. SP9 asks to turn those calls into cheap lookups against a persistent **per-entity taint-fact store that Clarion holds**, keyed by Clarion entity: Wardline computes the facts during `wardline scan`, writes them to Clarion, and later reads become graph lookups. - -This ADR records the decision to build that store **inside Clarion, scoped specifically to Wardline** — a dedicated `wardline_taint_facts` table and a set of `/api/wardline/*` routes — and to do so as Clarion's **first read+write use of its HTTP API** (the API is read-only today per ADR-014 and ADR-034). The decision is recorded as an ADR, **not** as a `loom.md` §5 asterisk, because the integration **passes** the federation failure test rather than accepting a violation of it (see §Federation analysis below). - -The load-bearing guard, carried verbatim because it is the one sentence that keeps this decision from becoming a precedent: - -> This is not a precedent for a general-purpose cross-product blob store. The next sibling that wants per-entity persistence gets its own named, justified surface or it does not get one. - -## Context - -### What Wardline asked for - -Wardline's SP9 request (`wardline/docs/integration/2026-05-30-wardline-clarion-taint-store-requirements.md`, referenced relative to the Wardline repo) wants a persistent per-entity taint/provenance store keyed by Clarion entity, so that the SP8 stateless re-run becomes a layered optimization rather than the only path. The seven capabilities requested are: batch qualname→entity resolution, per-entity taint-fact upsert, per-entity fetch (single + batch), a freshness/staleness contract, entity-lifecycle handling, an HTTP transport, and per-project isolation. - -### Where Clarion stands today - -- Clarion's HTTP API is **read-only**. ADR-014 introduced the federation read API; ADR-034 hardened it (HMAC inbound auth, batch resolution, `BRIEFING_BLOCKED`, stable per-project `instance_id`). No write path is exposed over HTTP. -- Writes to the `.clarion/` SQLite DB go through the ADR-011 writer-actor. `clarion analyze` holds one for the duration of a run. `clarion serve` opens the `ReaderPool` for queries and *additionally* spawns an optional MCP query-time writer-actor when an LLM summary provider is configured (`serve.rs`, for the summary/inferred-edge caches). No write path is exposed over HTTP today. -- The schema-reserved `wardline TEXT` column on `entities` is **orthogonal** to this work — it was reserved for the fingerprint/qualname reverse-map (`WardlineMeta`, `detailed-design.md` §7), a different and smaller dataset. It is not the taint store and is left as-is. Crucially, `clarion analyze` rebuilds every `EntityRecord` with `wardline_json: None` and the `entities` UPSERT sets `wardline = excluded.wardline`, so any taint fact stored in that column would be silently wiped on the next re-analyze. A separate table is the only clobber-safe home. - -### Why this needs a decision, not just an implementation - -Two things make this load-bearing rather than routine. First, it flips Clarion's HTTP API from read-only to read+write — a posture change that the security model (ADR-034) and the operator trust model (`docs/operator/clarion-http-read-api.md`) must absorb. Second, the shape "a sibling writes opaque blobs keyed by Clarion entity" is, generalised, exactly the **shared system-of-record** that `loom.md` §6 forbids. The decision is therefore about *boundaries* — what surface exists, what it is named, and what it must never be allowed to become — not merely about a table and four routes. - -## Decision - -### 1. A Wardline-specific, per-entity taint-fact store - -Clarion builds a per-entity taint-fact store **named for and scoped to Wardline**: - -- A dedicated SQLite table, `wardline_taint_facts`, introduced by **migration `0003`** (`crates/clarion-storage/migrations/0003_wardline_taint_facts.sql`; the design spec's "migration 0002" predates `0002_briefing_blocked.sql` and is superseded — `CURRENT_SCHEMA_VERSION` bumps `2 → 3`). The table is keyed by `entity_id` with `ON DELETE CASCADE` against `entities(id)`; it stores `wardline_json` (opaque, verbatim, Wardline-owned), and the queryable observability columns `scan_id`, `content_hash_at_compute`, and `updated_at`. -- A set of `/api/wardline/*` HTTP routes on `clarion serve` (enumerated in Consequences), HMAC-gated per ADR-034 inbound auth. - -The surface is `wardline`-named at every layer — the table, the routes — exactly the naming discipline the ADR-018 asterisk used. There is no generic `sibling_json` column, no `/api/blob/*` route, no capability bus. This structural specificity is what makes the federation guard (§below) enforceable rather than aspirational. - -### 2. The first read+write use of the HTTP API - -This is Clarion's first read+write HTTP surface. Writes go through an **optional** ADR-011 writer-actor that `clarion serve` spawns **only when the write API is config-enabled** — the new config knob **`serve.http.wardline_taint_write`**, which **defaults off**. With the knob off, `serve` retains exactly today's read-only posture (the `ReaderPool` alone) and the write routes reject cleanly. The writer-actor is the same ADR-011 mechanism `clarion analyze` uses; taint writes are query-time writes (the `query_time_write` actor path, like the summary-cache upsert), not analyze-run `BeginRun`/`CommitRun` writes. - -### 3. Resolution: exact-tier direct lookup; Wardline owns normalization - -Writes and reads are **qualname-keyed**. Wardline sends a **pre-composed** dotted qualname; Clarion builds the candidate entity ID `python:function:` and resolves it by **direct existence lookup** against the local catalog. **Clarion does no normalization at resolution time** — Wardline owns the normalization and pre-composes the qualname to byte-match Clarion's `canonical_qualified_name` per `docs/federation/fixtures/wardline-qualname-normalization.json`. The five ADR-018 divergence traps (``, nested-class chains, non-`src` package roots such as `lib.foo`/`app.service`, the `a.src.b` pattern) are therefore **Wardline's** conformance burden against the fixture; on Clarion's side they reduce to **verbatim-storage** correctness (Clarion must not strip, rewrite, or re-canonicalise the composed string). - -Resolution is **exact-tier only for writes**: a write requires an `exact` match; `heuristic`/`none` results are returned in `unresolved_qualnames` and **never written** (a heuristic *write* would silently mis-attach a fact to the wrong entity). Reads may surface a `heuristic` match. The heuristic resolution tier and the conformance oracle over raw file+qualname (`scheme=wardline_qualname`) are **deferred** to Flow B B.2 (`clarion-ca2d26ffbe`), which extends — and must consume, not rebuild — this exact-tier resolver. - -### 4. Concurrency posture (ADR-011) - -In-process, a write-enabled `serve` may run **two** ADR-011 writer-actors against the same DB at once — the optional MCP summary writer (when an LLM provider is configured) and the taint-store writer — each on its own connection. This is a deliberate, bounded relaxation of ADR-011's single-writer-per-process expectation: the two write *streams* are independent (summary/inferred-edge caches vs. Wardline taint facts), and every writer opens its batch with `BEGIN IMMEDIATE` under the same `PRAGMA busy_timeout=5000` + `clarion-storage::retry` capped-backoff layer, so they serialize at the SQLite write lock rather than corrupting. The same mechanism covers **cross-process** contention: a write-enabled `serve` and a concurrent `analyze` are **not expected** to write the same `.clarion/` DB at the same time (an operational expectation, documented rather than enforced beyond the SQLite lock), but if they do, the busy-timeout + retry resolves it. A write that still cannot land after retry **fails as a retryable error**, and Wardline degrades to its SP8 stateless re-run. Per-entity replace is atomic at the row level, so Clarion never corrupts or partially merges two scans for the same entity. - -### 5. The federation guard (load-bearing, verbatim) - -> This is not a precedent for a general-purpose cross-product blob store. The next sibling that wants per-entity persistence gets its own named, justified surface or it does not get one. - -The guard binds future decisions. A subsequent sibling (Shuttle, or a fourth-party tool) requesting per-entity persistence does not inherit this API by extension; it must pass the same `loom.md` §3–§5 analysis on its own terms and earn its own named, justified surface. There is no generic blob bus, and this ADR must not be cited as authority for building one. - -## Federation analysis (`loom.md` §3–§5) — passes; ADR, not asterisk - -The integration is **enrich-only and additive**, and passes the §5 failure test on all three modes: - -- **Solo-useful (both products).** Clarion's briefings, queries, and catalog work with the taint store **empty** — the store is optional enrichment on Clarion's *own* entities, never a precondition for Clarion's semantics. Wardline guarantees (request §6) that its **SP8 stateless re-run is the permanent fallback**: Wardline boots and answers `explain_taint` with Clarion absent, unreachable, write-disabled, or stale. Neither product requires the other to make sense of its own data. -- **Pairwise-composable.** `(Wardline, Clarion)` composes directly — Clarion stores Wardline's facts and serves them back cheaply. No third sibling mediates the pair (no pipeline coupling). -- **No semantic coupling.** `wardline_json` is stored **verbatim and opaque**. Clarion never parses, validates, or depends on its contents; all taint semantics (including the single-successor chain walk) stay Wardline-side. Removing Wardline changes nothing about the meaning of Clarion's own data — an empty or absent store reduces convenience, not coherence. -- **No initialization coupling.** `serve` boots and self-validates whether or not the write knob is set; with it off, the posture is identical to today's read-only `serve`. - -The one real risk is the **"no Loom store" rule** (`loom.md` §6): a *generic* "any sibling writes opaque blobs keyed by entity" API would turn Clarion into the shared system-of-record the doctrine forbids. The guard in §5 of the Decision neutralises that risk by keeping the surface **structurally Wardline-specific** (a `wardline`-named table and `wardline`-scoped routes), not a general `sibling_json` bus. - -Because this integration **passes** the failure test — rather than accepting a violation with a written retirement condition — it is recorded as a **new ADR, not a new `loom.md` §5 asterisk**. Per `loom.md` §5, an asterisk is the instrument for an *accepted, temporarily-tolerated violation* of one named failure-test mode, carrying a retirement condition and an honest statement of which mode is violated. This decision violates no mode, has nothing to retire, and so is the wrong shape for an asterisk. It is a clean federation surface, recorded as a locked architectural decision. - -## Consequences - -### Positive - -- Wardline's `explain_taint` becomes a cheap per-entity lookup instead of a re-analysis, with the SP8 re-run preserved as a permanent standalone fallback. The optimization is layered, never load-bearing. -- The store is clobber-safe by construction: a dedicated `wardline_taint_facts` table is never touched by the `entities` UPSERT, so re-analyze does not wipe taint facts the way the schema-reserved `wardline` column would. -- `scan_id` and `content_hash_at_compute` are real columns (not parsed out of the opaque blob), giving observability and an optional future prune-by-scan without ever requiring Clarion to read `wardline_json`. -- The federation boundary is structural, not merely documented: the `wardline`-named table and routes make "Wardline-specific, not a generic blob bus" a property of the schema, enforceable at review time against the §5 guard. - -### Negative / costs - -- Clarion's HTTP API gains a write posture for the first time, widening the security surface that ADR-034's HMAC auth and the operator trust model must cover. Mitigation: the write path is **off by default** (`serve.http.wardline_taint_write = false`) and HMAC-gated; with the knob off, `serve` is byte-for-byte today's read-only posture. -- `serve` gains an optional ADR-011 writer-actor, adding both an **in-process** contention surface (it coexists with the optional MCP summary writer — two writer-actors on one DB, §4) and the **cross-process** surface (vs. a concurrent `analyze`). Mitigation: every writer uses `BEGIN IMMEDIATE` + `PRAGMA busy_timeout=5000` + the `clarion-storage::retry` capped-backoff layer; a write that cannot land fails retryably and Wardline degrades to SP8 (enrich-only, never corruption). - -### What ships (artifact inventory) - -- **Migration `0003`** — `crates/clarion-storage/migrations/0003_wardline_taint_facts.sql`; the `wardline_taint_facts` table; `CURRENT_SCHEMA_VERSION` bumps `2 → 3`. -- **Routes** (HMAC-gated, on `clarion serve`): - - `POST /api/wardline/resolve` — batch qualname→entity resolution (exact-tier; pre-composed `python:function:` direct lookup). - - `POST /api/wardline/taint-facts` — batch upsert (per-entity replace), qualname-keyed, exact-only, returning `{written, unresolved_qualnames}`, `project`-guarded. - - `GET /api/wardline/taint-facts` — single fetch by qualname; returns blob + `current_content_hash` + `exists`. - - `POST /api/wardline/taint-facts:batch-get` — batch fetch; one round-trip; blob + `current_content_hash` + `exists` per entity. -- **Config knob** — `serve.http.wardline_taint_write` (boolean, default `false`); gates whether `serve` spawns the optional writer-actor and exposes the write/resolve routes. - -### What is deferred (not in this surface) - -- The **heuristic resolution tier** and the **conformance oracle** (`scheme=wardline_qualname` over raw file+qualname) remain deferred to **Flow B B.2** (`clarion-ca2d26ffbe`). The shipping resolve route here is exact-tier only; B.2 extends it and must consume this resolver rather than rebuild it. -- No general-purpose blob store, no Clarion parsing of `wardline_json`, no replacement of the SP8 re-run, no mandatory lifecycle cascade/prune machinery beyond the freshness gate, and no ADR-029 issue↔entity bindings — see the design spec §9 for the full non-goal list. - -## Loom vocabulary verdict - -Per `docs/clarion/adr/README.md` ("ADR acceptance criteria — Loom vocabulary discipline") and `loom.md` §8, this ADR introduces cross-product-visible field names that cross the Wardline↔Clarion wire in the SP9 contract: `wardline_json`, `scan_id`, `content_hash_at_compute`, `current_content_hash`, and `unresolved_qualnames`. Verdict: **`no clash`** — each term is either Wardline-namespaced or local to this Clarion surface, and none collides with an existing sibling term. `content_hash` semantics follow Clarion's existing definition (whole-file `blake3`, hex; per the design spec §3), so the hash-related fields reuse rather than redefine vocabulary. These entries are **recorded in [`docs/suite/glossary.md`](../../suite/glossary.md) ("SP9 Wardline taint-store wire terms") as part of this ADR's acceptance evidence**, per the ADR-acceptance rule. The Clarion-internal names `wardline_taint_facts` (table) and `serve.http.wardline_taint_write` (config) are deliberately excluded from the glossary — they never cross the wire to Wardline. - -## Related Decisions - -- [ADR-011](./ADR-011-writer-actor-concurrency.md) — Writer-actor concurrency. The taint store's optional `serve`-side writer is the same mechanism; the concurrency posture (§4) rests on ADR-011's `busy_timeout` + capped-backoff retry. -- [ADR-014](./ADR-014-filigree-registry-backend.md) — The federation HTTP read API this ADR extends from read-only to read+write. -- [ADR-018](./ADR-018-identity-reconciliation.md) — Identity reconciliation; Wardline owns its qualnames and pre-composes them to Clarion's canonical form. The divergence traps are Wardline's conformance burden against the normalization fixture; Clarion's side is verbatim storage. -- [ADR-029](./ADR-029-entity-associations-binding.md) — Entity-association binding; adjacent and explicitly out of scope (request §9). Not required by, and does not require, this surface. -- [ADR-034](./ADR-034-federation-http-read-api-hardening.md) — HTTP read-API hardening; the HMAC inbound auth and `project`/instance posture the `/api/wardline/*` routes inherit. - -## References - -- Design spec (federation verdict §2; seven decisions §3): [`docs/superpowers/specs/2026-05-30-clarion-wardline-taint-store-design.md`](../../superpowers/specs/2026-05-30-clarion-wardline-taint-store-design.md). -- Outward contract response: [`docs/federation/2026-05-30-clarion-wardline-taint-store-response.md`](../../federation/2026-05-30-clarion-wardline-taint-store-response.md). -- Implementation plan: [`docs/superpowers/plans/2026-05-31-clarion-wardline-taint-store.md`](../../superpowers/plans/2026-05-31-clarion-wardline-taint-store.md). -- Wardline request: `wardline/docs/integration/2026-05-30-wardline-clarion-taint-store-requirements.md` (Wardline repo). -- Qualname parity fixture: [`docs/federation/fixtures/wardline-qualname-normalization.json`](../../federation/fixtures/wardline-qualname-normalization.json). -- Federation doctrine: [`docs/suite/loom.md`](../../suite/loom.md) §3–§6. - -— End of ADR-036 — diff --git a/docs/federation/2026-05-30-clarion-wardline-taint-store-response.md b/docs/federation/2026-05-30-loomweave-wardline-taint-store-response.md similarity index 56% rename from docs/federation/2026-05-30-clarion-wardline-taint-store-response.md rename to docs/federation/2026-05-30-loomweave-wardline-taint-store-response.md index 12fa3089..e346ef69 100644 --- a/docs/federation/2026-05-30-clarion-wardline-taint-store-response.md +++ b/docs/federation/2026-05-30-loomweave-wardline-taint-store-response.md @@ -1,40 +1,40 @@ -# Clarion → Wardline: Taint-Store Contract Response (SP9) +# Loomweave → Wardline: Taint-Store Contract Response (SP9) -**From:** Clarion maintainers +**From:** Loomweave maintainers **To:** Wardline maintainer **Date:** 2026-05-30 -**Re:** `wardline/docs/integration/2026-05-30-wardline-clarion-taint-store-requirements.md` -**Status:** Clarion confirms the contract. Decisions answered below; Clarion-side +**Re:** `wardline/docs/integration/2026-05-30-wardline-loomweave-taint-store-requirements.md` +**Status:** Loomweave confirms the contract. Decisions answered below; Loomweave-side build is sequenced under `release:1.1`. Wardline may begin its SP9 spec against this response. -**Full design:** `docs/superpowers/specs/2026-05-30-clarion-wardline-taint-store-design.md`. +**Full design:** `docs/superpowers/specs/2026-05-30-loomweave-wardline-taint-store-design.md`. --- ## Verdict -**Confirmed — Clarion will build the per-entity taint store.** The ask passes the -Loom federation failure test (`loom.md` §3–§5): enrich-only, both products stay -solo-useful (Clarion queries work with the store empty; your SP8 re-run is the -permanent fallback), and the blob stays opaque to Clarion. We recorded the -decision as a Clarion **ADR**, not a `loom.md` asterisk, with one explicit guard: +**Confirmed — Loomweave will build the per-entity taint store.** The ask passes the +Weft federation failure test (`weft.md` §3–§5): enrich-only, both products stay +solo-useful (Loomweave queries work with the store empty; your SP8 re-run is the +permanent fallback), and the blob stays opaque to Loomweave. We recorded the +decision as a Loomweave **ADR**, not a `weft.md` asterisk, with one explicit guard: the surface is **Wardline-specific** (a `wardline`-named table + `wardline`-scoped routes), **not** a general-purpose cross-product blob store. The next sibling that wants per-entity persistence gets its own named, justified surface. ## Answers to your seven decisions -| # | Your ask | Clarion's answer | +| # | Your ask | Loomweave's answer | |---|---|---| -| 1 | Key by `EntityId` or qualname? | **Qualname-keyed**, as you preferred. Clarion resolves internally. **Writes require an `exact` resolution** — `heuristic`/`none` come back in `unresolved_qualnames` and are *not* written (a heuristic write would mis-attach a fact). Reads may surface `heuristic`. | +| 1 | Key by `EntityId` or qualname? | **Qualname-keyed**, as you preferred. Loomweave resolves internally. **Writes require an `exact` resolution** — `heuristic`/`none` come back in `unresolved_qualnames` and are *not* written (a heuristic write would mis-attach a fact). Reads may surface `heuristic`. | | 2 | Store `scan_id`/generation? | **Yes**, as a real column (for observability + an optional future prune-by-scan). Correctness rests on the freshness gate + per-entity replace, exactly as you proposed. | -| 3 | **Content-hash definition + per-entity exposure** | **Adopt Clarion's: `blake3` of the containing file's raw bytes, hex, whole-file.** *Not* sha256, *not* LF-normalized. It is file-granular (matches your "re-stale the whole file" intent) and is returned per entity on fetch as `current_content_hash`. Please pin `blake3`/raw-bytes/whole-file as the single source of truth on the Wardline side. | -| 4 | Cascade vs per-scan prune? | **Neither is needed.** Clarion's lifecycle is **wipe-and-rerun**; there is no incremental delete to cascade off. The **freshness gate is the safety net**: deleted / renamed / edited entities all surface to you as hash-mismatch or `exists:false` → you recompute. No cascade, no mandatory prune. (We can add `prune-by-scan` later via the `scan_id` column if it earns its keep.) | -| 5 | HTTP+JSON or local-only? | **HTTP+JSON**, stdlib-`urllib`-callable, on `clarion serve`, with a `--clarion-url` analog. Auth is HMAC (`X-Loom-Component`, ADR-034), the same posture as your Filigree emitter's bearer path. Note: this makes Clarion's HTTP API read+write for the first time — covered by the ADR. | -| 6 | Per-project isolation + `project` handle | **Confirmed.** One `clarion serve` = one project (`.clarion/` under the project root). The `project` field is accepted as a **guard** (must match the served project) rather than a selector. | -| 7 | Timeline | Sequenced below; tracked in Clarion's `release:1.1`. | +| 3 | **Content-hash definition + per-entity exposure** | **Adopt Loomweave's: `blake3` of the containing file's raw bytes, hex, whole-file.** *Not* sha256, *not* LF-normalized. It is file-granular (matches your "re-stale the whole file" intent) and is returned per entity on fetch as `current_content_hash`. Please pin `blake3`/raw-bytes/whole-file as the single source of truth on the Wardline side. | +| 4 | Cascade vs per-scan prune? | **Neither is needed.** Loomweave's lifecycle is **wipe-and-rerun**; there is no incremental delete to cascade off. The **freshness gate is the safety net**: deleted / renamed / edited entities all surface to you as hash-mismatch or `exists:false` → you recompute. No cascade, no mandatory prune. (We can add `prune-by-scan` later via the `scan_id` column if it earns its keep.) | +| 5 | HTTP+JSON or local-only? | **HTTP+JSON**, stdlib-`urllib`-callable, on `loomweave serve`, with a `--loomweave-url` analog. Auth is HMAC (`X-Weft-Component`, ADR-034), the same posture as your Filigree emitter's bearer path. Note: this makes Loomweave's HTTP API read+write for the first time — covered by the ADR. | +| 6 | Per-project isolation + `project` handle | **Confirmed.** One `loomweave serve` = one project (`.loomweave/` under the project root). The `project` field is accepted as a **guard** (must match the served project) rather than a selector. | +| 7 | Timeline | Sequenced below; tracked in Loomweave's `release:1.1`. | -## What Clarion commits to build (sequence) +## What Loomweave commits to build (sequence) 1. **ADR** — the federation decision + the read+write shift + the not-a-blob-store guard. 2. **Storage** — migration `0002`, a dedicated `wardline_taint_facts` table @@ -45,27 +45,27 @@ wants per-entity persistence gets its own named, justified surface. 4. **Read endpoints** — single + `:batch-get`, returning `wardline_json` verbatim + `current_content_hash` + `exists`. 5. **Resolve oracle** — `GET /api/v1/entities/resolve?scheme=wardline_qualname` (your - §A). This is a pre-designed, previously-deferred Clarion feature; it ships as its + §A). This is a pre-designed, previously-deferred Loomweave feature; it ships as its own unit. 6. **Contract pin** — all new routes + the freshness contract pinned in `docs/federation/contracts.md`. ## One thing to note on your proposed shapes -Your §B/§C/§E penciled in `wardline_json` living in Clarion's schema-reserved -`wardline` column. Clarion is instead using a **dedicated `wardline_taint_facts` -table** — because `clarion analyze` re-UPSERTs the `entities` row (writing +Your §B/§C/§E penciled in `wardline_json` living in Loomweave's schema-reserved +`wardline` column. Loomweave is instead using a **dedicated `wardline_taint_facts` +table** — because `loomweave analyze` re-UPSERTs the `entities` row (writing `wardline = NULL`) and would clobber facts stored in that column on every re-analyze. The dedicated table is untouched by analyze, keeps ownership clean, and makes `scan_id`/hash queryable without parsing your blob. No change to your wire -shapes — the column-vs-table choice is entirely Clarion-internal. +shapes — the column-vs-table choice is entirely Loomweave-internal. ## Reciprocal: what we're relying on from you (your §6) SP8 stateless re-run stays the permanent fallback; `wardline_json` stays opaque/versioned behind `schema_version`; writes idempotent / per-entity replace; qualname conformance per Round 1 + the shared corpus; Wardline owns the fresh/stale -decision (Clarion supplies `current_content_hash`, you decide). +decision (Loomweave supplies `current_content_hash`, you decide). Route-backs welcome on any shape — the capability is the contract. @@ -83,7 +83,7 @@ force write-ordering + a one-shot backfill into the coordinated cutover). index) to `wardline_taint_facts`. Facts stay locator-keyed; `sei` is a *second*, rename-stable lookup key. - **Write:** `POST /api/wardline/taint-facts` accepts an optional opaque `sei` per - fact. Omitted ⇒ Clarion resolves it from the alive `sei_bindings` row for the + fact. Omitted ⇒ Loomweave resolves it from the alive `sei_bindings` row for the resolved locator (batched). Supplied ⇒ stored verbatim. Pre-SEI/unbound ⇒ `null` (locator-keyed only). You already hold the SEI from `SeiResolver`, so sending it is the fast path. @@ -92,11 +92,11 @@ force write-ordering + a one-shot backfill into the coordinated cutover). under, with the same live whole-file `current_content_hash`. HMAC-gated; SEIs opaque (no locator-shape validation). - **Detection:** gate on the discrete `taint_store.read_by_sei` capability flag, - **not** `sei.supported` (an older SEI-capable Clarion lacks this route). Fall + **not** `sei.supported` (an older SEI-capable Loomweave lacks this route). Fall back to the locator-keyed read when absent. - **Named window:** a fact written *before* migration `0006` (`sei = null`) whose entity is renamed *before* its next re-scan is reachable by neither key until you recompute — the freshness gate surfaces it as stale and the rewrite populates the - `sei`. Self-healing; Clarion does not backfill historical rows. + `sei`. Self-healing; Loomweave does not backfill historical rows. Full shapes pinned in [`contracts.md`](./contracts.md#post-apiwardlinetaint-factsby-sei-read-batch-by-sei). diff --git a/docs/federation/2026-05-30-prune-unseen-filigree-request.md b/docs/federation/2026-05-30-prune-unseen-filigree-request.md index 67a77fc6..156a296c 100644 --- a/docs/federation/2026-05-30-prune-unseen-filigree-request.md +++ b/docs/federation/2026-05-30-prune-unseen-filigree-request.md @@ -2,13 +2,13 @@ > **⚠️ PRUNE ASK WITHDRAWN / SUPERSEDED (2026-05-30).** The prune surface this > memo requested **already exists** in Filigree: -> `POST /api/loom/findings/clean-stale` with body -> `{"scan_source": "clarion", "older_than_days": 30, "actor": "…"}` → +> `POST /api/weft/findings/clean-stale` with body +> `{"scan_source": "loomweave", "older_than_days": 30, "actor": "…"}` → > `{"findings_fixed": N, "scan_source": "…", "older_than_days": N}`. It > soft-archives `unseen_in_latest` findings to `fixed` (auto-reopen on > reappearance; Filigree ADR-015), `scan_source` required server-side as an > accident-guard. Verified against Filigree's handler -> (`src/filigree/dashboard_routes/files.py`) and its API tests. Clarion's +> (`src/filigree/dashboard_routes/files.py`) and its API tests. Loomweave's > `--prune-unseen` now consumes it directly (REQ-FINDING-06 done); the contract > is pinned in `docs/federation/contracts.md` → "Consumed Filigree route: > clean-stale retention". My §1–§3 and §5 below (asking Filigree to *design and @@ -17,14 +17,14 @@ > only live ask** and remains open. **Status:** Prune ask withdrawn; §4 (scan-run-create) open (2026-05-30) -**Author side:** Clarion -**Tracking issue (Clarion):** §4 tracked under a `release:1.1` issue (Phase-0 scan-run-create handshake); the prune/`--prune-unseen` piece of `clarion-dd29e69e0e` is done. +**Author side:** Loomweave +**Tracking issue (Loomweave):** §4 tracked under a `release:1.1` issue (Phase-0 scan-run-create handshake); the prune/`--prune-unseen` piece of `clarion-dd29e69e0e` is done. **Sibling docs:** -- Clarion `docs/federation/contracts.md` → "Consumed Filigree route: clean-stale retention" (the route Clarion now consumes) and "…: scan-results intake". -- Clarion `docs/clarion/1.0/requirements.md` → REQ-FINDING-05, REQ-FINDING-06. +- Loomweave `docs/federation/contracts.md` → "Consumed Filigree route: clean-stale retention" (the route Loomweave now consumes) and "…: scan-results intake". +- Loomweave `docs/loomweave/1.0/requirements.md` → REQ-FINDING-05, REQ-FINDING-06. - Filigree intake handler for reference: `db.process_scan_results` (`db_files.py:857-926`, per ADR-014). -> **Note on placement.** This is a Clarion-authored *request to* Filigree, kept here for Clarion's reference. The authoritative Filigree-side artifact (design + implementation) should live in the Filigree repo; refresh `docs/federation/filigree-side/` with a mirror once Filigree drafts its response. +> **Note on placement.** This is a Loomweave-authored *request to* Filigree, kept here for Loomweave's reference. The authoritative Filigree-side artifact (design + implementation) should live in the Filigree repo; refresh `docs/federation/filigree-side/` with a mirror once Filigree drafts its response. --- @@ -34,26 +34,26 @@ ## 1. Problem in one paragraph -Clarion emits findings into Filigree via `POST /api/v1/scan-results` with -`scan_source: "clarion"`, `mark_unseen: true`, `complete_scan_run: true`. When +Loomweave emits findings into Filigree via `POST /api/v1/scan-results` with +`scan_source: "loomweave"`, `mark_unseen: true`, `complete_scan_run: true`. When `mark_unseen` is set, Filigree transitions old-position findings for the same rule/file that weren't in the latest scan to `unseen_in_latest`. Over repeated -runs these accumulate. Clarion's REQ-FINDING-06 wants `clarion analyze +runs these accumulate. Loomweave's REQ-FINDING-06 wants `loomweave analyze --prune-unseen` to "remove `unseen_in_latest` findings older than 30 days -(configurable)." Clarion has **no way to do this** — there is no -prune/delete/retention route on Filigree's side (confirmed: nothing in Clarion's +(configurable)." Loomweave has **no way to do this** — there is no +prune/delete/retention route on Filigree's side (confirmed: nothing in Loomweave's `FiligreeHttpClient`, in the pinned `docs/federation/contracts.md`, or in the -Filigree MCP/HTTP tool surface). It is a server-side retention operation Clarion +Filigree MCP/HTTP tool surface). It is a server-side retention operation Loomweave cannot implement alone. -## 2. Federation constraint (load-bearing — `loom.md` §3–§5) +## 2. Federation constraint (load-bearing — `weft.md` §3–§5) - **Enrich-only.** Filigree's finding lifecycle must remain fully correct if - Clarion *never* calls prune. Prune is an optional retention convenience, never - a required step. Introduce no coupling where Filigree depends on Clarion + Loomweave *never* calls prune. Prune is an optional retention convenience, never + a required step. Introduce no coupling where Filigree depends on Loomweave calling it. -- **`scan_source`-scoped.** The operation must be scoped so Clarion can only - prune its own (`clarion`) findings and can never affect Wardline's or any +- **`scan_source`-scoped.** The operation must be scoped so Loomweave can only + prune its own (`loomweave`) findings and can never affect Wardline's or any other tool's findings. ## 3. Primary ask — design and implement a prune surface @@ -62,8 +62,8 @@ Resolve these and implement: 1. **Surface shape.** Pick one and justify: - (a) a dedicated route, e.g. `POST /api/v1/findings:prune` with body - `{ "scan_source": "clarion", "unseen_older_than_days": 30 }`; - - (b) `DELETE /api/v1/findings?scan_source=clarion&unseen_in_latest=true&older_than_days=30`; + `{ "scan_source": "loomweave", "unseen_older_than_days": 30 }`; + - (b) `DELETE /api/v1/findings?scan_source=loomweave&unseen_in_latest=true&older_than_days=30`; - (c) a field on the existing scan-results intake (e.g. `prune_unseen_older_than_days`) so prune piggybacks on the completing POST. @@ -71,39 +71,39 @@ Resolve these and implement: separations. Your call. 2. **Semantics — delete vs. archive.** REQ-FINDING-06 says "removes," but you own findings lifecycle: decide whether prune hard-deletes or soft-archives / - dismisses (audit-preserving). Clarion only needs *a* retention trigger; the + dismisses (audit-preserving). Loomweave only needs *a* retention trigger; the durability/audit policy is yours. 3. **"Age" definition.** What timestamp gates the 30-day threshold — when the finding first transitioned to `unseen_in_latest`, or `updated_at` / last-seen? If there is no "became-unseen-at" timestamp today, that may need adding. 4. **Response shape.** Return counts (e.g. - `{ "findings_pruned": N, "scan_source": "clarion" }`) so Clarion can log the + `{ "findings_pruned": N, "scan_source": "loomweave" }`) so Loomweave can log the outcome in `stats.json`. 5. **Auth.** Same posture as `/api/v1/scan-results`: `Authorization: Bearer ` + `x-filigree-actor: ` headers. -## 4. Secondary ask — scan-run-create contract decision (Clarion REQ-FINDING-05) — RESOLVED 2026-05-31 +## 4. Secondary ask — scan-run-create contract decision (Loomweave REQ-FINDING-05) — RESOLVED 2026-05-31 > **Resolved (2026-05-31): path (a) — tolerate-unknown is Filigree's permanent > contract.** Filigree confirmed that accepting findings with a client-supplied > `scan_run_id` it has never seen, ingesting them, and reconstructing the run in > history is a stable, supported contract — not transitional leniency. There is > no `POST /api/.../scan-runs` create endpoint (only read-only -> `GET /api/scan-runs` history), and none is planned. Clarion adds **no Phase-0 +> `GET /api/scan-runs` history), and none is planned. Loomweave adds **no Phase-0 > handshake**. The decision is pinned in Filigree's `contracts.md` §F6 and > defended by `tests/api/test_files_api.py::TestUnknownScanRunIdContract`; the -> Clarion-side caveats are dropped from `docs/federation/contracts.md` +> Loomweave-side caveats are dropped from `docs/federation/contracts.md` > (scan-results intake) and `requirements.md` REQ-FINDING-05, which now record > the three intake obligations (globally-unique UUIDv4 `run_id`, stable > `scan_source`, benign-completion-warning handling). Tracked by > clarion-694aab920a (closed). Original question retained below for context. -Clarion currently POSTs findings with a `scan_run_id` that Filigree has never -seen; Filigree tolerates the unknown id, emits a warning, and proceeds. Clarion +Loomweave currently POSTs findings with a `scan_run_id` that Filigree has never +seen; Filigree tolerates the unknown id, emits a warning, and proceeds. Loomweave is deciding whether to add a Phase-0 "create the scan run first" handshake. **Question for Filigree:** is "tolerate unknown `scan_run_id`, warn, proceed" the -intended *permanent* contract (Clarion will then document that and stop treating +intended *permanent* contract (Loomweave will then document that and stop treating the warning as a gap), or do you want an explicit create endpoint (e.g. `POST /api/v1/scan-runs` accepting a client-supplied id, or returning a server-assigned one)? Answer this; only build the endpoint if you want it. @@ -111,9 +111,9 @@ server-assigned one)? Answer this; only build the endpoint if you want it. ## 5. Deliverables / acceptance criteria - The chosen prune surface implemented, with `scan_source` scoping enforced - (a test proving a `clarion`-scoped prune leaves `wardline` findings untouched). + (a test proving a `loomweave`-scoped prune leaves `wardline` findings untouched). - The wire request/response shape documented on Filigree's side **and** a - normative fixture, so Clarion can mirror it in `docs/federation/contracts.md` + normative fixture, so Loomweave can mirror it in `docs/federation/contracts.md` (matching how the scan-results intake and the `/api/v1/files` family are pinned there). - A test that prune is enrich-only: deleting/archiving via prune doesn't corrupt diff --git a/docs/federation/contracts.md b/docs/federation/contracts.md index df1ff2db..90d4a039 100644 --- a/docs/federation/contracts.md +++ b/docs/federation/contracts.md @@ -1,32 +1,32 @@ -# Clarion Federation Contracts +# Loomweave Federation Contracts -This file pins Clarion's federation contracts in both directions: the surface -Clarion *exposes* to sibling products, and the conventions and routes Clarion +This file pins Loomweave's federation contracts in both directions: the surface +Loomweave *exposes* to sibling products, and the conventions and routes Loomweave *consumes* from Filigree. The exposed surface was historically read-only — the -file-resolution read API consumed by Filigree's `ClarionRegistry` (ADR-014). At +file-resolution read API consumed by Filigree's `LoomweaveRegistry` (ADR-014). At release:1.1 it also includes one **write** surface: the Wardline taint-fact store (ADR-036), a disabled-by-default `/api/wardline/*` sub-router that lets Wardline -persist per-entity taint facts into Clarion's catalog so briefings can carry them. -That write surface is enrich-only in the loom.md §5 sense — it is off unless -explicitly enabled, Clarion never requires Wardline to be present, and Clarion's +persist per-entity taint facts into Loomweave's catalog so briefings can carry them. +That write surface is enrich-only in the weft.md §5 sense — it is off unless +explicitly enabled, Loomweave never requires Wardline to be present, and Loomweave's own semantics never depend on a taint fact existing. Every consume-side coupling -here is likewise enrich-only and fail-soft — Clarion stays solo-useful when -Filigree is absent (loom.md §5). +here is likewise enrich-only and fail-soft — Loomweave stays solo-useful when +Filigree is absent (weft.md §5). > **Federation-pattern sources (pointers).** The federation axiom and composition -> law cited as `loom.md §5` throughout this document are now authoritative at the +> law cited as `weft.md §5` throughout this document are now authoritative at the > federation hub: `~/loom/doctrine.md` §5 -> (as of 2026-06-05; `loom.md` is now a pointer stub that preserves the original -> section numbers, so `loom.md §5` resolves there). The cross-product **contract +> (as of 2026-06-05; `weft.md` is now a pointer stub that preserves the original +> section numbers, so `weft.md §5` resolves there). The cross-product **contract > index** — the suite-level list of every live cross-product contract and its > owning authority — lives at > `~/loom/contracts-index.md`. The -> endpoint specs below are **Clarion-owned and authoritative**; the hub indexes +> endpoint specs below are **Loomweave-owned and authoritative**; the hub indexes > them, it does not restate them. ## HTTP Read API -`clarion serve` can expose the HTTP read API when enabled in `clarion.yaml`: +`loomweave serve` can expose the HTTP read API when enabled in `loomweave.yaml`: ```yaml serve: @@ -34,26 +34,26 @@ serve: enabled: true bind: 127.0.0.1:9111 # Preferred 1.0 identity mode. Optional on loopback, required for - # authenticated Loom component requests. - identity_token_env: CLARION_LOOM_IDENTITY_SECRET + # authenticated Weft component requests. + identity_token_env: WEFT_IDENTITY_SECRET # Name of the env var holding the inbound bearer token. Optional on a # loopback bind, accepted for compatibility on non-loopback binds. Default - # `CLARION_LOOM_TOKEN` matches Filigree's pinned client default. - token_env: CLARION_LOOM_TOKEN + # `WEFT_TOKEN` matches Filigree's pinned client default. + token_env: WEFT_TOKEN ``` The MCP stdio server remains available on stdin/stdout. The `/api/v1/*` read API -is read-only and uses Clarion's existing SQLite reader pool. The `/api/wardline/*` +is read-only and uses Loomweave's existing SQLite reader pool. The `/api/wardline/*` sub-router (see [Wardline taint-fact store](#wardline-taint-fact-store-sp9)) adds one write path — `POST /api/wardline/taint-facts` — which is disabled by default -and, when enabled, routes through Clarion's writer-actor rather than the reader +and, when enabled, routes through Loomweave's writer-actor rather than the reader pool; its read paths still use the reader pool. ### Authentication The `/api/v1/files`-family endpoints require -`X-Loom-Component: clarion:`, `X-Loom-Timestamp: `, and -`X-Loom-Nonce: ` when Clarion has resolved +`X-Weft-Component: loomweave:`, `X-Weft-Timestamp: `, and +`X-Weft-Nonce: ` when Loomweave has resolved `serve.http.identity_token_env` at startup. The HMAC is lowercase hex HMAC-SHA256 over the canonical message: @@ -61,19 +61,19 @@ HMAC-SHA256 over the canonical message: - - + + ``` -Clarion accepts timestamps within a five-minute skew window and rejects nonce -reuse inside that same process-local window. Nonces are scoped to one Clarion +Loomweave accepts timestamps within a five-minute skew window and rejects nonce +reuse inside that same process-local window. Nonces are scoped to one Loomweave server process and one shared secret; clients should use high-entropy unique nonces for every signed request. Replays, stale timestamps, missing freshness headers, malformed freshness headers, and wrong signatures all return the same `401 UNAUTHENTICATED` envelope. `/api/v1/_capabilities` is **always** unauthenticated so siblings can probe the -API surface pre-auth. Clarion still accepts the older +API surface pre-auth. Loomweave still accepts the older `Authorization: Bearer ` path when `token_env` resolves and `identity_token_env` is not configured. @@ -84,10 +84,10 @@ startup, before binding): |---|---|---|---| | Loopback | unset | unset | Unauthenticated; allow all requests. | | Loopback | set | any | HMAC required on protected routes; capabilities always allowed. | -| Loopback | configured but env missing | any | **Refuse to start** with `CLA-CONFIG-HTTP-IDENTITY-MISSING`. | +| Loopback | configured but env missing | any | **Refuse to start** with `LMWV-CONFIG-HTTP-IDENTITY-MISSING`. | | Non-loopback | set | any | HMAC required on protected routes. | | Non-loopback | unset | set | Bearer required on protected routes. | -| Non-loopback | unset | unset | **Refuse to start** with `CLA-CONFIG-HTTP-NO-AUTH`. | +| Non-loopback | unset | unset | **Refuse to start** with `LMWV-CONFIG-HTTP-NO-AUTH`. | Authentication rejection (header absent, wrong scheme/prefix, wrong token or signature, blank token or signature, stale timestamp, or reused nonce) returns: @@ -108,7 +108,7 @@ All non-2xx responses use this closed JSON error envelope: ```json { - "error": "path does not resolve to a Clarion file entity", + "error": "path does not resolve to a Loomweave file entity", "code": "NOT_FOUND" } ``` @@ -124,21 +124,21 @@ is emitted by `POST /api/v1/files/batch` (as `400`) and by the `/api/wardline/*` batch routes (as `413`) — the same `code` carries a **different HTTP status by endpoint**, so a client must route on `code`, not on status. -> The `code` enum is defined canonically in Rust as `clarion_core::errors::HttpErrorCode` +> The `code` enum is defined canonically in Rust as `loomweave_core::errors::HttpErrorCode` > (single source of truth shared with the MCP tool-error vocabulary; see ADR-037). > The wire spelling on this surface is unchanged. ### `GET /api/v1/files?path=&language=` -Resolves a project-relative or absolute file path to the Clarion file identity -Filigree stores as `file_records.id` when `registry_backend: clarion` is active. +Resolves a project-relative or absolute file path to the Loomweave file identity +Filigree stores as `file_records.id` when `registry_backend: loomweave` is active. Query parameters: | Name | Required | Meaning | |---|---:|---| -| `path` | yes | File path under the Clarion project root. | -| `language` | no | Caller-supplied language hint. If absent, Clarion infers from the source entity or file extension. | +| `path` | yes | File path under the Loomweave project root. | +| `language` | no | Caller-supplied language hint. If absent, Loomweave infers from the source entity or file extension. | Successful response: @@ -156,17 +156,17 @@ Semantics: - `entity_id` is opaque to Filigree and follows ADR-003's file-kind shape `core:file:{qualified_name}`. - `content_hash` is the drift signal Filigree stores with the resolved row. -- `canonical_path` is Clarion's project-relative canonical path: no leading +- `canonical_path` is Loomweave's project-relative canonical path: no leading `/`, no leading `./`, no trailing `/`, and `/` as the separator on every platform. -- `language` is the normalized language value Clarion used for the resolution. +- `language` is the normalized language value Loomweave used for the resolution. - Unknown or outside-project paths return a non-2xx JSON error instead of guessing. -- If no file-kind entity row exists for the path, Clarion returns +- If no file-kind entity row exists for the path, Loomweave returns `404` with `code: "NOT_FOUND"` instead of synthesizing a file ID. - If the file-kind entity row carries a `briefing_blocked` property (set by the pre-ingest secret scanner or the unscanned-source defense-in- - depth path), Clarion returns `403` with + depth path), Loomweave returns `403` with `code: "BRIEFING_BLOCKED"` and the response body `{"error": "entity is briefing-blocked and cannot be exposed", "code": "BRIEFING_BLOCKED"}`. The response does not include the @@ -220,7 +220,7 @@ Successful response (`200 OK`): "response": { "status": "not_found", "body": { - "error": "file is not known to Clarion", + "error": "file is not known to Loomweave", "code": "NOT_FOUND" } } @@ -257,7 +257,7 @@ is normative for this section. ### `POST /api/v1/files/batch` Resolves up to **256** file paths in a single request. Filigree's -`ClarionRegistry` uses this for cold-start hydration so that one rehydration +`LoomweaveRegistry` uses this for cold-start hydration so that one rehydration costs one round-trip and one pooled-connection checkout, rather than N of each. Request body (`application/json`, max 16 KiB): @@ -333,7 +333,7 @@ is normative for this section. ## Call-graph linkages Structural call-graph adjacency over HTTP (Wave 0 / WS2). These routes wrap -Clarion's stored `calls` edges so a sibling (notably the dossier assembler) can +Loomweave's stored `calls` edges so a sibling (notably the dossier assembler) can fetch a function's callers and callees without an MCP session. They are thin read wrappers — no inference is run at request time (see the confidence note). @@ -344,7 +344,7 @@ by `linkages.http: true` in `/api/v1/_capabilities`. ### `GET /api/v1/entities/:entity_id/callers` and `…/callees` `callers` returns the entities that call `entity_id`; `callees` returns the -entities `entity_id` calls. `:entity_id` is a full Clarion entity id +entities `entity_id` calls. `:entity_id` is a full Loomweave entity id (`{plugin}:{kind}:{qualname}`, e.g. `python:function:auth.tokens.refresh`) in the path segment; percent-encode any reserved characters. In practice the call-graph subject is a function/method (dotted qualnames carry no `/`). @@ -456,7 +456,7 @@ Envelope-level failures: ### `GET /api/v1/_capabilities` -Reports whether this Clarion instance can serve the registry-backend read +Reports whether this Loomweave instance can serve the registry-backend read contract. Successful response: @@ -487,8 +487,8 @@ Filigree should treat `registry_backend: true` as the flag that the (see [§SEI identity resolution](#sei-identity-resolution)); `sei.version` is the SEI wire/format version (`1`). A consumer **must** gate its use of SEIs on this flag and **degrade gracefully** when it is absent or `supported: false` — that -is a pre-SEI or non-conformant Clarion, and the consumer keeps working on -locators (per ADR-038 / the Loom SEI standard §4). +is a pre-SEI or non-conformant Loomweave, and the consumer keeps working on +locators (per ADR-038 / the Weft SEI standard §4). `linkages.http: true` advertises the call-graph linkage routes (`GET /api/v1/entities/:id/callers|callees` and their `:batch-get` variants — @@ -499,18 +499,18 @@ than probing the routes directly. `taint_store.read_by_sei: true` advertises the rename-stable taint read route ([`POST /api/wardline/taint-facts/by-sei`](#post-apiwardlinetaint-factsby-sei-read-batch-by-sei), T3.4). It is **discrete from `sei.supported`** deliberately: an older SEI-capable -Clarion sets `sei.supported: true` but predates this route, so a consumer must +Loomweave sets `sei.supported: true` but predates this route, so a consumer must gate the by-SEI taint read on **this** flag and fall back to the locator-keyed read when it is absent. -`api_version` is the HTTP read API wire-contract version, not Clarion product +`api_version` is the HTTP read API wire-contract version, not Loomweave product semver. It increments only for incompatible changes to the wire contract consumed by existing Filigree clients. -`instance_id` is the stable per-project Clarion instance fingerprint persisted -in `.clarion/instance_id`. Filigree should treat a changed `instance_id` for a +`instance_id` is the stable per-project Loomweave instance fingerprint persisted +in `.loomweave/instance_id`. Filigree should treat a changed `instance_id` for a previously known endpoint as evidence that it is now talking to a different -Clarion project instance. +Loomweave project instance. The contract fixture at [`fixtures/get-api-v1-capabilities.json`](./fixtures/get-api-v1-capabilities.json) @@ -520,14 +520,14 @@ asserts that `instance_id` is a UUID; the example uses a seeded stable ID. ## SEI identity resolution Stable Entity Identity (SEI) resolution over HTTP (Wave 1 / WS1, ADR-038; the -suite-wide standard is the Loom SEI conformance spec). The **SEI** is a durable, +suite-wide standard is the Weft SEI conformance spec). The **SEI** is a durable, opaque surrogate identity that survives rename and move; the `{plugin}:{kind}:{qualname}` entity id is demoted to a mutable **locator** (address). Cross-tool bindings (Filigree associations, Wardline taint facts, `legis` attestations) **must** key on the SEI, never the locator. These routes are HMAC-gated exactly like `/api/v1/files`. Identity is read from -Clarion's `sei_bindings` store (the source of truth). SEIs are **opaque on the +Loomweave's `sei_bindings` store (the source of truth). SEIs are **opaque on the wire** — consumers MUST NOT parse them. ### `POST /api/v1/identity/resolve` @@ -537,7 +537,7 @@ Resolve a locator to its alive SEI. Request: `{ "locator": "python:function:auth Successful response (`200 OK`): ```json -{ "sei": "clarion:eid:", "current_locator": "python:function:auth.tokens.refresh", "content_hash": "", "alive": true } +{ "sei": "loomweave:eid:", "current_locator": "python:function:auth.tokens.refresh", "content_hash": "", "alive": true } ``` When the locator resolves to nothing alive: `{ "alive": false }` (still `200`). @@ -545,7 +545,7 @@ When the locator resolves to nothing alive: `{ "alive": false }` (still `200`). **Fail-closed input validation (REQ-F-02).** `resolve` **rejects** any input that is not a locator — including an already-migrated, **SEI-shaped** string — with `400` and `code: "INVALID_PATH"` (`"not a valid locator…"`), **never** a silent -mis-resolution. The rejection keys on the reserved **`clarion:eid:` prefix**, not +mis-resolution. The rejection keys on the reserved **`loomweave:eid:` prefix**, not a colon count (an SEI carries the same two colons a locator does). This contract is what makes the idempotent, resumable cross-tool backfill safe — see [`sei-migration-playbook.md`](./sei-migration-playbook.md). @@ -557,8 +557,8 @@ Resolve up to **256** locators in one request. Request: ```json { - "resolved": { "python:function:a.b": { "sei": "clarion:eid:", "current_locator": "python:function:a.b", "content_hash": "", "alive": true } }, - "invalid": ["clarion:eid:…", "malformed"], + "resolved": { "python:function:a.b": { "sei": "loomweave:eid:", "current_locator": "python:function:a.b", "content_hash": "", "alive": true } }, + "invalid": ["loomweave:eid:…", "malformed"], "not_found": ["python:function:gone"] } ``` @@ -581,29 +581,29 @@ The ordered append-only lineage event list for an SEI: ### Conformance -Clarion's SEI behaviour is proven by the shared **SEI conformance oracle** (the -Loom SEI standard §8): identity round-trip + opacity, rename (`locator_changed`), +Loomweave's SEI behaviour is proven by the shared **SEI conformance oracle** (the +Weft SEI standard §8): identity round-trip + opacity, rename (`locator_changed`), move (`moved`), ambiguous (fail-closed mint + orphan), delete (orphan + `alive: false`), and capability-absent (graceful degrade). The normative scenario definitions live at [`fixtures/sei-conformance-oracle.json`](./fixtures/sei-conformance-oracle.json) -and Clarion's pass is enforced by -`cargo test -p clarion-storage --test sei_conformance_oracle`. +and Loomweave's pass is enforced by +`cargo test -p loomweave-storage --test sei_conformance_oracle`. ## Dossier participation surface -Wave 2 / WS4. Clarion does **not** assemble the cross-tool entity dossier — the +Wave 2 / WS4. Loomweave does **not** assemble the cross-tool entity dossier — the *assembler* (Wardline; see its entity-dossier design at -`wardline/docs/superpowers/specs/2026-06-01-wardline-loom-entity-dossier-design.md` +`wardline/docs/superpowers/specs/2026-06-01-wardline-weft-entity-dossier-design.md` in the sibling repo, not vendored here) composes it. This section pins the exact -Clarion HTTP slices the assembler reads, +Loomweave HTTP slices the assembler reads, so the contract is explicit and the assembler can build a complete, freshness-stamped, SEI-keyed view of an entity **that stays correct after a rename**. The participation contract is specified in full at -[`docs/superpowers/specs/2026-06-02-clarion-dossier-participation.md`](../superpowers/specs/2026-06-02-clarion-dossier-participation.md). +[`docs/superpowers/specs/2026-06-02-loomweave-dossier-participation.md`](../superpowers/specs/2026-06-02-loomweave-dossier-participation.md). Every slice is an existing pinned endpoint — WS4 adds **no new endpoint**: -| Dossier slice | Clarion endpoint | Pinned at | +| Dossier slice | Loomweave endpoint | Pinned at | |---|---|---| | Identity + **content-axis** freshness | `POST /api/v1/identity/resolve` → `{ sei, content_hash, alive }` | [§SEI identity resolution](#post-apiv1identityresolve) | | **Identity-axis** freshness (alive/orphaned + rename lineage) | `GET /api/v1/identity/sei/:sei`, `GET /api/v1/identity/lineage/:sei` | [§SEI identity resolution](#get-apiv1identityseisei) | @@ -617,12 +617,12 @@ stored fact against it → FRESH/STALE). The *identity axis* is `resolve_sei`'s alive — never silently orphaned). A rename therefore surfaces on the identity axis while content freshness is judged independently. -**Filigree associations are NOT a Clarion surface.** The dossier's `work` section +**Filigree associations are NOT a Loomweave surface.** The dossier's `work` section reads Filigree's own `GET /api/entity-associations?entity_id=…` (ADR-029) -**directly**; Clarion is not a proxy or aggregator for it. Clarion contributes +**directly**; Loomweave is not a proxy or aggregator for it. Loomweave contributes only the **join key** — the SEI from `resolve` — which both Filigree associations -and Wardline taint facts key on. Routing Filigree data through Clarion would -violate the enrich-only axiom (`loom.md` §5: Clarion serves slices, it does not +and Wardline taint facts key on. Routing Filigree data through Loomweave would +violate the enrich-only axiom (`weft.md` §5: Loomweave serves slices, it does not assemble or aggregate sibling data). This is a deliberate decision, not a gap. **Wardline's taint axis stays rename-correct via the SEI, not the locator.** A @@ -634,7 +634,7 @@ rest of the dossier joins on, so a rename surfaces on the identity axis while th taint fact remains reachable. Gate on `taint_store.read_by_sei`. **`scc_peers` is not directly HTTP-reachable.** The dossier envelope lists -`scc_peers[]`; Clarion exposes subsystem *clustering* +`scc_peers[]`; Loomweave exposes subsystem *clustering* (`subsystem_members`/`subsystem_of`, MCP-only), which is **not** the same as strongly-connected-component peers — serving it under that name would be a semantic mismatch. The dossier degrades gracefully on partial linkages @@ -645,13 +645,13 @@ here, not silently dropped. The composition is proven end-to-end against a renamed-function fixture by `serve_http_dossier_participation_surface_serves_a_renamed_function` -(`crates/clarion-cli/tests/serve.rs`). +(`crates/loomweave-cli/tests/serve.rs`). ## Governed paradise: `legis` governance consumption (WS9) Wave 3 / WS9 closes **governed paradise**: `legis` adds IRAP-grade governance -(attestations, sign-offs, custody, audit lineage) keyed to Clarion's stable -identity, as an **opt-in** layer a solo project never sees. Clarion is *already* +(attestations, sign-offs, custody, audit lineage) keyed to Loomweave's stable +identity, as an **opt-in** layer a solo project never sees. Loomweave is *already* the identity authority (Wave 1) — WS9 exposes existing surfaces and swaps one provider; it adds **no** new identity surface and **no** trust adjudication. Governed paradise does **not** gate core paradise (Wave 2); core paradise stands @@ -659,10 +659,10 @@ on its own. ### Audit-spine consumption (read-only, existing surface) -`legis` reads Clarion's **existing** Wave-1 identity surface as its audit spine — -no new endpoint, **no Clarion-side integrity machinery**: +`legis` reads Loomweave's **existing** Wave-1 identity surface as its audit spine — +no new endpoint, **no Loomweave-side integrity machinery**: -| `legis` need | Clarion endpoint | Maps to | +| `legis` need | Loomweave endpoint | Maps to | |---|---|---| | Attestation join key (SEI for a code entity) | `POST /api/v1/identity/resolve` → `{ sei, content_hash, alive }` | [§SEI identity resolution](#post-apiv1identityresolve) | | Orphan → governance gap (attestation in limbo) | `GET /api/v1/identity/sei/:sei` → `{ alive: false, lineage }` | [§SEI identity resolution](#get-apiv1identityseisei) | @@ -675,11 +675,11 @@ discharged **entirely** against these three routes. `legis` polls (pull-only); no push surface is required for v1 (it is informational future work per `legis`'s own conformance notes). -**Integrity is `legis`'s boundary, not Clarion's (REQ-L-01).** The SEI standard +**Integrity is `legis`'s boundary, not Loomweave's (REQ-L-01).** The SEI standard leaves lineage tamper-evidence to a named decision; `legis` accepts **Option 3** — each consumer establishes its own integrity layer over polled snapshots (`legis` stores a snapshot hash of the lineage at each governance decision and detects -divergence on re-read). Clarion therefore ships **no** lineage hash-chain or +divergence on re-read). Loomweave therefore ships **no** lineage hash-chain or signature in v1; the append-only `sei_lineage` log (INSERT-only, no UPDATE path) plus transport is the custody substrate `legis` verifies against. A signed lineage is North-Star, not a v1 requirement. This is the custody axiom: integrity @@ -690,7 +690,7 @@ is re-established at the governance boundary, never assumed from the store. The SEI matcher consumes a typed, locator-level git-rename signal behind the `GitRenameSource` trait (SEI spec §6). `legis` owns the git interface, so it is the intended external supplier: `LegisGitRenameSource` -(`crates/clarion-cli/src/sei_git.rs`) reads `legis`'s +(`crates/loomweave-cli/src/sei_git.rs`) reads `legis`'s `GET /git/renames?rev_range=…` and feeds the **same** file→locator translation as the v1 `ShellGitRenameSource`, behind the same trait — no matcher change. Selection is enrich-only and **capability-aware** (`select_git_rename_source`): @@ -699,7 +699,7 @@ when configured (`--legis-url`) **and** reachable. Unset/unreachable `legis` issues no HTTP and leaves behaviour byte-identical to pre-WS9. **Two windows, unioned (gap closed via option 2).** The two suppliers observe -**different rename windows**: Clarion's working-tree-vs-HEAD window (uncommitted +**different rename windows**: Loomweave's working-tree-vs-HEAD window (uncommitted renames — the "rename a file, re-analyze before commit" flow, shell `git diff -M HEAD`) and `legis`'s committed window over a rev-range (`git log -M`, which cannot see the working tree). A naive *swap* of one for the other would drop uncommitted @@ -713,7 +713,7 @@ renames, so `analyze` now **unions** both each run (`gather_git_renames`): `0007`) and reads the *prior* run's commit (`prior_analyzed_commit`, excluding the current run) to form the range. So `legis` is now **operatively consulted** for renames committed between runs — the gap formerly disclosed here is closed - by option 2 (Clarion drives a committed rev-range). Option 1 (a `legis` + by option 2 (Loomweave drives a committed rev-range). Option 1 (a `legis` working-tree surface) remains unnecessary. Enrich-only and never a regression: with `legis` unset, or no prior commit, only @@ -724,18 +724,18 @@ union can only *miss* a carry, never cause a false one. Proven by (working-tree rename survives a reachable-but-empty `legis`) and `gather_unions_committed_legis_and_working_tree_shell_renames` (committed `legis` rename and uncommitted shell rename both appear) in -`crates/clarion-cli/src/sei_git.rs`. +`crates/loomweave-cli/src/sei_git.rs`. ### Trust vocabulary — carried verbatim, never adjudicated -Clarion does **not** adjudicate trust. One judge: **Wardline analyses, `legis` -governs.** Clarion adds **no** policy / attestation engine — attestations live in -`legis`, keyed on Clarion's SEI. The trust vocabulary (`declared_tier`, +Loomweave does **not** adjudicate trust. One judge: **Wardline analyses, `legis` +governs.** Loomweave adds **no** policy / attestation engine — attestations live in +`legis`, keyed on Loomweave's SEI. The trust vocabulary (`declared_tier`, `wardline_groups`/`declared_groups`, boundary contracts) is carried **exactly as -Wardline emits it**; Clarion participates in, never leads, any suite +Wardline emits it**; Loomweave participates in, never leads, any suite trust-vocabulary convergence. -What Clarion carries from Wardline **today** is the taint-fact store and +What Loomweave carries from Wardline **today** is the taint-fact store and qualname-reconciled findings (see [§Wardline taint-fact store](#wardline-taint-fact-store-sp9) and [§Wardline qualname normalization](#wardline-qualname-normalization-entity-reconciliation)) — verbatim, no re-judgement. The `WardlineMeta` entity-property carriage of @@ -753,26 +753,26 @@ input-path shape: configured project root with the requested path (or treats an absolute path as-is when it falls under the project root), then folds `.` / `..` lexically. The path **does not need to exist on disk** at lookup - time — Clarion resolves against its entity catalog + time — Loomweave resolves against its entity catalog (`entities.source_file_path`), not against `stat(2)`. This is important for replay scenarios where the catalog row outlives the file. - **Forward-slash separators only**. Both project-relative paths (`src/foo.py`) and project-root-anchored absolute paths - (`/var/run/clarion-corpus/src/foo.py`) are accepted; backslash + (`/var/run/loomweave-corpus/src/foo.py`) are accepted; backslash separators are not. - **Project-relative or absolute under the project root**. A request whose normalized form escapes the project root returns 400 `PATH_OUTSIDE_PROJECT` (single-file) or surfaces as an `errors[].code = "PATH_OUTSIDE_PROJECT"` entry (batch). - **Symlink-resolved equivalents are not reconciled**. If your project - contains symlinks, both Clarion and Filigree must agree on the same + contains symlinks, both Loomweave and Filigree must agree on the same canonical form for the same logical file (typically the lexically- - joined form). Clarion does **not** call `canonicalize()` on the + joined form). Loomweave does **not** call `canonicalize()` on the request path; the catalog row carries the canonical form chosen at ingest. -Reference implementation: `clarion-storage::query::normalize_lookup_path` -(file path: [`crates/clarion-storage/src/query.rs`](../../crates/clarion-storage/src/query.rs)). +Reference implementation: `loomweave-storage::query::normalize_lookup_path` +(file path: [`crates/loomweave-storage/src/query.rs`](../../crates/loomweave-storage/src/query.rs)). The function signature is stable for the lifetime of `api_version: 1`; the *implementation* is free to change as long as the lexical / no-disk-touch / forward-slash / under-root contract holds. @@ -781,30 +781,30 @@ no-disk-touch / forward-slash / under-root contract holds. This contract governs how a sibling that emits Findings against Python code (Wardline's native Filigree emitter, per ADR-018's 2026-05-29 amendment and the -2026-05-29 integration brief §4.A) must spell the entity it references so Clarion -can reconcile it. It is *enrich-only*: when the contract is honored, Clarion -attaches the entity's structural context to the Finding; when it is not, Clarion +2026-05-29 integration brief §4.A) must spell the entity it references so Loomweave +can reconcile it. It is *enrich-only*: when the contract is honored, Loomweave +attaches the entity's structural context to the Finding; when it is not, Loomweave degrades to `resolution_confidence: heuristic|none` — there is no error and no broken state, only a worse match. Filigree's own ticket lifecycle is unaffected -either way (loom.md §5). +either way (weft.md §5). **The composed form.** A Finding carries `metadata.wardline.qualname` as the -**pre-composed** dotted name (Clarion's L7 `canonical_qualified_name`), not a +**pre-composed** dotted name (Loomweave's L7 `canonical_qualified_name`), not a `(file, bare-qualname)` pair. The composition is two parts: ```text metadata.wardline.qualname = module_dotted_name(file_path) + "." + __qualname__ ``` -- `module_dotted_name(file_path)` is Clarion's module-prefix rule. Its canonical +- `module_dotted_name(file_path)` is Loomweave's module-prefix rule. Its canonical implementation and tests are - [`module_dotted_name`](../../plugins/python/src/clarion_plugin_python/extractor.py) + [`module_dotted_name`](../../plugins/python/src/loomweave_plugin_python/extractor.py) and `test_module_dotted_name_helper` in [`test_extractor.py`](../../plugins/python/tests/test_extractor.py). The rule: strip a leading `src/` **only at position 0**; drop the `.py` suffix; collapse an `__init__` filename to its package; join the rest with `.`. No other root marker (`lib/`, `app/`, the project name, …) is stripped, and a top-level - `__init__.py` normalizes to the empty string and is **not emitted** (Clarion + `__init__.py` normalizes to the empty string and is **not emitted** (Loomweave rejects an empty qualified name). - `__qualname__` is copied **verbatim** — `` closure markers and dotted nested-class chains are preserved, never rewritten. @@ -824,7 +824,7 @@ top-level `__init__.py`). A conformant emitter reproduces every vector exactly. named in ADR-018 as the eventual conformance surface but is **not implemented at release:1.1**. Until it ships, the fixture above is the contract: validate against it offline. Building the endpoint ahead of a shipped Wardline consumer would be -speculative forward-work (loom.md §5 — Clarion translates qualnames because it owns +speculative forward-work (weft.md §5 — Loomweave translates qualnames because it owns the catalog, but only when a consumer needs it). What *did* ship is the narrower, exact-tier `POST /api/wardline/resolve` (see [Wardline taint-fact store](#wardline-taint-fact-store-sp9)), which takes @@ -835,29 +835,29 @@ extends the same resolver rather than reimplementing it. ## Wardline taint-fact store (SP9) -This pins the `/api/wardline/*` sub-router Clarion *exposes* to Wardline (ADR-036; +This pins the `/api/wardline/*` sub-router Loomweave *exposes* to Wardline (ADR-036; design spec -[`2026-05-30-clarion-wardline-taint-store-design.md`](../superpowers/specs/2026-05-30-clarion-wardline-taint-store-design.md)). -Wardline computes per-entity taint facts and persists them into Clarion's catalog -so Clarion can fold them into briefings; Clarion treats every fact's payload as an +[`2026-05-30-loomweave-wardline-taint-store-design.md`](../superpowers/specs/2026-05-30-loomweave-wardline-taint-store-design.md)). +Wardline computes per-entity taint facts and persists them into Loomweave's catalog +so Loomweave can fold them into briefings; Loomweave treats every fact's payload as an **opaque blob** and never asserts whether it is fresh. This is enrich-only and -disabled-by-default (loom.md §5): the write path is off unless explicitly enabled, -and Clarion's own semantics never depend on a stored fact. +disabled-by-default (weft.md §5): the write path is off unless explicitly enabled, +and Loomweave's own semantics never depend on a stored fact. -**Per-project isolation.** One `clarion serve` process serves exactly one project -(the `.clarion/` store under that project root). The `project` request field is a +**Per-project isolation.** One `loomweave serve` process serves exactly one project +(the `.loomweave/` store under that project root). The `project` request field is a **guard, not a selector** — it does not choose among projects; it only lets a client assert which project it believes it is talking to. The handle is the project-root directory name. An **empty** `project` is always accepted (no assertion); a **non-empty** value that does not match the served project's directory name returns `403` with `code: "PROJECT_MISMATCH"`. (Reference: `AppState::reject_project_mismatch` in -[`http_read.rs`](../../crates/clarion-cli/src/http_read.rs).) +[`http_read.rs`](../../crates/loomweave-cli/src/http_read.rs).) ### Sub-router framing, auth, and limits The `/api/wardline/*` routes sit behind the **same identity middleware** as the -protected `/api/v1/*` routes (HMAC `X-Loom-Component: clarion:` plus +protected `/api/v1/*` routes (HMAC `X-Weft-Component: loomweave:` plus timestamp/nonce freshness preferred per ADR-034/ADR-042, legacy `Authorization: Bearer` accepted as fallback, loopback-unauth allowed; see [Authentication](#authentication)). The only difference is the body @@ -889,18 +889,18 @@ capabilities. ### `POST /api/wardline/resolve` -Exact-tier resolution of **pre-composed** dotted qualnames to Clarion entity IDs. +Exact-tier resolution of **pre-composed** dotted qualnames to Loomweave entity IDs. No `&file=` disambiguator and no normalization: Wardline has already shaped each -qualname to byte-match Clarion's `canonical_qualified_name`, and Clarion does a +qualname to byte-match Loomweave's `canonical_qualified_name`, and Loomweave does a direct existence lookup of the candidate `python:function:` (taint facts -are function/method-scoped; methods are `python:function:` in Clarion's ontology +are function/method-scoped; methods are `python:function:` in Loomweave's ontology per ADR-022). Request body (`application/json`, max 4 MiB): ```json { - "project": "clarion", + "project": "loomweave", "qualnames": ["auth.tokens.refresh", "auth.sessions.SessionStore.load"] } ``` @@ -947,7 +947,7 @@ parsing the body. Request body (`application/json`, max 4 MiB): ```json { - "project": "clarion", + "project": "loomweave", "scan_id": "wardline-scan-2026-05-30", "facts": [ { @@ -960,18 +960,18 @@ parsing the body. Request body (`application/json`, max 4 MiB): } ``` -- `wardline_json` is **opaque** to Clarion (see [Opacity contract](#opacity-contract)). +- `wardline_json` is **opaque** to Loomweave (see [Opacity contract](#opacity-contract)). - `scan_id` and `content_hash_at_compute` are accepted as **top-level fields** - (queryable columns); Clarion does **not** parse them out of the blob. The + (queryable columns); Loomweave does **not** parse them out of the blob. The per-fact `scan_id` falls back to the batch-level `scan_id` when absent. Both are optional. - `sei` (optional, per fact) is the fact's **Stable Entity Identity** — a second, rename-stable lookup key (see [`POST /api/wardline/taint-facts/by-sei`](#post-apiwardlinetaint-factsby-sei-read-batch-by-sei)). - When **omitted**, Clarion resolves it server-side from the alive `sei_bindings` + When **omitted**, Loomweave resolves it server-side from the alive `sei_bindings` row for the fact's resolved locator (batched, one query for the whole request); when **present**, the caller-supplied value wins and is stored **verbatim** - (opaque — Clarion never parses `clarion:eid:`). A fact written on a pre-SEI + (opaque — Loomweave never parses `loomweave:eid:`). A fact written on a pre-SEI database / unbound locator stores `sei = null` and is reachable by locator only. Successful response (`200 OK`): @@ -1060,7 +1060,7 @@ The view has **exactly four fields**: Note what is **not** echoed: the write-time `scan_id` and `content_hash_at_compute` columns are **never returned by the read**. Wardline reads its own `content_hash_at_compute` from *inside* the opaque `wardline_json` blob, not from a -Clarion-returned field (see the freshness contract). +Loomweave-returned field (see the freshness contract). Failure modes: @@ -1084,7 +1084,7 @@ HMAC-gated like the rest of the `/api/wardline/*` group. Gate on the discrete capability flag [`taint_store.read_by_sei`](#get-apiv1_capabilities), **not** on `sei.supported`: -an older SEI-capable Clarion advertises `sei.supported: true` yet predates this +an older SEI-capable Loomweave advertises `sei.supported: true` yet predates this route. Request body (`application/json`, max 4 MiB): `{ "project"?, "seis": [..] }`. @@ -1096,12 +1096,12 @@ objects, **one per input SEI, in input order**: ```json [ { - "sei": "clarion:eid:9f2c…", + "sei": "loomweave:eid:9f2c…", "wardline_json": {"taint": "tainted"}, "current_content_hash": "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9", "exists": true }, - {"sei": "clarion:eid:deadbeef", "exists": false} + {"sei": "loomweave:eid:deadbeef", "exists": false} ] ``` @@ -1129,7 +1129,7 @@ pre-rescan fact in the window before it. it is stranded under both the dead locator (resolution now points elsewhere) and this route (no SEI tag) until Wardline recomputes — the freshness gate surfaces it as stale, Wardline rewrites it, and the `sei` is populated. This is self-healing -by design; Clarion does **not** backfill historical rows. +by design; Loomweave does **not** backfill historical rows. Failure modes: @@ -1159,23 +1159,23 @@ definition is pinned exactly: `current_content_hash` is **omitted** (field absent). `exists` still reflects the stored fact (it can be `true` with no `current_content_hash`). - Reference: `clarion_storage::current_file_hash` in - [`query.rs`](../../crates/clarion-storage/src/query.rs). + Reference: `loomweave_storage::current_file_hash` in + [`query.rs`](../../crates/loomweave-storage/src/query.rs). **Who decides freshness.** Wardline stamps `content_hash_at_compute` *inside* the opaque `wardline_json` blob when it computes a fact, then on read compares that -stamp to Clarion's returned `current_content_hash`: equal ⇒ the fact is fresh; +stamp to Loomweave's returned `current_content_hash`: equal ⇒ the fact is fresh; mismatch, or `exists: false`, or `current_content_hash` absent ⇒ Wardline -recomputes. **Wardline owns the fresh/stale decision; Clarion never asserts a +recomputes. **Wardline owns the fresh/stale decision; Loomweave never asserts a freshness verdict** — it only reports the live hash and lets Wardline compare. ### Opacity contract -`wardline_json` is stored and returned **byte-verbatim**. Clarion holds it as a +`wardline_json` is stored and returned **byte-verbatim**. Loomweave holds it as a serde_json `RawValue` on both the write and read paths, so object key order and whitespace are preserved exactly — `{"b":2,"a":1}` is *not* re-emitted as -`{"a":1,"b":2}`. Clarion **never parses or validates** the blob's contents. The -only fields Clarion reads structurally are the top-level `scan_id` / +`{"a":1,"b":2}`. Loomweave **never parses or validates** the blob's contents. The +only fields Loomweave reads structurally are the top-level `scan_id` / `content_hash_at_compute` accompanying a write (stored as queryable columns) — they are taken from the request envelope, **not** parsed out of the blob. @@ -1189,31 +1189,31 @@ for these routes (the prose shapes here are the contract). (see [Wardline qualname normalization](#wardline-qualname-normalization-entity-reconciliation)); `resolve_wardline_qualnames` is exercised against its vectors by `resolves_fixture_vectors_exact` in - [`wardline_taint.rs`](../../crates/clarion-storage/src/wardline_taint.rs). + [`wardline_taint.rs`](../../crates/loomweave-storage/src/wardline_taint.rs). - The **whole-file-vs-span freshness** definition is pinned by the `current_file_hash` tests in - [`query.rs`](../../crates/clarion-storage/src/query.rs) (asserting whole-file + [`query.rs`](../../crates/loomweave-storage/src/query.rs) (asserting whole-file blake3, not the span-scoped LF-normalized hash). - The **route behaviour** — exact resolve + unresolved, project-guard mismatch, `WRITE_DISABLED`, per-entity replace, byte-verbatim storage, the live whole-file hash, deleted-file ⇒ absent hash, the bare-array batch read, and the over-cap `413` — is pinned by the `wardline_*` async handler tests in - [`http_read.rs`](../../crates/clarion-cli/src/http_read.rs). + [`http_read.rs`](../../crates/loomweave-cli/src/http_read.rs). ## Consumed Filigree convention: ephemeral-port endpoint discovery -The contracts above pin the surface Clarion *exposes*. This section and the ones -that follow pin what Clarion *consumes*. Endpoint discovery comes first because -it is the prerequisite for every consumed route below: before Clarion can call +The contracts above pin the surface Loomweave *exposes*. This section and the ones +that follow pin what Loomweave *consumes*. Endpoint discovery comes first because +it is the prerequisite for every consumed route below: before Loomweave can call `issues_for`'s issue-detail read, the scan-results intake, or the clean-stale sweep, it must resolve *where* Filigree is actually listening. -`clarion serve` and `clarion analyze` resolve that base URL through -[`clarion_mcp::filigree_url::resolve_filigree_url`](../../crates/clarion-mcp/src/filigree_url.rs) +`loomweave serve` and `loomweave analyze` resolve that base URL through +[`loomweave_mcp::filigree_url::resolve_filigree_url`](../../crates/loomweave-mcp/src/filigree_url.rs) (added with clarion-084e82250c). It is strictly *enrich-only*: discovery only ever *upgrades* the statically configured `integrations.filigree.base_url`; it never -gates Clarion's own semantics. Clarion stays solo-useful with Filigree absent -(loom.md §5). +gates Loomweave's own semantics. Loomweave stays solo-useful with Filigree absent +(weft.md §5). **The convention.** Filigree's dashboard, when running in its default *ethereal* mode, publishes its live listen port to `/.filigree/ephemeral.port` @@ -1232,12 +1232,12 @@ read, never computed**. This mirrors the Filigree sources | Valid `/.filigree/ephemeral.port` present | configured URL with its **port** overridden by the live port (scheme, host, path preserved) | `.filigree/ephemeral.port` | | No / unreadable port file | configured URL unchanged | `config` | -**The negative contract (the load-bearing part).** What Clarion *refuses* to do is -the loom-§5 safety argument: +**The negative contract (the load-bearing part).** What Loomweave *refuses* to do is +the weft-§5 safety argument: - It **reads** the published port; it never **computes** Filigree's port itself (no reimplementation of the `8400 + sha256 % 1000` rule). -- When no live port file is present, it falls back to Clarion's **own** configured +- When no live port file is present, it falls back to Loomweave's **own** configured `base_url`, **never** to a Filigree-internal default. Copying Filigree's `DEFAULT_PORT` would be a silent cross-product coupling that breaks the moment Filigree changes its default. @@ -1249,7 +1249,7 @@ the loom-§5 safety argument: **Server-mode gap (named limitation).** Filigree also supports a *server* mode that publishes its endpoint through a home-directory global (`~/.config/filigree/server.json`) rather than the per-project `ephemeral.port` -file. Clarion does **not** read `server.json` at release:1.1 — under Filigree +file. Loomweave does **not** read `server.json` at release:1.1 — under Filigree server mode, discovery finds no `ephemeral.port` and degrades to the configured `base_url` (`source = config`), which is correct but does not auto-track a server-mode port. Closing this gap (reading the server-mode global) is tracked as @@ -1277,53 +1277,53 @@ only when the integration is disabled. connection discovery resolves a single scalar (a port), not a wire document, so a fixture is not warranted; the shapes above are the contract. The executable check is the test module in -[`filigree_url.rs`](../../crates/clarion-mcp/src/filigree_url.rs): it pins the +[`filigree_url.rs`](../../crates/loomweave-mcp/src/filigree_url.rs): it pins the live-port override, the no-file and disabled fall-throughs, and the fail-soft folding of corrupt / zero ports to the configured URL. Because the port file is a *read* of a Filigree-owned convention, a change on Filigree's side (path or -format) would be caught by re-reading its `ephemeral.py`, not by Clarion CI. +format) would be caught by re-reading its `ephemeral.py`, not by Loomweave CI. ## Consumed Filigree route: entity associations -This pins the Filigree reverse-association route Clarion consumes for +This pins the Filigree reverse-association route Loomweave consumes for `issues_for` / `entity_issue_list`: ```text GET {filigree_base}/api/entity-associations?entity_id={association_key} ``` -`association_key` is opaque to Filigree. Clarion sends exactly **one** key per -entity: the entity's SEI (`clarion:eid:*`) when the served index has one, and +`association_key` is opaque to Filigree. Loomweave sends exactly **one** key per +entity: the entity's SEI (`loomweave:eid:*`) when the served index has one, and the mutable locator (`{plugin}:{kind}:{qualname}`) only for entities with no SEI (pre-SEI or unbound indexes). There is no per-row fallback: once an entity has a -SEI, Clarion queries by SEI alone, so a *legacy locator-keyed* Filigree row for +SEI, Loomweave queries by SEI alone, so a *legacy locator-keyed* Filigree row for that same entity is **not** resolved until the SEI migration re-keys it onto the SEI (per [`sei-migration-playbook.md`](./sei-migration-playbook.md); bindings "must key on the SEI" — see [§SEI identity resolution](#sei-identity-resolution)). The locator query path therefore applies solely to entities that never had a SEI. -The response field remains `clarion_entity_id` for wire compatibility; a SEI -query echoes the SEI key, a locator query echoes the locator. Clarion maps a +The response field remains `loomweave_entity_id` for wire compatibility; a SEI +query echoes the SEI key, a locator query echoes the locator. Loomweave maps a returned SEI key back to the current locator before comparing `content_hash_at_attach` against the current entity content hash. ## Consumed Filigree route: issue detail (enrichment) -This pins the single Filigree route Clarion *consumes* (against the endpoint +This pins the single Filigree route Loomweave *consumes* (against the endpoint resolved above) to enrich an entity-association match — the read behind `issues_for`'s per-match `issue` block (clarion-51a2868c86). It is strictly *enrich-only*: if the route is absent or unreachable, the match still -resolves with `issue: null`, and Clarion's semantics are unaffected (loom.md §5). +resolves with `issue: null`, and Loomweave's semantics are unaffected (weft.md §5). ```text -GET {filigree_base}/api/loom/issues/{issue_id} +GET {filigree_base}/api/weft/issues/{issue_id} ``` - `{issue_id}` is percent-encoded as a path-segment value. - **Request headers:** `accept: application/json`; `x-filigree-actor: ` when an actor is configured; `Authorization: Bearer ` when a bearer token is configured. (HMAC is not used on this *outbound* read; it is an - inbound-auth mechanism on Clarion's own exposed routes.) + inbound-auth mechanism on Loomweave's own exposed routes.) - **`200` response body** — only these fields are read; **unknown fields are ignored** so Filigree may grow the route without breaking the consumer: @@ -1331,7 +1331,7 @@ GET {filigree_base}/api/loom/issues/{issue_id} { "title": "string", "status": "string", "priority": 0 } ``` - `priority` is an integer (Clarion's `IssueDetail.priority: i64`). + `priority` is an integer (Loomweave's `IssueDetail.priority: i64`). - **`404`** — the issue, or the whole route, is absent. Treated as the enrich-only degrade signal (`Ok(None)` → `issue: null`), **not** an error. - **Any other non-`2xx`** — surfaced as a client error; the enrichment for that @@ -1339,34 +1339,34 @@ GET {filigree_base}/api/loom/issues/{issue_id} There is no normative fixture for this route yet; the shape above is the contract. The `parse_issue_detail_response` shape test in -[`filigree.rs`](../../crates/clarion-mcp/src/filigree.rs) is the executable +[`filigree.rs`](../../crates/loomweave-mcp/src/filigree.rs) is the executable check. ## Consumed Filigree routes: Wardline findings (Flow B read-time reconciliation) Flow B (read-time Wardline finding reconciliation) consumes two existing Filigree -*loom* read routes — no new route is requested. Both are enrich-only: if either +*weft* read routes — no new route is requested. Both are enrich-only: if either is unreachable the `wardline_findings` section degrades to -`result_kind: "unavailable"` and the tool returns normally (loom.md §5). +`result_kind: "unavailable"` and the tool returns normally (weft.md §5). -1. `GET {filigree_base}/api/loom/files?scan_source=wardline&path_prefix=` → - a list envelope `{items, has_more}`. Clarion takes the item whose `path` is - byte-exact (the filter is a prefix query; Clarion performs the exact-match +1. `GET {filigree_base}/api/weft/files?scan_source=wardline&path_prefix=` → + a list envelope `{items, has_more}`. Loomweave takes the item whose `path` is + byte-exact (the filter is a prefix query; Loomweave performs the exact-match itself to get Filigree's `file_id`). -2. `GET {filigree_base}/api/loom/findings?scan_source=wardline&file_id=` → - a list envelope `{items, has_more}`. Clarion reads `rule_id`, `message`, +2. `GET {filigree_base}/api/weft/findings?scan_source=wardline&file_id=` → + a list envelope `{items, has_more}`. Loomweave reads `rule_id`, `message`, `severity`, `status`, `line_start`/`line_end`, `fingerprint`, and `metadata` (the reconciliation key is `metadata.wardline.qualname`). Unknown fields are ignored so Filigree may grow the row without breaking this consumer. -Clarion reads only the first page of each list response (it does not follow +Loomweave reads only the first page of each list response (it does not follow `has_more`); for a single source file the expected file/finding volume fits one Filigree page. Multi-page following is a documented v1 limitation. **In both hops, a truncated first page fails closed rather than returning a partial view:** - Hop-1: if the prefix query returns a page that does not contain the - exact-path match **and** `has_more` is true, Clarion cannot conclude the file + exact-path match **and** `has_more` is true, Loomweave cannot conclude the file is absent — the match may be on a later page. - Hop-2: if the findings page for the resolved `file_id` reports `has_more`, the first page is an incomplete enumeration of the file's findings. @@ -1384,8 +1384,8 @@ non-matching qualname is not surfaced (dropped); findings carrying no `metadata.wardline.qualname` are counted in `omitted_no_qualname`. `heuristic` is reserved but never returned in v1.1 (`wardline_reconcile.rs`). -Flow B does not use `POST /api/v1/files:resolve`; it uses the two `loom` GET -routes above (`POST /api/v1/files:resolve` is a route Clarion *exposes*, not +Flow B does not use `POST /api/v1/files:resolve`; it uses the two `weft` GET +routes above (`POST /api/v1/files:resolve` is a route Loomweave *exposes*, not one it consumes — see [`POST /api/v1/files:resolve`](#post-apiv1filesresolve)). The section is surfaced under `result.wardline_findings` in `issues_for` and as @@ -1394,29 +1394,29 @@ a top-level field of the orientation pack. **Verification scope.** There is no normative wire fixture for these routes — the prose above is the contract. Executable checks: -- `loom_files_url` / `loom_findings_url` / `parse_wardline_findings_response` +- `weft_files_url` / `weft_findings_url` / `parse_wardline_findings_response` / `wardline_findings_for_path_does_two_hops_and_exact_path_filter` in - [`filigree.rs`](../../crates/clarion-mcp/src/filigree.rs) pin the URL shape, + [`filigree.rs`](../../crates/loomweave-mcp/src/filigree.rs) pin the URL shape, response parsing, and exact-path-filter logic. - `reconcile_for_entity` tests in - [`wardline_reconcile.rs`](../../crates/clarion-mcp/src/wardline_reconcile.rs) + [`wardline_reconcile.rs`](../../crates/loomweave-mcp/src/wardline_reconcile.rs) pin the exact-tier match and the omitted-no-qualname count. - `issues_for_attaches_exact_wardline_findings` and `orientation_pack_includes_wardline_findings` in - [`storage_tools.rs`](../../crates/clarion-mcp/tests/storage_tools.rs) pin + [`storage_tools.rs`](../../crates/loomweave-mcp/tests/storage_tools.rs) pin the end-to-end section attachment and the `result_kind: "unavailable"` degrade. ## Consumed Filigree route: scan-results intake (finding emission) -This pins the Filigree route Clarion *consumes* to emit findings — WP9-B, -REQ-FINDING-03, ADR-004. `clarion analyze` Phase 8 POSTs this run's persisted +This pins the Filigree route Loomweave *consumes* to emit findings — WP9-B, +REQ-FINDING-03, ADR-004. `loomweave analyze` Phase 8 POSTs this run's persisted findings on completion via -[`FiligreeHttpClient::post_scan_results`](../../crates/clarion-mcp/src/filigree.rs). +[`FiligreeHttpClient::post_scan_results`](../../crates/loomweave-mcp/src/filigree.rs). It is *enrich-only*: emission is gated behind `integrations.filigree.{enabled,emit_findings}` (both default `false`), and any failure — Filigree down, transport error, build error — is recorded in -`stats.json` and logged as `CLA-INFRA-FILIGREE-UNREACHABLE`, never propagated. -The analyze run never fails because a sibling is unreachable (loom.md §5). +`stats.json` and logged as `LMWV-INFRA-FILIGREE-UNREACHABLE`, never propagated. +The analyze run never fails because a sibling is unreachable (weft.md §5). ```text POST {filigree_base}/api/v1/scan-results @@ -1424,16 +1424,16 @@ POST {filigree_base}/api/v1/scan-results - **Request headers:** `content-type: application/json`; `x-filigree-actor: ` when configured; `Authorization: Bearer ` - when a bearer token is configured. (HMAC is inbound-only, on Clarion's own + when a bearer token is configured. (HMAC is inbound-only, on Loomweave's own exposed routes; this outbound POST uses bearer.) - **Request body** — only the keys below are sent. Filigree silently drops any - top-level finding key outside its enumerated set, so Clarion's richer fields - nest under `metadata` and the Clarion-owned `metadata.clarion.*` slot (ADR-004, + top-level finding key outside its enumerated set, so Loomweave's richer fields + nest under `metadata` and the Loomweave-owned `metadata.loomweave.*` slot (ADR-004, detailed-design §7) where verbatim preservation is verified: ```json { - "scan_source": "clarion", + "scan_source": "loomweave", "scan_run_id": "", "mark_unseen": true, "create_observations": false, @@ -1441,7 +1441,7 @@ POST {filigree_base}/api/v1/scan-results "findings": [ { "path": "src/auth/tokens.py", - "rule_id": "CLA-PY-STRUCTURE-001", + "rule_id": "LMWV-PY-STRUCTURE-001", "message": "Circular import detected", "severity": "medium", "line_start": 12, @@ -1450,7 +1450,7 @@ POST {filigree_base}/api/v1/scan-results "kind": "defect", "confidence": 0.95, "confidence_basis": "ast_match", - "clarion": { + "loomweave": { "entity_id": "python:class:auth.tokens::TokenManager", "related_entities": ["python:class:auth.sessions::SessionStore"], "supports": [], @@ -1464,9 +1464,9 @@ POST {filigree_base}/api/v1/scan-results } ``` - - `scan_source` is always `"clarion"`; it is part of Filigree's dedup key, so + - `scan_source` is always `"loomweave"`; it is part of Filigree's dedup key, so it is stable across runs. - - `scan_run_id` carries Clarion's `run_id`. It is omitted entirely when unset; + - `scan_run_id` carries Loomweave's `run_id`. It is omitted entirely when unset; an unknown id is tolerated by Filigree (it ingests the findings and reconstructs the run in history), which is how REQ-FINDING-05's wire shape ships without a pre-create handshake. **This tolerate-unknown behavior is @@ -1474,17 +1474,17 @@ POST {filigree_base}/api/v1/scan-results recorded in Filigree's `docs/federation/contracts.md` §F6 and pinned by `tests/api/test_files_api.py::TestUnknownScanRunIdContract`). There is no `POST /api/.../scan-runs` create endpoint — only read-only - `GET /api/scan-runs` history — and Clarion emits no Phase-0 create call. - Three intake obligations Clarion honors against this contract: - (1) `run_id` is globally unique across producers — Clarion defaults it to a - UUIDv4 (`clarion-cli` `analyze.rs`), since Filigree keys on the id alone and + `GET /api/scan-runs` history — and Loomweave emits no Phase-0 create call. + Three intake obligations Loomweave honors against this contract: + (1) `run_id` is globally unique across producers — Loomweave defaults it to a + UUIDv4 (`loomweave-cli` `analyze.rs`), since Filigree keys on the id alone and a collision either rejects (`VALIDATION`, different `scan_source`) or silently misattributes (same `scan_source`); (2) `scan_source` stays stable - across a run (always `"clarion"`, per the bullet above), since history + across a run (always `"loomweave"`, per the bullet above), since history groups on `(scan_run_id, scan_source)`; (3) with `complete_scan_run=true` an unknown run cannot be marked completed, so the response `warnings[]` carries a benign `"Scan run status not updated to 'completed': …"` — - Clarion switches on HTTP status + stats counts and never treats a populated + Loomweave switches on HTTP status + stats counts and never treats a populated `warnings[]` as failure. - `mark_unseen` is `true` for a normal full run (old-position findings for the same rule/file transition to `unseen_in_latest`); a `--resume RUN_ID` run @@ -1510,13 +1510,13 @@ POST {filigree_base}/api/v1/scan-results meaningful only after normal `mark_unseen=true` runs. The emitted `mark_unseen` value is recorded in the run's `stats.json` `filigree_emission` block. - - `create_observations` is always `false` — Clarion emits findings, not + - `create_observations` is always `false` — Loomweave emits findings, not observations. - - `severity` is the **wire** vocabulary, mapped from Clarion's internal value: + - `severity` is the **wire** vocabulary, mapped from Loomweave's internal value: `CRITICAL→critical`, `ERROR→high`, `WARN→medium`, everything else (`INFO`, `NONE`, unknown) `→info`. This mirrors Filigree's own server-side coercion but is done client-side so the original survives under - `metadata.clarion.internal_severity`. + `metadata.loomweave.internal_severity`. - `line_start` / `line_end` are omitted when the anchor entity has no line range. A finding whose anchor entity has **no `path`** is skipped (and counted in `stats.json` as `skipped_no_path`); Filigree rejects path-less @@ -1525,10 +1525,10 @@ POST {filigree_base}/api/v1/scan-results batch (above) carries tier facts anchored to a synthetic subsystem entity, which has no `source_file_path`. Rather than dropping them, that pass anchors them to the **project-root** `path` (the same stand-in the `core:project:*` - `CLA-INFRA-*` findings use) and sets `metadata.clarion.synthetic_anchor=true`; + `LMWV-INFRA-*` findings use) and sets `metadata.loomweave.synthetic_anchor=true`; such findings carry no `line_start`/`line_end`. A consumer must treat the `path` of a `synthetic_anchor` finding as a placeholder for a non-file entity - (the real subject is `metadata.clarion.entity_id`), never as a source + (the real subject is `metadata.loomweave.entity_id`), never as a source location. The during-run Phase-8 batch supplies no fallback, so a path-less during-run finding (e.g. the weak-modularity subsystem fact) is still skipped. - **briefing-blocked exclusion:** findings anchored to a `briefing_blocked` @@ -1555,18 +1555,18 @@ POST {filigree_base}/api/v1/scan-results ``` - **Any non-`2xx`** — surfaced as a transport/HTTP error, folded into the - `filigree_emission` stats blob and the `CLA-INFRA-FILIGREE-UNREACHABLE` log; + `filigree_emission` stats blob and the `LMWV-INFRA-FILIGREE-UNREACHABLE` log; the analyze run still completes successfully. There is no normative fixture for this route yet; the shapes above are the contract. The `request_serializes_to_filigree_wire_shape` and `parses_live_response_shape` tests in -[`scan_results.rs`](../../crates/clarion-mcp/src/scan_results.rs) — the latter +[`scan_results.rs`](../../crates/loomweave-mcp/src/scan_results.rs) — the latter pinned to a real captured Filigree response — are the executable checks. **Verification scope.** CI exercises the emitter against a *mock* HTTP server (`post_scan_results_sends_batch_and_parses_response` in -[`filigree.rs`](../../crates/clarion-mcp/src/filigree.rs)), and the +[`filigree.rs`](../../crates/loomweave-mcp/src/filigree.rs)), and the `analyze`-level test asserts the enrich-only degrade when Filigree is unreachable. The wire shapes pinned above were captured from a **one-time live probe** against a running Filigree intake (the source of the `severity` @@ -1577,12 +1577,12 @@ changes. ## Consumed Filigree route: clean-stale retention (`--prune-unseen`) -`clarion analyze --prune-unseen` asks Filigree to run a retention sweep over its -own findings (REQ-FINDING-06). This is a **loom-generation** route, distinct +`loomweave analyze --prune-unseen` asks Filigree to run a retention sweep over its +own findings (REQ-FINDING-06). This is a **weft-generation** route, distinct from the classic `/api/v1/scan-results` emission intake. ``` -POST {filigree_base}/api/loom/findings/clean-stale +POST {filigree_base}/api/weft/findings/clean-stale ``` - **Headers** — `accept: application/json`, optional `x-filigree-actor` and @@ -1592,20 +1592,20 @@ POST {filigree_base}/api/loom/findings/clean-stale ```json { - "scan_source": "clarion", + "scan_source": "loomweave", "older_than_days": 30, - "actor": "clarion-mcp" + "actor": "loomweave-mcp" } ``` - `scan_source` is **required** server-side as an accident-guard (Filigree's core treats absent as "all sources", which the route refuses to expose). - Clarion always sends `"clarion"`, so the sweep can only touch Clarion's + Loomweave always sends `"loomweave"`, so the sweep can only touch Loomweave's findings — it can never affect Wardline's or any other tool's. - `older_than_days` comes from `integrations.filigree.prune_unseen_days` (default 30); a non-negative integer. `0` sweeps the whole current unseen backlog. - - `actor` is Clarion's configured actor, for Filigree's audit trail. + - `actor` is Loomweave's configured actor, for Filigree's audit trail. - **Semantics — soft-archive, not delete.** Filigree moves its `unseen_in_latest` findings older than the threshold to `fixed` status @@ -1618,22 +1618,22 @@ POST {filigree_base}/api/loom/findings/clean-stale defaulted: ```json - { "findings_fixed": 4, "scan_source": "clarion", "older_than_days": 30 } + { "findings_fixed": 4, "scan_source": "loomweave", "older_than_days": 30 } ``` - **Enrich-only.** The sweep runs after emission (Phase 8b) for the same non-hard-failed outcomes. A Filigree outage, a non-`2xx`, or the integration being disabled is recorded in the `filigree_prune` stats blob (status - `unreachable` / `skipped`) and the `CLA-INFRA-FILIGREE-UNREACHABLE` log — the + `unreachable` / `skipped`) and the `LMWV-INFRA-FILIGREE-UNREACHABLE` log — the analyze run still completes successfully. Prune keys on `unseen_in_latest`, which only `mark_unseen=true` (normal) runs create; a `--resume` (`mark_unseen=false`) run produces no unseen state for prune to sweep. **Verification scope.** Same posture as the emission intake: the wire shape is checked by `clean_stale_*` unit tests in -[`scan_results.rs`](../../crates/clarion-mcp/src/scan_results.rs) and exercised +[`scan_results.rs`](../../crates/loomweave-mcp/src/scan_results.rs) and exercised end-to-end against a *mock* Filigree (`analyze_prune_unseen_*` in -[`analyze.rs`](../../crates/clarion-cli/tests/analyze.rs), covering the +[`analyze.rs`](../../crates/loomweave-cli/tests/analyze.rs), covering the post-after-emission path, the unreachable degrade, and the disabled no-op). The route shape was read from Filigree's own handler + API tests; there is **no recurring end-to-end test against a live Filigree**. diff --git a/docs/federation/filigree-side/2026-05-19-registry-backend-cross-project-sequencing.md b/docs/federation/filigree-side/2026-05-19-registry-backend-cross-project-sequencing.md index e9d83554..d554c990 100644 --- a/docs/federation/filigree-side/2026-05-19-registry-backend-cross-project-sequencing.md +++ b/docs/federation/filigree-side/2026-05-19-registry-backend-cross-project-sequencing.md @@ -1,40 +1,40 @@ # Registry-Backend & File-Identity Displacement — Cross-Project Sequencing Memo **Status:** Draft (2026-05-19) -**Scope:** Sequencing memo for bringing Clarion ADR-014's vision forward across Filigree and Clarion. Pairs with [Filigree ADR-014](../architecture/decisions/ADR-014-registry-backend-and-file-identity-displacement.md). +**Scope:** Sequencing memo for bringing Loomweave ADR-014's vision forward across Filigree and Loomweave. Pairs with [Filigree ADR-014](../architecture/decisions/ADR-014-registry-backend-and-file-identity-displacement.md). **Sibling docs:** -- Clarion ADR-014 (the original 2026-04-18 design) -- Clarion ADR-029 (entity-associations, shipped 2026-05-17) -- Clarion v0.1 plan §WP10 (the cross-product work package this memo activates) +- Loomweave ADR-014 (the original 2026-04-18 design) +- Loomweave ADR-029 (entity-associations, shipped 2026-05-17) +- Loomweave v0.1 plan §WP10 (the cross-product work package this memo activates) --- ## 1. Problem in one paragraph -`loom.md` §2 says "Clarion owns the file registry." The 2.1.0 Filigree code still mints Filigree-native shadow file IDs on every `POST /api/loom/scan-results`, every `create_observation(file_path=…)`, and every `trigger_scan*`. The Clarion-side design exists (ADR-014, 2026-04-18) and was accepted; the Filigree-side counterpart was never drafted; the Clarion-side WP10 was deferred to v0.2 by the 2026-05-16 Sprint 2 amendment. ADR-029 (entity_associations, May 17) ships the *peer* primitive — issue↔entity binding — but does not close the file-identity split. This memo names what each side must do to close it. +`weft.md` §2 says "Loomweave owns the file registry." The 2.1.0 Filigree code still mints Filigree-native shadow file IDs on every `POST /api/weft/scan-results`, every `create_observation(file_path=…)`, and every `trigger_scan*`. The Loomweave-side design exists (ADR-014, 2026-04-18) and was accepted; the Filigree-side counterpart was never drafted; the Loomweave-side WP10 was deferred to v0.2 by the 2026-05-16 Sprint 2 amendment. ADR-029 (entity_associations, May 17) ships the *peer* primitive — issue↔entity binding — but does not close the file-identity split. This memo names what each side must do to close it. ## 2. Why now - ADR-029 is the last work that *could* have substituted for ADR-014's vision. It explicitly does not. - Filigree's 2.1.0 release prep is mid-flight (see `2026-05-18-2.1.0-release-prep.md`); landing the schema migration alongside that release minimises the operator-visible migration count. -- Clarion's Sprint 2 closed clean (2026-05-17 signoff); Sprint 3 is unscoped and can absorb the Clarion-side HTTP read API as its anchor work package. -- The Loom URI spec (`2026-05-17-loom-uri-spec.md`) is in draft and depends on a coherent file-identity story across the federation. Closing the split here removes a foundational ambiguity from that spec's scope. +- Loomweave's Sprint 2 closed clean (2026-05-17 signoff); Sprint 3 is unscoped and can absorb the Loomweave-side HTTP read API as its anchor work package. +- The Weft URI spec (`2026-05-17-weft-uri-spec.md`) is in draft and depends on a coherent file-identity story across the federation. Closing the split here removes a foundational ambiguity from that spec's scope. ## 3. Work, owned by project -### 3.1 Clarion side (Sprint 3, anchor work) +### 3.1 Loomweave side (Sprint 3, anchor work) -WP10 from `docs/implementation/v0.1-plan.md` was always Clarion's side of this story. Sprint 2 deferred it; this memo lifts it back into active scope. Plus one new item the original plan did not have: Clarion has no HTTP server today. The `resolve_file` surface ADR-014 assumes must be built. +WP10 from `docs/implementation/v0.1-plan.md` was always Loomweave's side of this story. Sprint 2 deferred it; this memo lifts it back into active scope. Plus one new item the original plan did not have: Loomweave has no HTTP server today. The `resolve_file` surface ADR-014 assumes must be built. | ID | Title | Notes | |---|---|---| -| C-WP10.1 | Clarion HTTP read API — `axum` server in `clarion-cli` | New crate-internal module; reuses `ReaderPool`. Bind on a port advertised in `clarion.yaml`. Wire into `clarion serve` alongside MCP stdio. | -| C-WP10.2 | `GET /api/v1/files?path=&language=` endpoint | Returns `{entity_id, content_hash, canonical_path, language}`. Backed by `clarion-storage::reader` queries; pure read, no writer involvement. | -| C-WP10.3 | Contracts directory for the read API | `docs/federation/contracts.md` on Clarion's side, parity with Filigree's. Publish a JSON fixture for `GET /api/v1/files`. | -| C-WP10.4 | Capability probe response | `GET /api/v1/_capabilities` (or equivalent) returning `{file_registry: true, version: "0.1"}` so Filigree's `ClarionRegistry` can fail fast on incompatible deployments. | +| C-WP10.1 | Loomweave HTTP read API — `axum` server in `loomweave-cli` | New crate-internal module; reuses `ReaderPool`. Bind on a port advertised in `loomweave.yaml`. Wire into `loomweave serve` alongside MCP stdio. | +| C-WP10.2 | `GET /api/v1/files?path=&language=` endpoint | Returns `{entity_id, content_hash, canonical_path, language}`. Backed by `loomweave-storage::reader` queries; pure read, no writer involvement. | +| C-WP10.3 | Contracts directory for the read API | `docs/federation/contracts.md` on Loomweave's side, parity with Filigree's. Publish a JSON fixture for `GET /api/v1/files`. | +| C-WP10.4 | Capability probe response | `GET /api/v1/_capabilities` (or equivalent) returning `{file_registry: true, version: "0.1"}` so Filigree's `LoomweaveRegistry` can fail fast on incompatible deployments. | | C-WP10.5 | Sprint 3 scope amendment memo | Mirrors `sprint-2/scope-amendment-2026-05.md` — names the lift, the rationale (this memo), the dependency on Filigree Phase A→B landing. | -Estimated cost: one Clarion sprint (~2 weeks). The dominant lift is C-WP10.1 (no HTTP machinery exists today). +Estimated cost: one Loomweave sprint (~2 weeks). The dominant lift is C-WP10.1 (no HTTP machinery exists today). ### 3.2 Filigree side (2.1.x or 2.2.0) @@ -49,24 +49,24 @@ Maps onto ADR-014 Phases B / C / E. | F-B.5 | Schema migration: add `file_records.content_hash`, `file_records.registry_backend` | §3 | | F-B.6 | Test suite parameterisation over `registry_backend` (default-only after B) | §1 | | F-C.1 | `registry_backend` config flag wiring (`.filigree.conf`, `ProjectConfig`) | §2 | -| F-C.2 | `ClarionRegistry` implementation (reqwest-equivalent in Python) | §1 | +| F-C.2 | `LoomweaveRegistry` implementation (reqwest-equivalent in Python) | §1 | | F-C.3 | Capability probe in `GET /api/files/_schema.config_flags` | §4 | | F-C.4 | `FILIGREE_FILE_REGISTRY_DISPLACED` error code + the three direct-mutation surfaces that emit it | §6 | -| F-C.5 | Fail-closed startup under `clarion` mode; `--allow-local-fallback` flag | §7 | +| F-C.5 | Fail-closed startup under `loomweave` mode; `--allow-local-fallback` flag | §7 | | F-C.6 | `filigree migrate-registry` CLI verb (dry-run, execute, rollback, manifest) | §5 | -| F-E.1 | `docs/federation/contracts.md` update referencing Clarion's read API | §8 | +| F-E.1 | `docs/federation/contracts.md` update referencing Loomweave's read API | §8 | | F-E.2 | Cross-project launch runbook | §"Sequencing" | Estimated cost: Phase B is ~1–2 weeks (mostly the refactor + tests). Phase C is ~2 weeks (config + capability + migration tool + fail-closed). Phase E is ~3 days (docs). ### 3.3 Cross-project (Phase D) -Integration tests run against a live Clarion read API. Owned jointly — fixtures published from Filigree, contract pinned-SHA per the pattern Clarion already uses for Filigree's entity-associations. +Integration tests run against a live Loomweave read API. Owned jointly — fixtures published from Filigree, contract pinned-SHA per the pattern Loomweave already uses for Filigree's entity-associations. ## 4. Sequencing and the critical path ``` - Clarion Sprint 3 Filigree 2.1.x or 2.2.0 + Loomweave Sprint 3 Filigree 2.1.x or 2.2.0 ──────────────── ───────────────────────── C-WP10.1 axum server │ @@ -87,29 +87,29 @@ Integration tests run against a live Clarion read API. Owned jointly — fixture E docs + launch runbook ``` -Phase B is independent of Clarion-side work and can land first (behaviour-preserving refactor + schema columns at empty defaults). Phase C cannot land usefully until C-WP10.2 ships. Phase D blocks on both. +Phase B is independent of Loomweave-side work and can land first (behaviour-preserving refactor + schema columns at empty defaults). Phase C cannot land usefully until C-WP10.2 ships. Phase D blocks on both. ## 5. Decisions deferred - **Batched resolution.** ADR-014 §Negative names sync-RPC cost as an issue under high-throughput scans. Out of scope; revisit if a real workload hits the wall. The `RegistryProtocol` interface admits batching at a future minor version (`resolve_files(paths: list[str])`). -- **Read-side displacement.** `GET /api/loom/files` continues to return Filigree's stored rows. Whether Clarion entity IDs should be the visible IDs in those responses (vs. left as-is, with consumers cross-referencing) is a UX decision; deferred until at least one cross-tool consumer asks. -- **Wardline integration.** Clarion ADR-015's native Wardline→Filigree emitter is unchanged in scope. Wardline findings still flow through Clarion's SARIF translator under both backends. +- **Read-side displacement.** `GET /api/weft/files` continues to return Filigree's stored rows. Whether Loomweave entity IDs should be the visible IDs in those responses (vs. left as-is, with consumers cross-referencing) is a UX decision; deferred until at least one cross-tool consumer asks. +- **Wardline integration.** Loomweave ADR-015's native Wardline→Filigree emitter is unchanged in scope. Wardline findings still flow through Loomweave's SARIF translator under both backends. ## 6. Risks - **Schema migration adoption.** F-B.5 ships a forward-only `ALTER TABLE` with safe defaults. Verified compatible with the 2.0.x → 2.1.x migration path. Operators with custom backup tooling that snapshots `file_records` mid-migration could see inconsistent rows; documented in the release notes. -- **Clarion ADR-014's `--allow-local-fallback` semantics.** The fallback flag exists in case the operator's Clarion is unreachable at startup; if the flag is left on permanently, the operator silently runs in `local` mode while believing they run `clarion` mode. F-C.5 surfaces a persistent dashboard banner while the flag is active to prevent this. -- **Sprint 3 scope contention.** Clarion's Sprint 3 has not yet been scoped; this memo proposes WP10 as the anchor. If Sprint 3 commits to something else first, F-B can still land (behaviour-preserving) and F-C waits. +- **Loomweave ADR-014's `--allow-local-fallback` semantics.** The fallback flag exists in case the operator's Loomweave is unreachable at startup; if the flag is left on permanently, the operator silently runs in `local` mode while believing they run `loomweave` mode. F-C.5 surfaces a persistent dashboard banner while the flag is active to prevent this. +- **Sprint 3 scope contention.** Loomweave's Sprint 3 has not yet been scoped; this memo proposes WP10 as the anchor. If Sprint 3 commits to something else first, F-B can still land (behaviour-preserving) and F-C waits. ## 7. What lands when (target dates, not commitments) | Milestone | Target | Gates | |---|---|---| | Filigree ADR-014 ratified | 2026-05-21 | One reviewer; user sign-off on this memo first | -| Clarion Sprint 3 scope amendment ratified | 2026-05-23 | Clarion sprint-planning meeting (author = self) | +| Loomweave Sprint 3 scope amendment ratified | 2026-05-23 | Loomweave sprint-planning meeting (author = self) | | F-B lands on Filigree main | 2026-06-06 | Tests green; no functional change visible to operators | -| C-WP10.1..4 land on Clarion main | 2026-06-13 | `clarion serve` exposes HTTP read API on a configurable port | -| F-C lands behind feature flag | 2026-06-20 | `registry_backend: clarion` works against a local Clarion | +| C-WP10.1..4 land on Loomweave main | 2026-06-13 | `loomweave serve` exposes HTTP read API on a configurable port | +| F-C lands behind feature flag | 2026-06-20 | `registry_backend: loomweave` works against a local Loomweave | | Phase D integration tests green | 2026-06-27 | Cross-process CI lane in both repos | | Phase E docs published | 2026-06-30 | Cross-project launch runbook + contract refs | @@ -118,7 +118,7 @@ Phase B is independent of Clarion-side work and can land first (behaviour-preser This memo is a strategy proposal, not a plan. Before issues are filed in either tracker, sign-off on: 1. **Scope of "as initially designed."** This memo treats ADR-014 (2026-04-18) as the design of record. ADR-029 stays. The `entity_associations` table is *not* unwound — it remains the right primitive for issue↔entity binding. ADR-014 is additive over it. -2. **Sequencing.** Filigree Phase B first (independent, behaviour-preserving) is the safer parallelisation than waiting for Clarion. If you'd rather sequence Clarion-first to validate the read-API shape against a real consumer, F-B waits. -3. **Migration verb scope.** The `filigree migrate-registry` CLI verb is the operationally-real piece of this story. Worth a second pair of eyes (operator-facing tools land harder than refactors). If you'd rather defer the migration verb behind an explicit follow-up issue (operators stay on `local` for the first release of `clarion` mode), F-C ships smaller. +2. **Sequencing.** Filigree Phase B first (independent, behaviour-preserving) is the safer parallelisation than waiting for Loomweave. If you'd rather sequence Loomweave-first to validate the read-API shape against a real consumer, F-B waits. +3. **Migration verb scope.** The `filigree migrate-registry` CLI verb is the operationally-real piece of this story. Worth a second pair of eyes (operator-facing tools land harder than refactors). If you'd rather defer the migration verb behind an explicit follow-up issue (operators stay on `local` for the first release of `loomweave` mode), F-C ships smaller. -On greenlight, the next two artifacts are: (a) Filigree filigree-tracker milestone + phases + steps under the existing `planning` pack, (b) Clarion filigree-tracker milestone + phases + steps mirroring WP10. Both repos already use filigree for their own work tracking; no new tracker required. +On greenlight, the next two artifacts are: (a) Filigree filigree-tracker milestone + phases + steps under the existing `planning` pack, (b) Loomweave filigree-tracker milestone + phases + steps mirroring WP10. Both repos already use filigree for their own work tracking; no new tracker required. diff --git a/docs/federation/filigree-side/ADR-014-registry-backend-and-file-identity-displacement.md b/docs/federation/filigree-side/ADR-014-registry-backend-and-file-identity-displacement.md index ff2eb141..68f45e06 100644 --- a/docs/federation/filigree-side/ADR-014-registry-backend-and-file-identity-displacement.md +++ b/docs/federation/filigree-side/ADR-014-registry-backend-and-file-identity-displacement.md @@ -1,33 +1,33 @@ -# ADR-014: `registry_backend` Flag and File-Identity Displacement to Clarion +# ADR-014: `registry_backend` Flag and File-Identity Displacement to Loomweave **Status**: Accepted **Date**: 2026-05-19 **Deciders**: John (project lead) -**Context**: Closes the Filigree-side hole that Clarion ADR-014 (2026-04-18) named as a v0.1 prerequisite and that Clarion ADR-029 (2026-05-16) explicitly deferred. The work is Filigree's; the sibling decision is Clarion's. +**Context**: Closes the Filigree-side hole that Loomweave ADR-014 (2026-04-18) named as a v0.1 prerequisite and that Loomweave ADR-029 (2026-05-16) explicitly deferred. The work is Filigree's; the sibling decision is Loomweave's. ## Summary Filigree gains a pluggable `RegistryProtocol` selected by a `registry_backend` config flag with two modes: - `local` (default, unchanged behaviour) — Filigree's native UUID-derived file IDs. -- `clarion` (opt-in, per-project) — Filigree delegates file-identity resolution to Clarion's HTTP read API; `file_records.id` stores Clarion's symbolic file-kind entity ID (`core:file:{qualified_name}` per Clarion ADR-003). Clarion supplies `content_hash` separately as drift metadata; hashes are not embedded in file IDs. +- `loomweave` (opt-in, per-project) — Filigree delegates file-identity resolution to Loomweave's HTTP read API; `file_records.id` stores Loomweave's symbolic file-kind entity ID (`core:file:{qualified_name}` per Loomweave ADR-003). Loomweave supplies `content_hash` separately as drift metadata; hashes are not embedded in file IDs. -A `FILIGREE_FILE_REGISTRY_DISPLACED` error code surfaces direct file-registration attempts that conflict with `clarion` mode. The `registry_backend` value is published in `GET /api/files/_schema.config_flags` for capability probing. Fail-closed startup applies only under `clarion` mode (an `--allow-local-fallback` escape exists for single-operator recovery). +A `FILIGREE_FILE_REGISTRY_DISPLACED` error code surfaces direct file-registration attempts that conflict with `loomweave` mode. The `registry_backend` value is published in `GET /api/files/_schema.config_flags` for capability probing. Fail-closed startup applies only under `loomweave` mode (an `--allow-local-fallback` escape exists for single-operator recovery). -The new column `file_records.content_hash` stores the hash Clarion supplied at resolution time, reusing the same drift-vocabulary that ADR-029's `entity_associations.content_hash_at_attach` introduced. There is one drift signal across both surfaces. +The new column `file_records.content_hash` stores the hash Loomweave supplied at resolution time, reusing the same drift-vocabulary that ADR-029's `entity_associations.content_hash_at_attach` introduced. There is one drift signal across both surfaces. ## Context ### The gap ADR-029 leaves on the table -ADR-029 (the entity-associations binding) is shipping and is right. It does not, however, close the file-identity split between Filigree and Clarion. Concretely, today, on the 2.1.0 branch: +ADR-029 (the entity-associations binding) is shipping and is right. It does not, however, close the file-identity split between Filigree and Loomweave. Concretely, today, on the 2.1.0 branch: -- `POST /api/loom/scan-results` (`dashboard_routes/files.py:401-417`) routes to `db.process_scan_results(**parsed)`. +- `POST /api/weft/scan-results` (`dashboard_routes/files.py:401-417`) routes to `db.process_scan_results(**parsed)`. - `process_scan_results` (`db_files.py:857-926`) iterates findings and calls `_upsert_file_record(path=f["path"], …)` for each. - `_upsert_file_record` (`db_files.py:640-678`) mints a Filigree-native ID (`f"{prefix}-f-{uuid4().hex[:10]}"`) the first time it sees a path. -- No code path consults `scan_source` (or the `metadata.clarion.*` payload) to thread Clarion's entity ID through as the `file_records.id`. +- No code path consults `scan_source` (or the `metadata.loomweave.*` payload) to thread Loomweave's entity ID through as the `file_records.id`. -The result: every Clarion-sourced scan POST mints a shadow file row whose ID is Filigree-native; an issue with an `entity_associations` row pointing at `python:function:auth.tokens::issue_token` and a `file_associations` row pointing at the file that function lives in carries **two unrelated identities for the same code**. `loom.md` §2's claim that "Clarion owns the file registry" is, today, false at the storage layer. +The result: every Loomweave-sourced scan POST mints a shadow file row whose ID is Filigree-native; an issue with an `entity_associations` row pointing at `python:function:auth.tokens::issue_token` and a `file_associations` row pointing at the file that function lives in carries **two unrelated identities for the same code**. `weft.md` §2's claim that "Loomweave owns the file registry" is, today, false at the storage layer. Two further auto-create paths exhibit the same shadow-mint behaviour: `db_observations.register_file` (`db_observations.py:223`) and the three call sites of `tracker.register_file` in `mcp_tools/scanners.py` (`:657`, `:746`, `:964`). @@ -35,11 +35,11 @@ ADR-029 explicitly named this gap as out of scope and called the registry-backen ### Why ADR-029's approach is not a substitute -ADR-029's defence — opaque-string IDs, no schema surgery, no Clarion-runtime dependency — answers the question *"how do we let Filigree issues reference Clarion entities without coupling the products?"*. It does not answer *"how do we make the file_id Filigree stores be the same identifier Clarion stores?"*. Those are different questions; ADR-029 solves the first, this ADR solves the second. +ADR-029's defence — opaque-string IDs, no schema surgery, no Loomweave-runtime dependency — answers the question *"how do we let Filigree issues reference Loomweave entities without coupling the products?"*. It does not answer *"how do we make the file_id Filigree stores be the same identifier Loomweave stores?"*. Those are different questions; ADR-029 solves the first, this ADR solves the second. ### The "thrown away" history -Clarion ADR-014 (2026-04-18) designed this displacement in detail: `RegistryProtocol` trait, `local`/`clarion` modes, `FILIGREE_FILE_REGISTRY_DISPLACED` error code, capability probe via `_schema.config_flags`, fail-closed startup, `--allow-local-fallback` recovery flag. The Filigree-side ADR was never drafted; the WP10 work package on the Clarion side was deferred to v0.2 by the Sprint 2 scope amendment (2026-05-16). This ADR adopts Clarion ADR-014's design near-verbatim and is the Filigree-side counterpart that closes the cross-product story. +Loomweave ADR-014 (2026-04-18) designed this displacement in detail: `RegistryProtocol` trait, `local`/`loomweave` modes, `FILIGREE_FILE_REGISTRY_DISPLACED` error code, capability probe via `_schema.config_flags`, fail-closed startup, `--allow-local-fallback` recovery flag. The Filigree-side ADR was never drafted; the WP10 work package on the Loomweave side was deferred to v0.2 by the Sprint 2 scope amendment (2026-05-16). This ADR adopts Loomweave ADR-014's design near-verbatim and is the Filigree-side counterpart that closes the cross-product story. ## Decision @@ -69,7 +69,7 @@ class ResolvedFile(TypedDict): Two implementations: - `LocalRegistry` — current behaviour. `file_id` is `f"{prefix}-f-{uuid4().hex[:10]}"`. `content_hash` is the empty string under `local` mode (column is nullable in the schema; see §3). `is_displaced()` returns `False`. -- `ClarionRegistry` — issues `GET {clarion_base}/api/v1/files?path=…&language=…` and returns Clarion's `{entity_id, content_hash, canonical_path, language}` reshaped into `ResolvedFile`. `is_displaced()` returns `True`. Connection failures surface as `RegistryUnavailableError` (see §6). +- `LoomweaveRegistry` — issues `GET {loomweave_base}/api/v1/files?path=…&language=…` and returns Loomweave's `{entity_id, content_hash, canonical_path, language}` reshaped into `ResolvedFile`. `is_displaced()` returns `True`. Connection failures surface as `RegistryUnavailableError` (see §6). The protocol is composed into `FiligreeDB` at construction time; the three auto-create surfaces (`_upsert_file_record`, `register_file`, `tracker.register_file`) take a `registry: RegistryProtocol` parameter instead of generating IDs inline. @@ -79,13 +79,13 @@ The protocol is composed into `FiligreeDB` at construction time; the three auto- ```yaml registry_backend: local # default -clarion: +loomweave: base_url: http://localhost:9111 timeout_seconds: 5 allow_local_fallback: false ``` -`local` is the forever-default. Filigree-the-project, every existing Filigree dogfood, and every existing third-party Filigree deployment continue to operate without change. Clarion mode is strictly opt-in per project. +`local` is the forever-default. Filigree-the-project, every existing Filigree dogfood, and every existing third-party Filigree deployment continue to operate without change. Loomweave mode is strictly opt-in per project. ### 3. Schema additions @@ -94,7 +94,7 @@ ALTER TABLE file_records ADD COLUMN content_hash TEXT NOT NULL DEFAULT ''; ALTER TABLE file_records ADD COLUMN registry_backend TEXT NOT NULL DEFAULT 'local'; ``` -Both columns survive a backend swap: a row created under `local` and re-resolved under `clarion` updates `content_hash`, `registry_backend`, and (one-time, see §5) the row's `id`. +Both columns survive a backend swap: a row created under `local` and re-resolved under `loomweave` updates `content_hash`, `registry_backend`, and (one-time, see §5) the row's `id`. Schema version bumps; migration is forward-only and additive (no FK rewrites, no row identity churn under default `local` mode). @@ -112,51 +112,51 @@ Schema version bumps; migration is forward-only and additive (no FK rewrites, no } ``` -`registry_backend_features` enumerates what this Filigree build *can* serve (always `["local"]` after Phase B; `["local", "clarion"]` after Phase C). Clarion's startup probe reads this; absent the field, Clarion enters shadow-registry mode. +`registry_backend_features` enumerates what this Filigree build *can* serve (always `["local"]` after Phase B; `["local", "loomweave"]` after Phase C). Loomweave's startup probe reads this; absent the field, Loomweave enters shadow-registry mode. ### 5. ID rewrite policy under backend swap -A project that flips from `local` to `clarion` mid-life will have existing `file_records` rows with Filigree-native IDs and four NOT-NULL FK consumers pointing at those IDs. The displacement story therefore needs a row-ID rewrite path: +A project that flips from `local` to `loomweave` mid-life will have existing `file_records` rows with Filigree-native IDs and four NOT-NULL FK consumers pointing at those IDs. The displacement story therefore needs a row-ID rewrite path: -- A new CLI verb `filigree migrate-registry --to clarion [--dry-run]` issues `resolve_file` for every existing row, fetches Clarion's entity ID, and rewrites `file_records.id` and all four FK consumers (`scan_findings.file_id`, `file_associations.file_id`, `file_events.file_id`, plus the `entity_associations` table introduced in PR #42 — verified to *not* hold file IDs, only entity IDs, so untouched here) inside a single SQLite transaction. -- Rows whose paths Clarion cannot resolve (deleted-on-disk, outside-project, etc.) are flagged in the manifest; the operator chooses delete-row or keep-as-orphan. +- A new CLI verb `filigree migrate-registry --to loomweave [--dry-run]` issues `resolve_file` for every existing row, fetches Loomweave's entity ID, and rewrites `file_records.id` and all four FK consumers (`scan_findings.file_id`, `file_associations.file_id`, `file_events.file_id`, plus the `entity_associations` table introduced in PR #42 — verified to *not* hold file IDs, only entity IDs, so untouched here) inside a single SQLite transaction. +- Rows whose paths Loomweave cannot resolve (deleted-on-disk, outside-project, etc.) are flagged in the manifest; the operator chooses delete-row or keep-as-orphan. - Rollback uses the same manifest in reverse. -The migration is not run automatically. A capability-probe mismatch (registry says `clarion` but rows have `registry_backend = 'local'`) raises `RegistryStateMismatch` on next write and halts auto-create paths until the operator runs the migration or reverts the flag. +The migration is not run automatically. A capability-probe mismatch (registry says `loomweave` but rows have `registry_backend = 'local'`) raises `RegistryStateMismatch` on next write and halts auto-create paths until the operator runs the migration or reverts the flag. ### 6. `FILIGREE_FILE_REGISTRY_DISPLACED` error code -Under `clarion` mode, the following direct-mutation paths return `FILIGREE_FILE_REGISTRY_DISPLACED`: +Under `loomweave` mode, the following direct-mutation paths return `FILIGREE_FILE_REGISTRY_DISPLACED`: - MCP tool `register_file`. - CLI verb `filigree register-file`. - HTTP `POST /api/files` direct-create (if/when it exists; currently not exposed). -The error message includes the Clarion read URL the operator should use instead. The three auto-create paths route through `RegistryProtocol` and never raise this code — they get Clarion IDs transparently. +The error message includes the Loomweave read URL the operator should use instead. The three auto-create paths route through `RegistryProtocol` and never raise this code — they get Loomweave IDs transparently. -### 7. Fail-closed startup under `clarion` mode +### 7. Fail-closed startup under `loomweave` mode -If `registry_backend: clarion` is configured but the Clarion HTTP read API is unreachable at Filigree startup, the three auto-create paths return `503 Service Unavailable` with `RegistryUnavailableError`. Read paths (`GET /api/loom/files`, `GET /api/loom/issues/.../files`) continue to operate against stored rows. +If `registry_backend: loomweave` is configured but the Loomweave HTTP read API is unreachable at Filigree startup, the three auto-create paths return `503 Service Unavailable` with `RegistryUnavailableError`. Read paths (`GET /api/weft/files`, `GET /api/weft/issues/.../files`) continue to operate against stored rows. `allow_local_fallback: true` (in `.filigree.conf` or via `--allow-local-fallback`) downgrades the failure to a `WARN` and routes auto-creates through `LocalRegistry`. The flag is for single-operator recovery, not steady-state operation; the dashboard surfaces a banner while it is active. ### 8. Living surface, classic surface -The `registry_backend` flag affects *behaviour*, not API shape. Both classic (`/api/v1/scan-results`) and loom (`/api/loom/scan-results`) handlers continue to accept identical payloads. Under `clarion` mode, the `file_id` returned in responses is a Clarion entity ID rather than a Filigree-native ID; the *shape* is unchanged (`file_id: str`). This is a contract-level addition, not a break: ADR-002 generation freezes apply to shapes, not to ID grammars. +The `registry_backend` flag affects *behaviour*, not API shape. Both classic (`/api/v1/scan-results`) and weft (`/api/weft/scan-results`) handlers continue to accept identical payloads. Under `loomweave` mode, the `file_id` returned in responses is a Loomweave entity ID rather than a Filigree-native ID; the *shape* is unchanged (`file_id: str`). This is a contract-level addition, not a break: ADR-002 generation freezes apply to shapes, not to ID grammars. ## Alternatives Considered ### Alternative 1 — Keep ADR-029 only; never close the file-identity split -`entity_associations` covers the cross-product reference need for issues. Files keep Filigree-native IDs forever; `loom.md` §2's "Clarion owns the file registry" is informally downgraded to "Clarion owns the entity catalog; Filigree owns the file mapping." +`entity_associations` covers the cross-product reference need for issues. Files keep Filigree-native IDs forever; `weft.md` §2's "Loomweave owns the file registry" is informally downgraded to "Loomweave owns the entity catalog; Filigree owns the file mapping." -**Why rejected**: the downgrade is real but unstated; consumers reading `loom.md` get one story, the code does another. Either fix the code or fix the doctrine. Fixing the code is the cheaper of the two because the design already exists (Clarion ADR-014) and the surface is bounded (5–8 files; ~17 test files reference `file_id` directly). +**Why rejected**: the downgrade is real but unstated; consumers reading `weft.md` get one story, the code does another. Either fix the code or fix the doctrine. Fixing the code is the cheaper of the two because the design already exists (Loomweave ADR-014) and the surface is bounded (5–8 files; ~17 test files reference `file_id` directly). -### Alternative 2 — Single mode, always-Clarion (no flag) +### Alternative 2 — Single mode, always-Loomweave (no flag) -Drop `local`; always delegate. Filigree without Clarion fails to start. +Drop `local`; always delegate. Filigree without Loomweave fails to start. -**Why rejected**: violates `loom.md` §4 composition law and §5 enrichment failure test. Filigree-the-project (which uses Filigree to track its own work) would require Clarion to operate, which is absurd. The flag is the price of staying federated. +**Why rejected**: violates `weft.md` §4 composition law and §5 enrichment failure test. Filigree-the-project (which uses Filigree to track its own work) would require Loomweave to operate, which is absurd. The flag is the price of staying federated. ### Alternative 3 — Generalize `entity_associations` to carry file IDs too @@ -164,62 +164,62 @@ Add an `association_kind: 'file' | 'entity'` discriminator to `entity_associatio **Why rejected**: same reason ADR-029 rejected merging file_associations and entity_associations — overloading. `file_records.id` is referenced by four NOT-NULL FKs; routing those references through a discriminated union would touch more code than the `RegistryProtocol` refactor and would leave `file_records.id` itself still shadowed. -### Alternative 4 — Schema-level join across two DBs (Filigree + Clarion) +### Alternative 4 — Schema-level join across two DBs (Filigree + Loomweave) -`file_records.id` becomes a foreign key into `.clarion/clarion.db`. +`file_records.id` becomes a foreign key into `.loomweave/loomweave.db`. -**Why rejected**: `loom.md` §6 — no shared store. Each product owns its storage. The HTTP-mediated `RegistryProtocol` is the federation axiom expressed as code. +**Why rejected**: `weft.md` §6 — no shared store. Each product owns its storage. The HTTP-mediated `RegistryProtocol` is the federation axiom expressed as code. ## Consequences ### Positive -- `loom.md` §2 "Clarion owns the file registry" becomes honest at the storage layer. -- Cross-tool "same file" queries get a deterministic answer: same ID across products under `clarion` mode. +- `weft.md` §2 "Loomweave owns the file registry" becomes honest at the storage layer. +- Cross-tool "same file" queries get a deterministic answer: same ID across products under `loomweave` mode. - Reuses ADR-029's drift vocabulary (`content_hash`); one mental model for both file-level and entity-level drift. - `local` stays default; no impact on Filigree-only deployments. ### Negative -- Two code paths per auto-create operation. Test surface doubles for file-registry behaviour (parameterise the test suite over `registry_backend ∈ {local, clarion}`). -- One synchronous RPC hop per Filigree write that touches `file_records` under `clarion` mode. Loopback HTTP cost ~1–5ms; acceptable for developer workloads, would need batched resolution for high-throughput scans (Phase E candidate). -- Cross-product launch sequencing: under `clarion` mode the operator must start Clarion's HTTP read API before Filigree, or set `--allow-local-fallback` for recovery. +- Two code paths per auto-create operation. Test surface doubles for file-registry behaviour (parameterise the test suite over `registry_backend ∈ {local, loomweave}`). +- One synchronous RPC hop per Filigree write that touches `file_records` under `loomweave` mode. Loopback HTTP cost ~1–5ms; acceptable for developer workloads, would need batched resolution for high-throughput scans (Phase E candidate). +- Cross-product launch sequencing: under `loomweave` mode the operator must start Loomweave's HTTP read API before Filigree, or set `--allow-local-fallback` for recovery. - The `migrate-registry` CLI verb is a one-way operation in practice (rollback only works inside the reversibility window). Documented as a hard boundary. ### Neutral -- Classic and loom HTTP shapes unchanged. ADR-002 generation discipline applies to shape, not ID grammar; `clarion` mode's swap of ID grammar is contract-compatible. -- `entity_associations.clarion_entity_id` is still opaque to Filigree under `clarion` mode — the two surfaces remain independent. The same Clarion entity ID may appear in both `file_records.id` (for the file the entity lives in) and `entity_associations.clarion_entity_id` (for the entity itself, e.g. a function inside that file); the relationship between them is Clarion's domain, not Filigree's. +- Classic and weft HTTP shapes unchanged. ADR-002 generation discipline applies to shape, not ID grammar; `loomweave` mode's swap of ID grammar is contract-compatible. +- `entity_associations.loomweave_entity_id` is still opaque to Filigree under `loomweave` mode — the two surfaces remain independent. The same Loomweave entity ID may appear in both `file_records.id` (for the file the entity lives in) and `entity_associations.loomweave_entity_id` (for the entity itself, e.g. a function inside that file); the relationship between them is Loomweave's domain, not Filigree's. ## Sequencing (cross-project) -The work has a fixed one-way dependency: Filigree's `clarion` mode is a no-op until Clarion ships an HTTP read API. +The work has a fixed one-way dependency: Filigree's `loomweave` mode is a no-op until Loomweave ships an HTTP read API. | Phase | Owner | Scope | |---|---|---| -| **A** | Clarion | Add an `axum`-based HTTP read server to `clarion-cli/src/serve.rs`. Expose `GET /api/v1/files?path=&language=` returning `{entity_id, content_hash, canonical_path, language}`. Wire into `clarion serve`. Surface in `clarion.yaml`. Document in Clarion's contracts directory. | +| **A** | Loomweave | Add an `axum`-based HTTP read server to `loomweave-cli/src/serve.rs`. Expose `GET /api/v1/files?path=&language=` returning `{entity_id, content_hash, canonical_path, language}`. Wire into `loomweave serve`. Surface in `loomweave.yaml`. Document in Loomweave's contracts directory. | | **B** | Filigree | Land `RegistryProtocol` interface and `LocalRegistry`; refactor `_upsert_file_record`, `register_file`, and the three `tracker.register_file` call sites to consume the protocol. Behavior-preserving — no flag yet, default-only. Schema migration adds `content_hash` and `registry_backend` columns (empty values under `local`). | -| **C** | Filigree | Add `registry_backend` config flag, `ClarionRegistry` impl, capability probe (`_schema.config_flags`), `FILIGREE_FILE_REGISTRY_DISPLACED` error code, fail-closed startup, `--allow-local-fallback` escape, the `migrate-registry` CLI verb. | -| **D** | Both | Cross-process integration tests against a live Clarion read API. Parity tests parameterised over `registry_backend ∈ {local, clarion}`. Capability-probe handshake tests. | -| **E** | Both | Documentation: Filigree `docs/federation/contracts.md` references the Clarion read surface; Clarion's `loom.md` §2 claim is restated as factual rather than aspirational; cross-project launch runbook published. | +| **C** | Filigree | Add `registry_backend` config flag, `LoomweaveRegistry` impl, capability probe (`_schema.config_flags`), `FILIGREE_FILE_REGISTRY_DISPLACED` error code, fail-closed startup, `--allow-local-fallback` escape, the `migrate-registry` CLI verb. | +| **D** | Both | Cross-process integration tests against a live Loomweave read API. Parity tests parameterised over `registry_backend ∈ {local, loomweave}`. Capability-probe handshake tests. | +| **E** | Both | Documentation: Filigree `docs/federation/contracts.md` references the Loomweave read surface; Loomweave's `weft.md` §2 claim is restated as factual rather than aspirational; cross-project launch runbook published. | Phase A must ship before Phase C can land an integration that does anything observable; B is independent and can ship first behind a flagless refactor. ## Related Decisions -- **ADR-002** (this repo) — `registry_backend` is *behaviour*, not a generation. Loom/classic HTTP shapes are unchanged; this ADR is contract-compatible by construction. -- **ADR-029 of Clarion** — entity_associations is the peer concept; this ADR closes the file-side of the same split. Same drift vocabulary (`content_hash`). -- **ADR-014 of Clarion** — original 2026-04-18 design; this ADR is its Filigree-side counterpart and adopts the design near-verbatim. -- **ADR-015 of Clarion** — Wardline→Filigree native emitter; not in scope here. Wardline's findings continue to flow via Clarion's SARIF translator under both backends. -- **Loom URI spec (draft 2026-05-17)** — orthogonal; URIs and registry-backend are independent decisions. Not yet ratified; not used as a cross-tracker reference primitive in this ADR. +- **ADR-002** (this repo) — `registry_backend` is *behaviour*, not a generation. Weft/classic HTTP shapes are unchanged; this ADR is contract-compatible by construction. +- **ADR-029 of Loomweave** — entity_associations is the peer concept; this ADR closes the file-side of the same split. Same drift vocabulary (`content_hash`). +- **ADR-014 of Loomweave** — original 2026-04-18 design; this ADR is its Filigree-side counterpart and adopts the design near-verbatim. +- **ADR-015 of Loomweave** — Wardline→Filigree native emitter; not in scope here. Wardline's findings continue to flow via Loomweave's SARIF translator under both backends. +- **Weft URI spec (draft 2026-05-17)** — orthogonal; URIs and registry-backend are independent decisions. Not yet ratified; not used as a cross-tracker reference primitive in this ADR. ## References -- Clarion ADR-014: `/home/john/clarion/docs/clarion/adr/ADR-014-filigree-registry-backend.md`. -- Clarion ADR-029: `/home/john/clarion/docs/clarion/adr/ADR-029-entity-associations-binding.md`. -- Clarion v0.1 plan §WP10: `/home/john/clarion/docs/implementation/v0.1-plan.md` (the cross-product work package this ADR fulfils). -- Sprint 2 scope amendment (defer): `/home/john/clarion/docs/implementation/sprint-2/scope-amendment-2026-05.md`. -- Clarion integration recon: `/home/john/clarion/docs/clarion/1.0/reviews/pre-restructure/integration-recon.md` (auto-create paths and FK survey). +- Loomweave ADR-014: `/home/john/loomweave/docs/loomweave/adr/ADR-014-filigree-registry-backend.md`. +- Loomweave ADR-029: `/home/john/loomweave/docs/loomweave/adr/ADR-029-entity-associations-binding.md`. +- Loomweave v0.1 plan §WP10: `/home/john/loomweave/docs/implementation/v0.1-plan.md` (the cross-product work package this ADR fulfils). +- Sprint 2 scope amendment (defer): `/home/john/loomweave/docs/implementation/sprint-2/scope-amendment-2026-05.md`. +- Loomweave integration recon: `/home/john/loomweave/docs/loomweave/1.0/reviews/pre-restructure/integration-recon.md` (auto-create paths and FK survey). - Filigree auto-create paths (verified 2026-05-19): - `src/filigree/db_files.py:640` `_upsert_file_record` - `src/filigree/db_files.py:184` `register_file` diff --git a/docs/federation/filigree-side/README.md b/docs/federation/filigree-side/README.md index a13ca6cb..eb684375 100644 --- a/docs/federation/filigree-side/README.md +++ b/docs/federation/filigree-side/README.md @@ -1,9 +1,9 @@ # Filigree-Side Federation Context -This directory mirrors Filigree-authored planning artifacts for Clarion's local +This directory mirrors Filigree-authored planning artifacts for Loomweave's local cross-reference while Sprint 3 scopes WP10. These files are read-only context copies. The authoritative sources live in -`/home/john/filigree`; update them there, then refresh this mirror if Clarion +`/home/john/filigree`; update them there, then refresh this mirror if Loomweave needs a newer snapshot. diff --git a/docs/federation/fixtures/get-api-v1-capabilities.json b/docs/federation/fixtures/get-api-v1-capabilities.json index 22104f17..ff2f9d64 100644 --- a/docs/federation/fixtures/get-api-v1-capabilities.json +++ b/docs/federation/fixtures/get-api-v1-capabilities.json @@ -1,18 +1,18 @@ { "_meta": { - "contract": "clarion-http-read-api", + "contract": "loomweave-http-read-api", "endpoint": "GET /api/v1/_capabilities", "api_version": 1, "fixture_version": 5, "stability": "normative", - "authority": "Clarion ADR-014 federation registry-backend contract; Wave 0 / WS2 linkages; Wave 1 / WS1 SEI (ADR-038); T3.4 taint-store read-by-SEI", - "verification": "cargo test -p clarion-cli --test serve", + "authority": "Loomweave ADR-014 federation registry-backend contract; Wave 0 / WS2 linkages; Wave 1 / WS1 SEI (ADR-038); T3.4 taint-store read-by-SEI", + "verification": "cargo test -p loomweave-cli --test serve", "normative": true, "updated": "2026-06-02", - "description": "Contract example for Clarion registry-backend capability discovery (advertising the WS2 call-graph linkage routes, the WS1 SEI identity capability, and the T3.4 taint-store read-by-SEI route)." + "description": "Contract example for Loomweave registry-backend capability discovery (advertising the WS2 call-graph linkage routes, the WS1 SEI identity capability, and the T3.4 taint-store read-by-SEI route)." }, "shape_decl": { - "kind": "clarion-http-fixture-shapes", + "kind": "loomweave-http-fixture-shapes", "shapes": { "capabilities_response": { "status": 200, diff --git a/docs/federation/fixtures/get-api-v1-files.demo-python.json b/docs/federation/fixtures/get-api-v1-files.demo-python.json index 3e40b41c..1cc5bad9 100644 --- a/docs/federation/fixtures/get-api-v1-files.demo-python.json +++ b/docs/federation/fixtures/get-api-v1-files.demo-python.json @@ -1,18 +1,18 @@ { "_meta": { - "contract": "clarion-http-read-api", + "contract": "loomweave-http-read-api", "endpoint": "GET /api/v1/files", "api_version": 1, "fixture_version": 3, "stability": "normative", - "authority": "Clarion ADR-014 federation registry-backend contract", - "verification": "cargo test -p clarion-cli --test serve", + "authority": "Loomweave ADR-014 federation registry-backend contract", + "verification": "cargo test -p loomweave-cli --test serve", "normative": true, "updated": "2026-05-19", - "description": "Contract examples for Clarion file identity resolution consumed by Filigree registry backends." + "description": "Contract examples for Loomweave file identity resolution consumed by Filigree registry backends." }, "shape_decl": { - "kind": "clarion-http-fixture-shapes", + "kind": "loomweave-http-fixture-shapes", "shapes": { "file_resolution": { "status": 200, @@ -100,7 +100,7 @@ "status": 404, "shape": "error_envelope", "body": { - "error": "file is not known to Clarion", + "error": "file is not known to Loomweave", "code": "NOT_FOUND" } } diff --git a/docs/federation/fixtures/post-api-v1-files-batch.json b/docs/federation/fixtures/post-api-v1-files-batch.json index 273f66f3..942dd03b 100644 --- a/docs/federation/fixtures/post-api-v1-files-batch.json +++ b/docs/federation/fixtures/post-api-v1-files-batch.json @@ -1,18 +1,18 @@ { "_meta": { - "contract": "clarion-http-read-api", + "contract": "loomweave-http-read-api", "endpoint": "POST /api/v1/files/batch", "api_version": 1, "fixture_version": 1, "stability": "normative", - "authority": "Clarion ADR-014 federation registry-backend contract (CONTRACT-1)", - "verification": "cargo test -p clarion-cli --test serve", + "authority": "Loomweave ADR-014 federation registry-backend contract (CONTRACT-1)", + "verification": "cargo test -p loomweave-cli --test serve", "normative": true, "updated": "2026-05-19", - "description": "Contract examples for Clarion bulk file identity resolution consumed by Filigree registry backends. The batch endpoint runs all lookups inside a single ReaderPool checkout; ETag is intentionally not applied; auth (CONTRACT-2) applies." + "description": "Contract examples for Loomweave bulk file identity resolution consumed by Filigree registry backends. The batch endpoint runs all lookups inside a single ReaderPool checkout; ETag is intentionally not applied; auth (CONTRACT-2) applies." }, "shape_decl": { - "kind": "clarion-http-fixture-shapes", + "kind": "loomweave-http-fixture-shapes", "shapes": { "batch_resolution": { "status": 200, diff --git a/docs/federation/fixtures/post-api-v1-files-resolve.batch.json b/docs/federation/fixtures/post-api-v1-files-resolve.batch.json index b4cbfb0f..dd6dbedf 100644 --- a/docs/federation/fixtures/post-api-v1-files-resolve.batch.json +++ b/docs/federation/fixtures/post-api-v1-files-resolve.batch.json @@ -1,18 +1,18 @@ { "_meta": { - "contract": "clarion-http-read-api", + "contract": "loomweave-http-read-api", "endpoint": "POST /api/v1/files:resolve", "api_version": 1, "fixture_version": 1, "stability": "normative", - "authority": "Clarion ADR-014 federation registry-backend contract", - "verification": "cargo test -p clarion-cli --test serve", + "authority": "Loomweave ADR-014 federation registry-backend contract", + "verification": "cargo test -p loomweave-cli --test serve", "normative": true, "updated": "2026-05-20", "description": "Contract example for per-path batch file identity resolution. The endpoint preserves input order and returns one status/body pair per requested path." }, "shape_decl": { - "kind": "clarion-http-fixture-shapes", + "kind": "loomweave-http-fixture-shapes", "shapes": { "resolve_batch": { "status": 200, @@ -77,7 +77,7 @@ "response": { "status": "not_found", "body": { - "error": "file is not known to Clarion", + "error": "file is not known to Loomweave", "code": "NOT_FOUND" } } diff --git a/docs/federation/fixtures/sei-conformance-oracle.json b/docs/federation/fixtures/sei-conformance-oracle.json index 3a54e148..0ea57702 100644 --- a/docs/federation/fixtures/sei-conformance-oracle.json +++ b/docs/federation/fixtures/sei-conformance-oracle.json @@ -1,17 +1,17 @@ { "_meta": { - "contract": "loom-sei-conformance-oracle", - "standard": "Loom Stable Entity Identity (SEI) conformance standard §8", - "authority": "Clarion ADR-038 (token/signature/persistence/reserved-namespace); SEI standard (suite-wide)", + "contract": "weft-sei-conformance-oracle", + "standard": "Weft Stable Entity Identity (SEI) conformance standard §8", + "authority": "Loomweave ADR-038 (token/signature/persistence/reserved-namespace); SEI standard (suite-wide)", "fixture_version": 1, "stability": "normative", "token_format_agnostic": true, - "verification": "cargo test -p clarion-storage --test sei_conformance_oracle", + "verification": "cargo test -p loomweave-storage --test sei_conformance_oracle", "updated": "2026-06-02", - "description": "The six shared SEI conformance scenarios every Loom tool runs against a reference Clarion. Asserts BEHAVIOUR and OPACITY, never the SEI's internal form. A subsystem is SEI-conformant only when it passes all six (no grandfathering)." + "description": "The six shared SEI conformance scenarios every Weft tool runs against a reference Loomweave. Asserts BEHAVIOUR and OPACITY, never the SEI's internal form. A subsystem is SEI-conformant only when it passes all six (no grandfathering)." }, "invariants": [ - "SEI is opaque: a consumer never parses it. It carries the reserved `clarion:eid:` prefix and is NOT a locator.", + "SEI is opaque: a consumer never parses it. It carries the reserved `loomweave:eid:` prefix and is NOT a locator.", "Fail-closed: when sameness cannot be PROVEN, mint a new SEI and orphan the old one — never silently re-point.", "Lineage is append-only: born / locator_changed / moved / orphaned / superseded.", "Identity is carried (never re-minted) for an unchanged locator; SEI values are not part of the byte-identical-run guarantee, but carry/mint decisions are deterministic given the same bindings + source." @@ -24,7 +24,7 @@ "expect": { "resolve_locator": { "sei": "", "current_locator": "", "content_hash": "", "alive": true }, "resolve_sei": { "current_locator": "", "alive": true }, - "opacity": "the returned `sei` begins with `clarion:eid:` and is treated as an opaque string by the consumer (never parsed); it is not equal to the locator" + "opacity": "the returned `sei` begins with `loomweave:eid:` and is treated as an opaque string by the consumer (never parsed); it is not equal to the locator" } }, { @@ -73,7 +73,7 @@ }, { "id": "capability_absent", - "given": "A Clarion instance that has not populated SEI (pre-SEI DB, or `_capabilities.sei.supported` false / absent).", + "given": "A Loomweave instance that has not populated SEI (pre-SEI DB, or `_capabilities.sei.supported` false / absent).", "when": "A consumer probes `_capabilities` and/or resolves.", "expect": { "consumer": "detects the absent capability and DEGRADES gracefully — keeps working on locators, no crash, honest 'identity unavailable'", diff --git a/docs/federation/fixtures/wardline-qualname-normalization.json b/docs/federation/fixtures/wardline-qualname-normalization.json index 2c50a6af..a26f6e12 100644 --- a/docs/federation/fixtures/wardline-qualname-normalization.json +++ b/docs/federation/fixtures/wardline-qualname-normalization.json @@ -1,9 +1,9 @@ { "$comment": [ - "Normative parity fixture for the Wardline -> Clarion entity-reconciliation", + "Normative parity fixture for the Wardline -> Loomweave entity-reconciliation", "contract (ADR-018 2026-05-29 amendment, integration brief §4.A).", "Wardline's native Filigree emitter sets metadata.wardline.qualname to the", - "PRE-COMPOSED dotted form so it byte-matches Clarion's canonical_qualified_name.", + "PRE-COMPOSED dotted form so it byte-matches Loomweave's canonical_qualified_name.", "Each vector below is a (file_path, qualname) input and the exact dotted form", "Wardline must emit. Divergence on any vector silently degrades reconciliation", "to resolution_confidence: heuristic|none — there is no error, only a worse match.", @@ -12,7 +12,7 @@ "implemented as of release:1.1. Mirrors the role of fixtures/entity_id.json." ], "rules_source": { - "module_normalization_fn": "plugins/python/src/clarion_plugin_python/extractor.py::module_dotted_name", + "module_normalization_fn": "plugins/python/src/loomweave_plugin_python/extractor.py::module_dotted_name", "module_normalization_tests": "plugins/python/tests/test_extractor.py::test_module_dotted_name_helper", "composition": "expected_qualified_name = module_dotted_name(file_path) + '.' + qualname (qualname is Python __qualname__, copied verbatim incl. '' and dotted class chains)", "module_rules": [ @@ -21,7 +21,7 @@ "An '__init__' filename collapses to its containing package (UQ-WP3-06).", "Remaining path separators become '.'.", "No other root marker (lib/, app/, tests/, the project name, ...) is stripped.", - "A top-level '__init__.py' normalizes to '' and is REJECTED by entity_id() — Clarion skips the file; Wardline must not emit such an entity." + "A top-level '__init__.py' normalizes to '' and is REJECTED by entity_id() — Loomweave skips the file; Wardline must not emit such an entity." ], "conformance_oracle": "GET /api/v1/entities/resolve?scheme=wardline_qualname (DEFERRED; not implemented at release:1.1)" }, @@ -81,7 +81,7 @@ "description": "REJECTED: top-level __init__.py normalizes to empty and is skipped", "file_path": "__init__.py", "expected_module": "", - "note": "entity_id() rejects an empty qualified_name (crates/clarion-core/src/entity_id.rs). Clarion emits no entity for this file; a conformant Wardline emitter must not emit one either." + "note": "entity_id() rejects an empty qualified_name (crates/loomweave-core/src/entity_id.rs). Loomweave emits no entity for this file; a conformant Wardline emitter must not emit one either." }, { "description": "REJECTED: src/__init__.py normalizes to empty after src-strip", diff --git a/docs/federation/sei-migration-playbook.md b/docs/federation/sei-migration-playbook.md index 1fa3b9f5..025f23ca 100644 --- a/docs/federation/sei-migration-playbook.md +++ b/docs/federation/sei-migration-playbook.md @@ -5,15 +5,15 @@ cross-tool release*, not a self-service migration. Do **not** fire it unilaterally; it is scheduled by the suite owner once Filigree and Wardline are ready to cut at the same time. -**Authority:** Loom SEI conformance standard §7 / §7.1; Clarion ADR-038. +**Authority:** Weft SEI conformance standard §7 / §7.1; Loomweave ADR-038. --- ## What this migration does -The Loom suite is moving every cross-tool binding off the mutable **locator** +The Weft suite is moving every cross-tool binding off the mutable **locator** (`{plugin}:{kind}:{qualname}`) and onto the durable, opaque **SEI** -(`clarion:eid:`). Before the cutover, producers store and emit locators; +(`loomweave:eid:`). Before the cutover, producers store and emit locators; after it, they store and emit SEIs. There is deliberately **no mixed-format window** — a single hard cutover, because one owner controls all four release cycles (§7.1). A feed that emitted a *mix* of locators and SEIs would be @@ -23,23 +23,23 @@ uninterpretable, since every consumer treats the id as opaque. | Subsystem | Role in the cutover | |---|---| -| **Clarion** (authority) | Mints an SEI for **every** current entity on its first SEI-aware analyze run, and serves `resolve(locator)` so producers can re-key. This is the only Clarion action — it has no cross-tool bindings of its own to re-key. | -| **Filigree** | Re-keys every stored `clarion_entity_id` association from its locator to the resolved SEI. | +| **Loomweave** (authority) | Mints an SEI for **every** current entity on its first SEI-aware analyze run, and serves `resolve(locator)` so producers can re-key. This is the only Loomweave action — it has no cross-tool bindings of its own to re-key. | +| **Filigree** | Re-keys every stored `loomweave_entity_id` association from its locator to the resolved SEI. | | **Wardline** | Re-keys every stored taint fact (and dossier handle) from its locator to the resolved SEI. | | **legis** (when present) | Re-keys governance attestations onto SEIs. | -## Clarion's side (already shipped in Wave 1) +## Loomweave's side (already shipped in Wave 1) 1. **Mint-all-on-first-run.** The analyze SEI mint pass (ADR-038 §3) mints an `alive` `sei_bindings` row for every current entity on the first SEI-aware run, and carries (never re-mints) them on every subsequent unchanged run. No - separate "backfill job" is needed on Clarion's side — minting *is* the + separate "backfill job" is needed on Loomweave's side — minting *is* the first-run behaviour, and it is **idempotent** (a re-run carries every SEI; proven by `analyze_carries_sei_on_unchanged_rerun`). 2. **Resolution surface.** `POST /api/v1/identity/resolve` maps a locator to its alive SEI. Producers drive their re-key off this endpoint. 3. **Reserved-prefix rejection (REQ-F-02) — the safety interlock.** `resolve` - **rejects** any SEI-shaped input (reserved `clarion:eid:` prefix) with `400`. + **rejects** any SEI-shaped input (reserved `loomweave:eid:` prefix) with `400`. This is what makes each producer's backfill **idempotent and resumable**: an already-migrated row whose value is now an SEI is *rejected*, never mis-resolved, so re-running a partially-completed backfill is safe. @@ -47,11 +47,11 @@ uninterpretable, since every consumer treats the id as opaque. ## Producer backfill protocol (Filigree / Wardline / legis) Each producer owns its **own** progress cursor (a rowid or a migration-state -side table). No Clarion-side generation marker is required. +side table). No Loomweave-side generation marker is required. For each stored binding the producer holds: -1. Read the stored id. If it already begins with `clarion:eid:`, it is **already +1. Read the stored id. If it already begins with `loomweave:eid:`, it is **already migrated** — skip (this is why the backfill is resumable). 2. Call `POST /api/v1/identity/resolve` (or `…/resolve:batch`) with the locator. 3. On `{ alive: true, sei }` → rewrite the stored id to `sei`, advance the cursor. @@ -67,13 +67,13 @@ skipped (step 1 / step 5), so it converges. ## Cutover sequencing (owner-gated, single coordinated release) 1. **Freeze.** Quiesce writes across the producers being cut. -2. **Clarion cuts first.** Deploy SEI-aware Clarion and run analyze so every +2. **Loomweave cuts first.** Deploy SEI-aware Loomweave and run analyze so every entity has an alive SEI (verify via `_capabilities.sei.supported: true` and a non-empty `sei_bindings`). 3. **Producers backfill** their stored ids locator→SEI (protocol above), each to completion, surfacing ORPHANs. 4. **Flip the feeds.** Every federation feed that carries entity ids - (e.g. Filigree's `affected_entities` on `GET /api/loom/changes`) switches to + (e.g. Filigree's `affected_entities` on `GET /api/weft/changes`) switches to emitting **only SEIs** — never a mix. 5. **Unfreeze.** Resume writes; from here all new bindings key on SEIs. @@ -82,8 +82,8 @@ skipped (step 1 / step 5), so it converges. - **Conformance:** the shared SEI oracle ([`fixtures/sei-conformance-oracle.json`](./fixtures/sei-conformance-oracle.json)) must pass for each subsystem before it is declared conformant (no - grandfathering). Clarion's pass: - `cargo test -p clarion-storage --test sei_conformance_oracle`. + grandfathering). Loomweave's pass: + `cargo test -p loomweave-storage --test sei_conformance_oracle`. - **Idempotency / resumability** rests entirely on the REQ-F-02 rejection contract; do not relax it. - **Rollback** is a coordinated re-deploy of the prior producer builds; because @@ -95,5 +95,5 @@ skipped (step 1 / step 5), so it converges. This procedure is **surfaced for owner scheduling**, not executed here. It runs only when Filigree and Wardline are both ready to cut in the same release. Until -then Clarion mints and serves SEIs additively (enrich-only); consumers that have +then Loomweave mints and serves SEIs additively (enrich-only); consumers that have not yet cut keep working on locators and degrade on `_capabilities.sei`. diff --git a/docs/implementation/2026-06-05-loomweave-1.0-rename-and-pypi-plan.md b/docs/implementation/2026-06-05-loomweave-1.0-rename-and-pypi-plan.md new file mode 100644 index 00000000..0562ee07 --- /dev/null +++ b/docs/implementation/2026-06-05-loomweave-1.0-rename-and-pypi-plan.md @@ -0,0 +1,211 @@ +# Loomweave 1.0.0 — Rename + PyPI Distribution (Master Plan) + +> **For agentic workers:** Phases B–C use checkbox (`- [ ]`) tasks. Phase B (the rename) MUST land as ONE atomic branch over a coordination/freeze window — do not drip it across small PRs (a ~400-file rename races the concurrent agent and is a conflict bomb). Phase C may ride the same branch or a follow-up, but the first PyPI publish happens only AFTER the rename + version recut. + +**Goal:** Ship the product formerly called *Clarion* as **Loomweave 1.0.0** on PyPI — `uvx loomweave install --path .` installs the Rust binary + Python plugin and `loomweave analyze` discovers/spawns the plugin with no extra flags — consistent with the **Weft** framework (Filigree, Wardline) which are PyPI-native. (Naming: the **Weft** framework — formerly "Loom" — comprises **Loomweave** (this product, formerly Clarion), Filigree, Wardline, Shuttle.) + +**Why one plan:** the PyPI distribution work and the Clarion→Loomweave rename are interdependent. The PyPI package *name* and *version* are exactly what the rename changes (`clarion`→`loomweave`, `1.3.0`→`1.0.0`), and `clarion-plugin-python` is **unpublished**, so the only correct order is **rename first, publish second**. Publishing `clarion` then renaming would be the worst outcome (renaming a live PyPI package + abandoned name = the naming tech-debt we explicitly avoid). + +**Supersedes:** `docs/superpowers/plans/2026-06-05-clarion-pypi-distribution.md` and `docs/implementation/2026-06-05-clarion-to-loomweave-rename-plan.md` (both folded in here). +**Design rationale:** `docs/superpowers/specs/2026-06-05-loomweave-pypi-distribution-design.md` (names to be updated to loomweave — Phase C Task C0). + +--- + +## Sequencing overview + +| Phase | What | State | Atomicity | +|-------|------|-------|-----------| +| **A** | PyPI-enabling foundation (discovery `current_exe()` level + doctor real-discovery) | **DONE** (commits `cecc134`, `7305af9` on `docs/pypi-distribution-spec`) | already landed; rename-agnostic logic | +| **B** | Atomic rename Clarion → Loomweave + version recut to 1.0.0 | not started | **one branch, freeze window** | +| **C** | PyPI packaging against `loomweave` / `1.0.0` | not started | rides B's branch or follows it | +| **D** | External infra + first publish (owner actions) | not started | post-rename | + +Phase A's committed code contains literal `clarion-plugin-`/`share/clarion/` strings; **Phase B's mechanical pass renames those like everything else**. The *logic* is final — no rework. Land/merge Phase A's branch into the rename branch (or cherry-pick its two commits) so the foundation is part of the atomic landing. + +--- + +## Phase A — PyPI-enabling foundation (DONE) + +Recorded for completeness; already implemented and reviewed (spec-compliance + code-quality) on branch `docs/pypi-distribution-spec`. + +- [x] **`current_exe()`-relative plugin discovery level** (`crates/clarion-core/src/plugin/discovery.rs`, commit `cecc134`). `discover()` now scans the directory of the running binary in addition to `$PATH`, so a co-located (venv) plugin is found without being on `$PATH` — the change that makes a one-command PyPI install work. `$PATH` first (shadowing preserved), world-writable refusal + dedup intact. Tests t2b/t2c/t2d. +- [x] **doctor reports availability via real discovery** (`crates/clarion-cli/src/doctor.rs`, commit `7305af9`). `check_plugin_availability_json()` now uses `discover()` (PATH + current_exe) instead of a hand-rolled `$PATH` scan, so `doctor` agrees with `analyze` about co-located plugins. Integration test `crates/clarion-cli/tests/discovery_co_located.rs` proves off-`$PATH` discovery end-to-end with the real binary. +- ADR-021 amendment (the discovery source) is folded into Phase B WS4 (the ADR file gets swept by the rename anyway). + +--- + +## Phase B — Atomic rename: Clarion → Loomweave + recut to 1.0.0 + +**Scale:** ~403 working-set files mention `clarion` (after excluding `target/`/`.git`/caches/`site-build`). This is a **triage problem, not 11k mechanical edits** — the bulk renames; a handful of buckets must NOT be touched; a few are cross-product. + +### B.1 Decisions locked (from owner) + +| Axis | Decision | Consequence | +|------|----------|-------------| +| Filigree tracker prefix | **Code-side only.** Keep the `clarion-` issue-ID prefix. | `.filigree.conf` `prefix`/`project_name` stay `clarion`. Issue IDs (`clarion-XXXX`) in docs/commits are **do-not-rename**. Only actor strings + the `clarion_entity_id` federation field move. | +| Persisted on-disk state | **Clean break — no users yet** (1.0 soft launch; we're at 1.3). | No migration shim, no path auto-detect, no dual-magic. Free to change `.clarion/`→`.loomweave/` and the DB magic outright. | +| External contracts | **Big-bang hard cut.** No aliases. | No `clarion` binary symlink, no plugin-prefix fallback, no MCP server-name alias. One atomic landing. | +| Framework/suite "Loom" name | **Renamed `Loom` → `Weft`** (cloth-themed, broader than the weaving-device "loom"). | Product vars `CLARION_*`→`LOOMWEAVE_*`; framework/federation vars `CLARION_LOOM_*`→`WEFT_*`. No double-loom (framework is no longer "Loom"). | +| Version | **Recut to 1.0.0** (1.3 Clarion ≡ 1.0 Loomweave). | Reset `version = "1.3.0"`→`"1.0.0"` in `Cargo.toml` + all `pyproject.toml`; CHANGELOG gets a Loomweave 1.0.0 header re-baselining history. | + +**Naming (resolved):** the framework is renamed "Loom" → **`Weft`**. "Loomweave" keeps "loom" (the weaving device) but no longer duplicates the framework name, so the former double-loom env-var concern is gone — framework/federation vars rename to `WEFT_*`. Hierarchy: **Weft** framework › **Loomweave** (flagship, was Clarion), Filigree, Wardline, Shuttle. + +### B.2 Reference triage recipe (verifiable, not asserted) + +```bash +for pat in clarion Clarion CLARION clarion_ clarion-; do + echo "== $pat ==" + grep -rl "$pat" . | grep -vE '/(target|\.git|\.mypy_cache|\.pytest_cache|\.ruff_cache|site-build)/' +done +``` + +| Variant | Renames to | Notes | +|---------|-----------|-------| +| `clarion` | `loomweave` | bulk (crate dirs, paths, binary) | +| `Clarion` | `Loomweave` | bulk (prose, titles, `site_name`) | +| `CLARION` | `LOOMWEAVE` | **minus the LOOM carve-out** | +| `clarion_` | `loomweave_` | bulk, but `clarion_entity_id` is cross-product (WS6) | +| `clarion-` | `loomweave-` | crate names, plugin prefix — but **Filigree issue IDs are do-not-rename** | + +### B.3 Workstreams (WS1–WS3 are load-bearing) + +- [ ] **WS1 — Cargo workspace & crate rename (`crates/`, ~126 files)** + - `git mv` 9 crates+dirs `crates/clarion-{core,cli,mcp,storage,analysis,federation,scanner,plugin-fixture}` and the `clarion` umbrella → `loomweave-*` (history preservation; matches the "renames over filename tech-debt" stance). + - Update every `Cargo.toml` `name =`, `[[bin]] name = "clarion"`→`"loomweave"`, intra-workspace `path`/`dependencies` keys, and all `use clarion_*::` paths. Regenerate `Cargo.lock`. + - **Gate:** `cargo build --workspace && cargo nextest run` green. +- [ ] **WS2 — Persisted identity & config (clean break)** + - `.clarion/`→`.loomweave/`; `clarion.db`→`loomweave.db`; `.clarion/instance_id`; `clarion.yaml`→`loomweave.yaml`. + - SQLite `application_id` magic `0x434C524E ("CLRN")`: **OPEN DECISION** — change to `LMWV` (`0x4C4D5756`) or keep `CLRN`. Clean break makes change free; keeping avoids touching the `ForeignDatabase` guard + tests. *Recommend change.* (`crates/.../storage/src/{error,reader,pragma,schema}.rs`.) + - Update `clarion.yaml` loaders + `CLARION_YAML_*` plumbing (overlaps WS5). +- [ ] **WS3 — MCP server identity (`crates/clarion-mcp`)** + - `serverInfo.name` `"clarion"`→`"loomweave"` (`lib.rs:2421`, asserts `:4978`,`:5741`). + - `clarion-workflow` prompt name→`loomweave-workflow` (`lib.rs:2445/2457/5218/5226/5253`) **and** the `.claude` skill + `CLARION_WORKFLOW_SKILL` env. + - Default actor `"clarion"`→`"loomweave"` (`lib.rs:1416`). + - **`.mcp.json`** (repo root): server key `clarion`→`loomweave`, `command` `…/bin/clarion`→`…/bin/loomweave`. (Wardline's `--clarion-url` in the same file is cross-product — WS6.) +- [ ] **WS4 — Plugin contract (hard cut) — *the PyPI prerequisite*** + - Discovery prefix `clarion-plugin-*`→`loomweave-plugin-*` (`crates/.../core/src/plugin/discovery.rs` — including the Phase A `current_exe()` code, `mod.rs` doc, the **ADR-021 §L9 amendment** for the discovery source). + - Python plugin package `clarion-plugin-python`→**`loomweave-plugin-python`** (`plugins/python/pyproject.toml` name, module `clarion_plugin_python`→`loomweave_plugin_python`, ~264 refs, shared-data path `share/clarion/plugins/python/`→`share/loomweave/plugins/python/`). + - Fixture crate + test plugins (`clarion-plugin-{bogus,partial,cross-file,x,mocktest,secretfixture}` in `analyze_failure_modes.rs`, `discovery_co_located.rs`, etc.). + - **This is the package Phase C builds and publishes.** Because it is unpublished, fixing the name here (before Phase C) costs nothing; renaming post-publish would be costly. +- [ ] **WS5 — Environment variables (`CLARION_*`→`LOOMWEAVE_*`, ~25)** — mechanical **except the LOOM carve-out**: + - Rename: `CLARION_CODEX_CONFIG`, `CLARION_APPLICATION_ID`, `CLARION_SCAN_SOURCE`, `CLARION_LLM_LIVE`, `CLARION_PLUGIN_FILE_TIMEOUT_MS`, `CLARION_HTTP_{URL,BIND}`, `CLARION_YAML_STUB`, `CLARION_GUIDANCE_PROPOSAL_V`, `CLARION_FIXTURE_EXCEED_RLIMIT_AS`, `CLARION_FILIGREE_MCP_COMMAND`, `CLARION_DOTENV_SENTINEL`, `CLARION_TEST_IDENTITY`, `CLARION_TEST_FIXTURE_BATCH_TOKEN`, `CLARION_LOOPBACK_NO_TOKEN_TEST_UNSET`. + - **CARVE OUT — rename to the suite name, NOT `LOOMWEAVE_`:** `CLARION_LOOM_TOKEN`, `CLARION_LOOM_IDENTITY_SECRET`, `CLARION_TEST_LOOM_{TOKEN_*,IDENTITY_*}` name the federation/suite concept, not the product. Rename `CLARION_LOOM_*`→`WEFT_*` (e.g. `WEFT_TOKEN`), not `LOOMWEAVE_*` (that's the product) and not `LOOMWEAVE_LOOM_*` (double-loom). Same for the `/api/loom/...` wire paths if the suite concept is renamed there too — but those are a versioned federation contract, so treat as a coordinated cross-product change (keep `loom` there until Wardline/Filigree move in lockstep). +- [ ] **WS6 — Federation / cross-product (`crates/clarion-federation`)** + - `clarion_entity_id` field (`filigree.rs:78`) — the **ADR-029 entity-association contract field** consumed by Filigree's read path. Renaming is cross-product: verify Filigree treats `entity_id` as opaque (it should) before moving. + - Actor `"clarion-mcp"` (`scan_results.rs:599`)→`"loomweave-mcp"`. + - `--clarion-url` flag that **Wardline** passes (`.mcp.json`) — coordinated change in the Wardline repo (same owner); do not break unilaterally. + - **Leave** `/api/loom/scan-results` + `api_version` wire paths (federation contract, not product). +- [ ] **WS7 — CLI/UX strings, README, CHANGELOG, version recut** + - `README.md`, CLI help/usage, `LICENSE` (if it names Clarion). + - `CHANGELOG.md`: rebrand header; add `## [1.0.0] — Loomweave` explaining the rename + version reset. + - `version = "1.3.0"`→`"1.0.0"` in `Cargo.toml`, `plugins/python/pyproject.toml`, and (Phase C) `crates/loomweave-cli/pyproject.toml`. +- [ ] **WS8 — Docs + web (`docs/` ~213, `web/` ~12)** + - `web/mkdocs.yml`: `site_name`/`site_url`/`repo_url`/`repo_name`. + - `docs/**` prose, `docs/clarion/`→`docs/loomweave/` (`git mv`), implementation/federation/operator/suite. + - **`docs/archive/`**: rebrand only living docs; leave archived dated reports as historical record (they also carry `clarion-XXXX` issue IDs — do-not-rename). +- [ ] **WS9 — Tests & fixtures (`tests/` ~31 + in-crate)** + - `.clarion`/`clarion.yaml`/`clarion.db` path literals (`serve.rs`, `catalogue_tools.rs`, `status.rs`, …). + - `tests/e2e`, `tests/perf` corpus refs. + - Verify no golden/snapshot key relies on the literal `clarion` before mass-replacing fixtures. + +### B.4 Cross-product & coordination constraints +1. **Concurrent agent (Antigravity)** commits in real time. Land the rename as **one atomic branch over a freeze window**; do not drip across small PRs. +2. **Wardline** passes `--clarion-url` (sibling product) — coordinate the flag rename. +3. **Filigree** `.filigree.conf` (`project_name`/`prefix: clarion`) **stays** — only `clarion_entity_id` + actor strings move; verify Filigree's read path treats `entity_id` as opaque. + +### B.5 DO NOT RENAME (traps a naive replace corrupts) +- **Filigree issue IDs** `clarion-[0-9a-f]{8,}` / `clarion-sf-*` (docs, CHANGELOG, commits, ADR filenames) — historical identifiers; prefix stays `clarion` by decision. +- **Federation `*LOOM*` env vars** (WS5 carve-out). +- **`/api/loom/...` wire paths + `api_version`** — federation contract. +- **`docs/archive/` dated reports** — historical record. +- **`.filigree.conf` `project_name`/`prefix`**. +- Recorded **test corpus / golden snapshot** identifiers that double as keys (verify first). + +### B.6 Execution strategy + verification gate +1. One atomic branch (`rename/clarion-to-loomweave`), freeze window coordinated with the concurrent agent. Merge/cherry-pick the Phase A commits onto it. +2. Mechanical pass per case-variant with do-not-rename buckets excluded by path/regex. +3. `git mv` for crate dirs + `docs/clarion/` (history). +4. Manual review of every carve-out: LOOM env vars, `clarion_entity_id`, `--clarion-url`, Filigree issue IDs, archive docs, DB magic. +5. **Verification gate (all must pass before merge):** + ```bash + cargo build --workspace + cargo nextest run + (cd plugins/python && pytest) + # residual audit — every remaining hit must be an intentional carve-out: + grep -rniE 'clarion' . | grep -vE '/(target|\.git|.*cache|site-build)/' \ + | grep -vE 'clarion-[0-9a-f]{8}|clarion-sf-|LOOM|\.filigree|docs/archive' + ``` + Residual audit returning **only** known carve-outs is the completeness proof. +6. Re-run `loomweave analyze` to rebuild `.clarion/`→`.loomweave/`; wipe stale `.clarion/` first. + +--- + +## Phase C — PyPI packaging (against `loomweave` / `1.0.0`) + +Runs after (or as the tail of) Phase B, so all names are final. Topology (unchanged from the design): two PyPI projects — **`loomweave`** (maturin `bindings="bin"` platform wheels of the Rust binary) depending on **`loomweave-plugin-python`** (pure-Python wheel). Wheel matrix = standard-4 (linux x86_64 + aarch64, macOS via universal2). Publish via PyPI Trusted Publishing (OIDC, PEP 740). Keep cosign GitHub Release tarballs as the offline channel. + +- [x] **C0 — Update the design spec to loomweave names.** In `docs/superpowers/specs/2026-06-05-loomweave-pypi-distribution-design.md`, replace `clarion`→`loomweave`, `clarion-plugin-python`→`loomweave-plugin-python`, `1.3.0`→`1.0.0`, `share/clarion/`→`share/loomweave/`. (WS4's "fix the spec before publish".) +- [x] **C1 — `loomweave-plugin-python` wheel.** `uv build --wheel --project plugins/python --out-dir dist/`. Verify the wheel ships `plugin.toml` shared-data at `…data/data/share/loomweave/plugins/python/plugin.toml` (`python -m zipfile -l`). Fix `[tool.hatch.build.targets.wheel.shared-data]` if the path didn't follow the rename. ✅ wheel ships `…data/data/share/loomweave/plugins/python/plugin.toml` — path followed the rename. +- [x] **C2 — Version-lockstep guard.** Extend `scripts/check-workspace-version-lockstep.py` to assert `crates/loomweave-cli/pyproject.toml` declares `loomweave-plugin-python==1.0.0` and that all package versions equal the workspace version. Add `--self-test` fixtures. Run self-test + live. ✅ guard extended (cli version + `==` pin), `--self-test` (6 cases) + live both green; already wired into ci.yml + release.yml. +- [x] **C3 — `loomweave` maturin bin-wheel.** Create `crates/loomweave-cli/pyproject.toml`: + ```toml + [build-system] + requires = ["maturin>=1.7,<2"] + build-backend = "maturin" + + [project] + name = "loomweave" + version = "1.0.0" + description = "Loomweave — graph-aware code archaeology (Rust core)" + readme = "../../README.md" + requires-python = ">=3.11" + license = { text = "MIT" } + authors = [{ name = "John Morrissey", email = "qacona@gmail.com" }] + classifiers = ["Development Status :: 4 - Beta", "Programming Language :: Rust", "Programming Language :: Python :: 3"] + dependencies = ["loomweave-plugin-python==1.0.0"] + + [project.urls] + Repository = "https://github.com/foundryside-dev/loomweave" + + [tool.maturin] + bindings = "bin" + manifest-path = "Cargo.toml" + bins = ["loomweave"] + strip = true + ``` + Build locally: `cd crates/loomweave-cli && uvx maturin build --release`. Verify the wheel carries `…data/scripts/loomweave`. ✅ created; `maturin build --release` → `loomweave-1.0.0-py3-none-manylinux_2_39_x86_64.whl` carrying `…data/scripts/loomweave`. +- [x] **C4 — Local install-and-spawn proof (KEYSTONE GATE).** ✅ PASSED — clean venv install of both wheels; `env -i HOME=/tmp /tmp/lw/bin/loomweave doctor --format json` → `plugin.availability: ok, "1 language plugin discovered: python"` (venv `bin/` off the scrubbed `$PATH`, so discovery came through the Phase A `current_exe()` level). + ```bash + python -m venv /tmp/lw && /tmp/lw/bin/pip install \ + dist/loomweave_plugin_python-1.0.0-py3-none-any.whl \ + target/wheels/loomweave-1.0.0-*.whl + env -i HOME=/tmp /tmp/lw/bin/loomweave doctor --format json + ``` + Expected: `plugin.availability` reports the `python` plugin discovered via the Phase A `current_exe()` level (the venv `bin/` is not on the scrubbed `$PATH`). If this fails, STOP — the topology is broken and must be revisited before CI. *(This is the local equivalent of the design's §7 gate; the Phase A integration test already proves the mechanism with a staged binary.)* +- [ ] **C5 — CI wheels workflow** (`.github/workflows/wheels.yml`): build `loomweave-plugin-python` wheel + `loomweave` standard-4 wheels (PyO3/maturin-action; linux aarch64 via zig or native arm64 runner; macOS universal2). Add a clean-room `loomweave` sdist build test → decide keep/drop sdist (fall back to GitHub Release/cargo-binstall for off-matrix). Validate via `workflow_dispatch` on the branch (`gh workflow run` + `gh run watch`), download a wheel, repeat the C4 proof. +- [ ] **C6 — Publish job (Trusted Publishing, gated).** In the wheels workflow, gate on the existing `verify` job (tag-on-main ancestry + CI). On `v*` tag: **publish `loomweave-plugin-python` FIRST, then `loomweave`** (so the `==1.0.0` pin resolves on first release). Use `pypa/gh-action-pypi-publish@release/v1` with `permissions: id-token: write`, `environment: pypi`, `attestations: true`. Recommended: merge these jobs into `release.yml` after `verify` for a single tag-triggered workflow. +- [ ] **C7 — `loomweave install` Node cache-warm + README.** Best-effort prime of pyright's `nodeenv` Node fetch during `loomweave install` (gated by `--no-cache-warm`, non-fatal on failure). Rewrite the README install section to `uvx loomweave install --path .` (+ `pipx` equivalent), with the cosign-signed GitHub Release tarball as the offline/verified-binary path. Note the first-run Node fetch. +- [ ] **C8 — TestPyPI dry-run gate.** Reserve TestPyPI trusted publishers; publish to TestPyPI from a pre-release tag/dispatch; on linux-x64, linux-arm64, macOS run `uvx --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ loomweave doctor --format json` and confirm plugin discovery. Revert the TestPyPI override. + +--- + +## Phase D — External infra + go-live (owner actions; cannot be scripted) + +- [x] **D1 — Reserve PyPI names** `loomweave` and `loomweave-plugin-python` — **done** (owner reserved; this is what fixed the rename naming). +- [ ] **D2 — Register Trusted Publishers** for both projects on PyPI + TestPyPI: org `foundryside-dev`, repo `loomweave`, workflow `wheels.yml` (or `release.yml`), environment `pypi`. (Reserve the TestPyPI names too.) +- [ ] **D3 — Rename the GitHub repo** `tachyon-beep/clarion`→`foundryside-dev/loomweave` (GitHub keeps redirects); update `repo_url`/`Repository` everywhere. +- [ ] **D4 — DNS/site** `clarion.foundryside.dev`→`loomweave.foundryside.dev` (DNS + `web/mkdocs.yml` site config). +- [ ] **D5 — Coordinate Wardline** for the `--clarion-url`→`--loomweave-url` flag change (sibling repo, same owner). +- [ ] **D6 — Cut `v1.0.0`** → CI publishes (plugin first). Confirm `pip index versions loomweave` and `loomweave-plugin-python` show 1.0.0. + +--- + +## Consolidated open decisions (resolve before/at execution) +1. **DB magic:** change `CLRN`→`LMWV` or keep? (Free either way; recommend change.) — WS2. +2. ✅ **Framework "Loom" rename:** RESOLVED — renamed to **`Weft`**; `CLARION_LOOM_*`→`WEFT_*`. The `/api/loom/...` wire path stays until cross-product (Wardline/Filigree) lockstep. — WS5. +3. ✅ **PyPI names:** RESOLVED — `loomweave` + `loomweave-plugin-python` reserved. Remaining owner actions: GitHub repo rename → `foundryside-dev/loomweave` (D3) + DNS (D4) before Phase C publish. +4. ✅ **Sequencing:** RESOLVED — rename (Phase B) lands **before** the 1.0.0 cut. Naming double-loom concern dissolved (suite no longer "Loom"). +5. ✅ **Framework name:** RESOLVED — **Weft**. Hierarchy: Weft (framework) › Loomweave (flagship), Filigree, Wardline, Shuttle. +5. **Wheel sdist:** keep the `loomweave` sdist (clean-room build passes) or drop and route off-matrix to GitHub Release/binstall. — C5. +6. **Node hermeticity:** v1 documents the first-run fetch; tracked follow-up to bundle Node via `nodejs-wheel`. — C7. +``` diff --git a/docs/implementation/README.md b/docs/implementation/README.md index 044944ea..debd2e77 100644 --- a/docs/implementation/README.md +++ b/docs/implementation/README.md @@ -1,8 +1,8 @@ # Implementation Archive -This folder is the consolidated archive of Clarion's implementation and planning history. It is **not** part of the release-facing doc surface — readers entering via [`docs/README.md`](../README.md) and the [Clarion 1.0 docset](../clarion/1.0/README.md) are not expected to need anything here. +This folder is the consolidated archive of Loomweave's implementation and planning history. It is **not** part of the release-facing doc surface — readers entering via [`docs/README.md`](../README.md) and the [Loomweave 1.0 docset](../loomweave/1.0/README.md) are not expected to need anything here. -Material is kept rather than deleted because the [ADRs](../clarion/adr/README.md) cite it for historical context (panel reviews, the v0.1 scope-commitment memo, sprint plans, and agent handoffs that motivated specific decisions). +Material is kept rather than deleted because the [ADRs](../loomweave/adr/README.md) cite it for historical context (panel reviews, the v0.1 scope-commitment memo, sprint plans, and agent handoffs that motivated specific decisions). ## Layout @@ -21,8 +21,8 @@ Material is kept rather than deleted because the [ADRs](../clarion/adr/README.md ## Relationship to release-facing docs -- **Authoritative design**: [`../clarion/1.0/system-design.md`](../clarion/1.0/system-design.md) and [`../clarion/1.0/detailed-design.md`](../clarion/1.0/detailed-design.md). Each work package under this folder names the sections it implements. -- **Decisions**: [`../clarion/adr/README.md`](../clarion/adr/README.md). Each work package names the accepted ADRs it depends on and any backlog ADRs it is expected to surface. +- **Authoritative design**: [`../loomweave/1.0/system-design.md`](../loomweave/1.0/system-design.md) and [`../loomweave/1.0/detailed-design.md`](../loomweave/1.0/detailed-design.md). Each work package under this folder names the sections it implements. +- **Decisions**: [`../loomweave/adr/README.md`](../loomweave/adr/README.md). Each work package names the accepted ADRs it depends on and any backlog ADRs it is expected to surface. - **Scope and commitments**: [`v0.1-scope-plans/v0.1-scope-commitments.md`](./v0.1-scope-plans/v0.1-scope-commitments.md). That memo locks *what* v0.1 ships; the work-package plans describe *how* the build proceeds. ## Conventions diff --git a/docs/implementation/agent-plans/2026-04-18-wp1-scaffold-storage.md b/docs/implementation/agent-plans/2026-04-18-wp1-scaffold-storage.md index aa63f1d7..7dbf97bb 100644 --- a/docs/implementation/agent-plans/2026-04-18-wp1-scaffold-storage.md +++ b/docs/implementation/agent-plans/2026-04-18-wp1-scaffold-storage.md @@ -2,9 +2,9 @@ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. -**Goal:** Ship the Sprint 1 walking-skeleton storage foundation — Cargo workspace, full SQLite schema migration, writer-actor with per-N-batch transactions, entity-ID assembler, and `clarion install` + `clarion analyze` CLI skeletons. Plugin spawning is WP2's concern; Sprint 1 WP1 must exit with `runs.status = 'skipped_no_plugins'`. +**Goal:** Ship the Sprint 1 walking-skeleton storage foundation — Cargo workspace, full SQLite schema migration, writer-actor with per-N-batch transactions, entity-ID assembler, and `loomweave install` + `loomweave analyze` CLI skeletons. Plugin spawning is WP2's concern; Sprint 1 WP1 must exit with `runs.status = 'skipped_no_plugins'`. -**Architecture:** Three-crate Cargo workspace: `clarion-core` (domain types + entity-ID + LlmProvider trait stub), `clarion-storage` (SQLite layer + writer-actor over a bounded tokio mpsc channel per ADR-011), `clarion-cli` (binary). Writer-actor is a `tokio::task` owning the sole write `rusqlite::Connection`; readers come from a `deadpool-sqlite` pool. Full schema from `detailed-design.md §3` ships in migration `0001_initial_schema.sql` even though Sprint 1 only writes `entities` + `runs`. The design pressure is applied now so Sprint 2+ doesn't face data-migration work. **Tooling baseline per ADR-023** lands with Task 1 before any other code — edition 2024, workspace `[lints]` pedantic, rustfmt/clippy configs, cargo-nextest, cargo-deny, GitHub Actions CI — so every subsequent commit passes the strict floor from day one. +**Architecture:** Three-crate Cargo workspace: `loomweave-core` (domain types + entity-ID + LlmProvider trait stub), `loomweave-storage` (SQLite layer + writer-actor over a bounded tokio mpsc channel per ADR-011), `loomweave-cli` (binary). Writer-actor is a `tokio::task` owning the sole write `rusqlite::Connection`; readers come from a `deadpool-sqlite` pool. Full schema from `detailed-design.md §3` ships in migration `0001_initial_schema.sql` even though Sprint 1 only writes `entities` + `runs`. The design pressure is applied now so Sprint 2+ doesn't face data-migration work. **Tooling baseline per ADR-023** lands with Task 1 before any other code — edition 2024, workspace `[lints]` pedantic, rustfmt/clippy configs, cargo-nextest, cargo-deny, GitHub Actions CI — so every subsequent commit passes the strict floor from day one. **Tech Stack:** **Rust 2024** stable (ADR-023); workspace `[lints]` with `clippy::pedantic = "warn"` + `unsafe_code = "forbid"`; `rusqlite` (bundled SQLite); `deadpool-sqlite`; `tokio` (rt-multi-thread, macros, sync); `clap` (CLI); `thiserror` (library errors); `anyhow` (binary); `tracing` + `tracing-subscriber`; `assert_cmd` + `tempfile` (CLI integration tests); **`cargo-nextest`** (test runner); **`cargo-deny`** (supply chain); **GitHub Actions** (CI gates). @@ -16,11 +16,11 @@ - **UQ-WP1-01** rusqlite + bundled SQLite (ADR-011). - **UQ-WP1-02** tokio from day one (ADR-011). - **UQ-WP1-03** per-command oneshot ack. Commit-counter test hook uses an `Arc` threaded through `Writer::spawn` — keeps the hook path identical in release and test builds; no `#[cfg(test)]` branches in the hot loop. -- **UQ-WP1-04** `.gitignore` seeded with: `tmp/`, `logs/`, `*.shadow.db`, `*.wal`, `*.shm`, `runs/*/log.jsonl`. Tracked: `clarion.db`, `config.json`, schema history in the DB. +- **UQ-WP1-04** `.gitignore` seeded with: `tmp/`, `logs/`, `*.shadow.db`, `*.wal`, `*.shm`, `runs/*/log.jsonl`. Tracked: `loomweave.db`, `config.json`, schema history in the DB. - **UQ-WP1-05** `runs` row shape matches `detailed-design.md §3:695-701` fully; Sprint 1 inserts NULL/JSON-`{}` for plugin-invocation columns, WP2 fills them. -- **UQ-WP1-06** `clarion-storage` wraps `rusqlite::Error` in a crate-local `StorageError` via `thiserror`. +- **UQ-WP1-06** `loomweave-storage` wraps `rusqlite::Error` in a crate-local `StorageError` via `thiserror`. - **UQ-WP1-07** assembler rejects any segment containing `:` with an `EntityIdError::SegmentContainsColon` — documents the grammar contract as a type-checked invariant. -- **UQ-WP1-08** `clarion install` refuses if `.clarion/` exists without `--force`; `--force` is recognised in clap but returns `unimplemented in Sprint 1` at runtime. +- **UQ-WP1-08** `loomweave install` refuses if `.loomweave/` exists without `--force`; `--force` is recognised in clap but returns `unimplemented in Sprint 1` at runtime. - **UQ-WP1-09** **reopened and re-resolved by ADR-023**: Rust **edition 2024**, workspace `[lints]` block with `clippy::pedantic = "warn"` + `unsafe_code = "forbid"`, pinned `rustfmt.toml` + `clippy.toml`, **`cargo-nextest`** as the test runner, **`cargo-deny`** for supply-chain hygiene, **GitHub Actions CI** running fmt-check + pedantic clippy + nextest + cargo-doc + cargo-deny on every PR. `rust-toolchain.toml` pins `stable` + `clippy`/`rustfmt`/`llvm-tools-preview`. The original "fine to document and move on" framing was the tell for unexamined tech debt; adopted at the zero-code frontier where retrofit cost is zero. **Scope note on entity-ID format:** ADR-003 fixes the 3-segment form as `{plugin_id}:{kind}:{canonical_qualified_name}`. The assembler (Task 2) validates `plugin_id` + `kind` against the ADR-022 grammar (`[a-z][a-z0-9_]*`) and rejects any segment containing `:`. It is **format-agnostic on `canonical_qualified_name`** — that segment's internal shape is the emitting plugin's concern (Python plugin: dotted qualname; core file-discovery: `{hash}@{path}`; etc.). Sprint 1 tests use simplified example strings to exercise concatenation; the plugin-specific shapes are validated by WP3 + the core file-discovery pass post-Sprint-1. @@ -30,38 +30,38 @@ ## Task 1: Workspace skeleton + ADR-023 tooling baseline **Files:** -- Create: `/home/john/clarion/Cargo.toml` (workspace root, `[workspace.package]`, `[workspace.dependencies]`, `[workspace.lints]`) -- Create: `/home/john/clarion/rust-toolchain.toml` -- Create: `/home/john/clarion/rustfmt.toml` -- Create: `/home/john/clarion/clippy.toml` -- Create: `/home/john/clarion/deny.toml` -- Create: `/home/john/clarion/.github/workflows/ci.yml` -- Create: `/home/john/clarion/.gitignore` -- Create: `/home/john/clarion/crates/clarion-core/Cargo.toml` -- Create: `/home/john/clarion/crates/clarion-core/src/lib.rs` -- Create: `/home/john/clarion/crates/clarion-storage/Cargo.toml` -- Create: `/home/john/clarion/crates/clarion-storage/src/lib.rs` -- Create: `/home/john/clarion/crates/clarion-cli/Cargo.toml` -- Create: `/home/john/clarion/crates/clarion-cli/src/main.rs` +- Create: `/home/john/loomweave/Cargo.toml` (workspace root, `[workspace.package]`, `[workspace.dependencies]`, `[workspace.lints]`) +- Create: `/home/john/loomweave/rust-toolchain.toml` +- Create: `/home/john/loomweave/rustfmt.toml` +- Create: `/home/john/loomweave/clippy.toml` +- Create: `/home/john/loomweave/deny.toml` +- Create: `/home/john/loomweave/.github/workflows/ci.yml` +- Create: `/home/john/loomweave/.gitignore` +- Create: `/home/john/loomweave/crates/loomweave-core/Cargo.toml` +- Create: `/home/john/loomweave/crates/loomweave-core/src/lib.rs` +- Create: `/home/john/loomweave/crates/loomweave-storage/Cargo.toml` +- Create: `/home/john/loomweave/crates/loomweave-storage/src/lib.rs` +- Create: `/home/john/loomweave/crates/loomweave-cli/Cargo.toml` +- Create: `/home/john/loomweave/crates/loomweave-cli/src/main.rs` - [ ] **Step 1: Create the workspace root `Cargo.toml`** -Write `/home/john/clarion/Cargo.toml`: +Write `/home/john/loomweave/Cargo.toml`: ```toml [workspace] resolver = "3" members = [ - "crates/clarion-core", - "crates/clarion-storage", - "crates/clarion-cli", + "crates/loomweave-core", + "crates/loomweave-storage", + "crates/loomweave-cli", ] [workspace.package] version = "0.1.0-dev" edition = "2024" license = "MIT OR Apache-2.0" -repository = "https://github.com/qacona/clarion" +repository = "https://github.com/qacona/loomweave" rust-version = "1.85" [workspace.lints.rust] @@ -93,7 +93,7 @@ The `resolver = "3"` value is required for edition 2024. Priority `-1` on `clipp - [ ] **Step 2: Pin the toolchain** -Write `/home/john/clarion/rust-toolchain.toml`: +Write `/home/john/loomweave/rust-toolchain.toml`: ```toml [toolchain] @@ -106,7 +106,7 @@ profile = "minimal" - [ ] **Step 3: Write `rustfmt.toml`** -Write `/home/john/clarion/rustfmt.toml`: +Write `/home/john/loomweave/rustfmt.toml`: ```toml edition = "2024" @@ -118,7 +118,7 @@ use_try_shorthand = true - [ ] **Step 4: Write `clippy.toml`** -Write `/home/john/clarion/clippy.toml`: +Write `/home/john/loomweave/clippy.toml`: ```toml cognitive-complexity-threshold = 15 @@ -128,7 +128,7 @@ too-many-lines-threshold = 120 - [ ] **Step 5: Write `deny.toml`** -Write `/home/john/clarion/deny.toml`: +Write `/home/john/loomweave/deny.toml`: ```toml # deny.toml — cargo-deny v2 schema. Anything not in `allow` is denied. @@ -165,7 +165,7 @@ allow-git = [] - [ ] **Step 6: Write the GitHub Actions CI workflow** -Write `/home/john/clarion/.github/workflows/ci.yml`: +Write `/home/john/loomweave/.github/workflows/ci.yml`: ```yaml name: CI @@ -218,14 +218,14 @@ jobs: - [ ] **Step 7: Write the repo-root `.gitignore`** -Write `/home/john/clarion/.gitignore`: +Write `/home/john/loomweave/.gitignore`: ``` /target **/*.rs.bk Cargo.lock.bak -# SQLite working files (project-level .clarion/ is tracked per ADR-005) +# SQLite working files (project-level .loomweave/ is tracked per ADR-005) *.db-journal *.db-wal @@ -234,15 +234,15 @@ Cargo.lock.bak /.vscode ``` -Note: we do **not** ignore `*.db` here. `.clarion/clarion.db` is tracked per ADR-005 (authored in Task 5); only write-ahead files are excluded. +Note: we do **not** ignore `*.db` here. `.loomweave/loomweave.db` is tracked per ADR-005 (authored in Task 5); only write-ahead files are excluded. -- [ ] **Step 8: Write `clarion-core`'s `Cargo.toml` and `lib.rs`** +- [ ] **Step 8: Write `loomweave-core`'s `Cargo.toml` and `lib.rs`** -Write `/home/john/clarion/crates/clarion-core/Cargo.toml`: +Write `/home/john/loomweave/crates/loomweave-core/Cargo.toml`: ```toml [package] -name = "clarion-core" +name = "loomweave-core" version.workspace = true edition.workspace = true license.workspace = true @@ -258,22 +258,22 @@ serde_json.workspace = true thiserror.workspace = true ``` -Write `/home/john/clarion/crates/clarion-core/src/lib.rs`: +Write `/home/john/loomweave/crates/loomweave-core/src/lib.rs`: ```rust -//! clarion-core — domain types, identifiers, and provider traits. +//! loomweave-core — domain types, identifiers, and provider traits. //! //! This crate is dependency-light and contains no I/O. Storage and CLI //! crates depend on it; it depends on neither. ``` -- [ ] **Step 9: Write `clarion-storage`'s `Cargo.toml` and `lib.rs`** +- [ ] **Step 9: Write `loomweave-storage`'s `Cargo.toml` and `lib.rs`** -Write `/home/john/clarion/crates/clarion-storage/Cargo.toml`: +Write `/home/john/loomweave/crates/loomweave-storage/Cargo.toml`: ```toml [package] -name = "clarion-storage" +name = "loomweave-storage" version.workspace = true edition.workspace = true license.workspace = true @@ -284,7 +284,7 @@ rust-version.workspace = true workspace = true [dependencies] -clarion-core = { path = "../clarion-core" } +loomweave-core = { path = "../loomweave-core" } deadpool-sqlite.workspace = true rusqlite.workspace = true serde_json.workspace = true @@ -297,23 +297,23 @@ tempfile.workspace = true tokio = { workspace = true, features = ["rt-multi-thread", "macros", "sync", "time", "test-util"] } ``` -Write `/home/john/clarion/crates/clarion-storage/src/lib.rs`: +Write `/home/john/loomweave/crates/loomweave-storage/src/lib.rs`: ```rust -//! clarion-storage — SQLite layer, writer-actor, reader pool. +//! loomweave-storage — SQLite layer, writer-actor, reader pool. //! //! All mutations route through the writer actor (a single `tokio::task` //! owning the sole write `rusqlite::Connection`). Readers come from a //! `deadpool-sqlite` pool. See ADR-011. ``` -- [ ] **Step 10: Write `clarion-cli`'s `Cargo.toml` and `main.rs`** +- [ ] **Step 10: Write `loomweave-cli`'s `Cargo.toml` and `main.rs`** -Write `/home/john/clarion/crates/clarion-cli/Cargo.toml`: +Write `/home/john/loomweave/crates/loomweave-cli/Cargo.toml`: ```toml [package] -name = "clarion-cli" +name = "loomweave-cli" version.workspace = true edition.workspace = true license.workspace = true @@ -324,14 +324,14 @@ rust-version.workspace = true workspace = true [[bin]] -name = "clarion" +name = "loomweave" path = "src/main.rs" [dependencies] anyhow.workspace = true clap.workspace = true -clarion-core = { path = "../clarion-core" } -clarion-storage = { path = "../clarion-storage" } +loomweave-core = { path = "../loomweave-core" } +loomweave-storage = { path = "../loomweave-storage" } serde_json.workspace = true tokio.workspace = true tracing.workspace = true @@ -342,16 +342,16 @@ assert_cmd.workspace = true tempfile.workspace = true ``` -Write `/home/john/clarion/crates/clarion-cli/src/main.rs`: +Write `/home/john/loomweave/crates/loomweave-cli/src/main.rs`: ```rust -//! clarion — command-line entry point. +//! loomweave — command-line entry point. //! //! Real subcommand implementations land in Tasks 5 and 7. This Task-1 //! stub exists so the workspace compiles pedantic-clean from day one. fn main() -> anyhow::Result<()> { - eprintln!("clarion: unimplemented (Sprint 1 WP1 scaffold — Task 1)"); + eprintln!("loomweave: unimplemented (Sprint 1 WP1 scaffold — Task 1)"); std::process::exit(2); } ``` @@ -372,12 +372,12 @@ CI installs these via `taiki-e/install-action`; local execution needs them once Run each in sequence. Every one must exit zero before committing: ```bash -cd /home/john/clarion && cargo build --workspace -cd /home/john/clarion && cargo fmt --all -- --check -cd /home/john/clarion && cargo clippy --workspace --all-targets --all-features -- -D warnings -cd /home/john/clarion && cargo nextest run --workspace --all-features -cd /home/john/clarion && cargo doc --workspace --no-deps --all-features -cd /home/john/clarion && cargo deny check +cd /home/john/loomweave && cargo build --workspace +cd /home/john/loomweave && cargo fmt --all -- --check +cd /home/john/loomweave && cargo clippy --workspace --all-targets --all-features -- -D warnings +cd /home/john/loomweave && cargo nextest run --workspace --all-features +cd /home/john/loomweave && cargo doc --workspace --no-deps --all-features +cd /home/john/loomweave && cargo deny check ``` Expected: all six commands exit 0. `cargo nextest run` reports "no tests to run" at this stage (Task 2 lands the first tests). `cargo deny check` may warn about `multiple-versions` if two transitive deps resolve different versions of the same crate — warnings are fine; only errors block. @@ -387,10 +387,10 @@ If `cargo clippy` fires any pedantic warning (from `clippy::pedantic = "warn"` - [ ] **Step 13: Commit** ```bash -cd /home/john/clarion && git add Cargo.toml rust-toolchain.toml rustfmt.toml clippy.toml deny.toml .github/ .gitignore crates/ && git commit -m "$(cat <<'EOF' +cd /home/john/loomweave && git add Cargo.toml rust-toolchain.toml rustfmt.toml clippy.toml deny.toml .github/ .gitignore crates/ && git commit -m "$(cat <<'EOF' feat(wp1): workspace skeleton + ADR-023 tooling baseline -Cargo workspace with clarion-core, clarion-storage, clarion-cli members, +Cargo workspace with loomweave-core, loomweave-storage, loomweave-cli members, edition 2024, resolver 3, workspace [lints] block with clippy::pedantic = "warn" + unsafe_code = "forbid" (ADR-023). Every member crate declares lints.workspace = true so a later-added crate cannot drift off the floor. @@ -415,17 +415,17 @@ EOF ## Task 2: Entity-ID assembler (L2) **Files:** -- Create: `/home/john/clarion/crates/clarion-core/src/entity_id.rs` -- Modify: `/home/john/clarion/crates/clarion-core/src/lib.rs` +- Create: `/home/john/loomweave/crates/loomweave-core/src/entity_id.rs` +- Modify: `/home/john/loomweave/crates/loomweave-core/src/lib.rs` - [ ] **Step 1: Write the failing unit tests** -Write `/home/john/clarion/crates/clarion-core/src/entity_id.rs`: +Write `/home/john/loomweave/crates/loomweave-core/src/entity_id.rs`: ```rust //! Entity-ID assembler. //! -//! Per ADR-003 + ADR-022, every Clarion entity has a stable 3-segment ID: +//! Per ADR-003 + ADR-022, every Loomweave entity has a stable 3-segment ID: //! `{plugin_id}:{kind}:{canonical_qualified_name}`. //! //! - `plugin_id` and `kind` must match the grammar `[a-z][a-z0-9_]*`. @@ -639,10 +639,10 @@ mod tests { } ``` -Modify `/home/john/clarion/crates/clarion-core/src/lib.rs` to: +Modify `/home/john/loomweave/crates/loomweave-core/src/lib.rs` to: ```rust -//! clarion-core — domain types, identifiers, and provider traits. +//! loomweave-core — domain types, identifiers, and provider traits. //! //! This crate is dependency-light and contains no I/O. Storage and CLI //! crates depend on it; it depends on neither. @@ -657,7 +657,7 @@ pub use entity_id::{entity_id, EntityId, EntityIdError}; The tests above compile and drive the implementation at the same time (the implementation is written alongside, not separately). Run: ```bash -cd /home/john/clarion && cargo nextest run -p clarion-core -E 'test(entity_id)' +cd /home/john/loomweave && cargo nextest run -p loomweave-core -E 'test(entity_id)' ``` Expected: all 13 tests pass. If any test fails, the implementation above has a bug — fix the code, not the test. @@ -665,7 +665,7 @@ Expected: all 13 tests pass. If any test fails, the implementation above has a b - [ ] **Step 3: Confirm no clippy warnings** ```bash -cd /home/john/clarion && cargo clippy -p clarion-core --all-targets -- -D warnings +cd /home/john/loomweave && cargo clippy -p loomweave-core --all-targets -- -D warnings ``` Expected: no warnings. If clippy complains about unused imports or dead code, address before committing. @@ -673,7 +673,7 @@ Expected: no warnings. If clippy complains about unused imports or dead code, ad - [ ] **Step 4: Commit** ```bash -cd /home/john/clarion && git add crates/clarion-core/ && git commit -m "$(cat <<'EOF' +cd /home/john/loomweave && git add crates/loomweave-core/ && git commit -m "$(cat <<'EOF' feat(wp1): L2 entity-ID assembler per ADR-003 + ADR-022 entity_id() assembles {plugin_id}:{kind}:{canonical_qualified_name} with @@ -695,22 +695,22 @@ EOF ## Task 3: Schema migration file (L1) **Files:** -- Create: `/home/john/clarion/crates/clarion-storage/migrations/0001_initial_schema.sql` -- Create: `/home/john/clarion/crates/clarion-storage/src/error.rs` -- Create: `/home/john/clarion/crates/clarion-storage/src/schema.rs` -- Create: `/home/john/clarion/crates/clarion-storage/src/pragma.rs` -- Modify: `/home/john/clarion/crates/clarion-storage/src/lib.rs` -- Create: `/home/john/clarion/crates/clarion-storage/tests/schema_apply.rs` +- Create: `/home/john/loomweave/crates/loomweave-storage/migrations/0001_initial_schema.sql` +- Create: `/home/john/loomweave/crates/loomweave-storage/src/error.rs` +- Create: `/home/john/loomweave/crates/loomweave-storage/src/schema.rs` +- Create: `/home/john/loomweave/crates/loomweave-storage/src/pragma.rs` +- Modify: `/home/john/loomweave/crates/loomweave-storage/src/lib.rs` +- Create: `/home/john/loomweave/crates/loomweave-storage/tests/schema_apply.rs` - [ ] **Step 1: Write the migration SQL** -Write `/home/john/clarion/crates/clarion-storage/migrations/0001_initial_schema.sql`. The SQL below is transcribed directly from `detailed-design.md §3:593-755` plus the migration-framework `schema_migrations` meta table. Do not summarise, abbreviate, or drop anything — the full shape is load-bearing per the L1 lock-in. +Write `/home/john/loomweave/crates/loomweave-storage/migrations/0001_initial_schema.sql`. The SQL below is transcribed directly from `detailed-design.md §3:593-755` plus the migration-framework `schema_migrations` meta table. Do not summarise, abbreviate, or drop anything — the full shape is load-bearing per the L1 lock-in. ```sql -- ============================================================================ --- Clarion migration 0001 — initial schema. +-- Loomweave migration 0001 — initial schema. -- --- Source: docs/clarion/1.0/detailed-design.md §3 (Storage Implementation). +-- Source: docs/loomweave/1.0/detailed-design.md §3 (Storage Implementation). -- Sprint 1 walking skeleton writes only to `entities` and `runs`, but every -- table, FTS5 virtual table, trigger, generated column, index, and view -- is created here so the full shape is frozen at L1-lock time. See ADR-011 @@ -911,7 +911,7 @@ COMMIT; - [ ] **Step 2: Write the `StorageError` type** -Write `/home/john/clarion/crates/clarion-storage/src/error.rs`: +Write `/home/john/loomweave/crates/loomweave-storage/src/error.rs`: ```rust use thiserror::Error; @@ -946,7 +946,7 @@ pub type Result = std::result::Result; - [ ] **Step 3: Write the PRAGMA application helper** -Write `/home/john/clarion/crates/clarion-storage/src/pragma.rs`: +Write `/home/john/loomweave/crates/loomweave-storage/src/pragma.rs`: ```rust //! PRAGMAs applied at connection open per ADR-011 §SQLite PRAGMAs. @@ -985,7 +985,7 @@ pub fn apply_read_pragmas(conn: &Connection) -> Result<()> { - [ ] **Step 4: Write the schema migration runner** -Write `/home/john/clarion/crates/clarion-storage/src/schema.rs`: +Write `/home/john/loomweave/crates/loomweave-storage/src/schema.rs`: ```rust //! Schema migration runner. @@ -1079,10 +1079,10 @@ pub fn applied_count(conn: &Connection) -> Result { - [ ] **Step 5: Wire the modules into `lib.rs`** -Replace `/home/john/clarion/crates/clarion-storage/src/lib.rs` with: +Replace `/home/john/loomweave/crates/loomweave-storage/src/lib.rs` with: ```rust -//! clarion-storage — SQLite layer, writer-actor, reader pool. +//! loomweave-storage — SQLite layer, writer-actor, reader pool. //! //! All mutations route through [`writer::Writer`] (a single `tokio::task` //! owning the sole write `rusqlite::Connection`). Readers come from a @@ -1097,7 +1097,7 @@ pub use error::{Result, StorageError}; - [ ] **Step 6: Write the integration test** -Write `/home/john/clarion/crates/clarion-storage/tests/schema_apply.rs`: +Write `/home/john/loomweave/crates/loomweave-storage/tests/schema_apply.rs`: ```rust //! Schema-apply integration tests. @@ -1108,10 +1108,10 @@ Write `/home/john/clarion/crates/clarion-storage/tests/schema_apply.rs`: use rusqlite::{params, Connection}; -use clarion_storage::{pragma, schema}; +use loomweave_storage::{pragma, schema}; fn open_fresh(tempdir: &tempfile::TempDir) -> Connection { - let path = tempdir.path().join("clarion.db"); + let path = tempdir.path().join("loomweave.db"); let mut conn = Connection::open(&path).expect("open"); pragma::apply_write_pragmas(&conn).expect("pragmas"); schema::apply_migrations(&mut conn).expect("apply migrations"); @@ -1293,17 +1293,17 @@ fn schema_migrations_records_one_row() { - [ ] **Step 7: Run the tests** ```bash -cd /home/john/clarion && cargo nextest run -p clarion-storage --test schema_apply +cd /home/john/loomweave && cargo nextest run -p loomweave-storage --test schema_apply ``` Expected: 7 tests pass. If any fail, the migration SQL has a bug — fix `0001_initial_schema.sql`, not the tests. -**Checkpoint** (per the plan review): if `cargo nextest run -p clarion-storage --test schema_apply` isn't green by end of day 2 of WP1 execution, pause and reassess before proceeding to Task 4. A half-locked schema is worse than a one-day slip. +**Checkpoint** (per the plan review): if `cargo nextest run -p loomweave-storage --test schema_apply` isn't green by end of day 2 of WP1 execution, pause and reassess before proceeding to Task 4. A half-locked schema is worse than a one-day slip. - [ ] **Step 8: Clippy clean** ```bash -cd /home/john/clarion && cargo clippy -p clarion-storage --all-targets -- -D warnings +cd /home/john/loomweave && cargo clippy -p loomweave-storage --all-targets -- -D warnings ``` Expected: no warnings. @@ -1311,7 +1311,7 @@ Expected: no warnings. - [ ] **Step 9: Commit** ```bash -cd /home/john/clarion && git add crates/clarion-storage/ && git commit -m "$(cat <<'EOF' +cd /home/john/loomweave && git add crates/loomweave-storage/ && git commit -m "$(cat <<'EOF' feat(wp1): L1 SQLite schema migration framework Migration 0001 transcribes the full detailed-design.md §3 schema: tables @@ -1339,13 +1339,13 @@ EOF ## Task 4: Reader pool **Files:** -- Create: `/home/john/clarion/crates/clarion-storage/src/reader.rs` -- Modify: `/home/john/clarion/crates/clarion-storage/src/lib.rs` -- Create: `/home/john/clarion/crates/clarion-storage/tests/reader_pool.rs` +- Create: `/home/john/loomweave/crates/loomweave-storage/src/reader.rs` +- Modify: `/home/john/loomweave/crates/loomweave-storage/src/lib.rs` +- Create: `/home/john/loomweave/crates/loomweave-storage/tests/reader_pool.rs` - [ ] **Step 1: Write the reader pool wrapper** -Write `/home/john/clarion/crates/clarion-storage/src/reader.rs`: +Write `/home/john/loomweave/crates/loomweave-storage/src/reader.rs`: ```rust //! Read-only connection pool wrapping `deadpool-sqlite` per ADR-011. @@ -1406,10 +1406,10 @@ impl ReaderPool { - [ ] **Step 2: Export from `lib.rs`** -Modify `/home/john/clarion/crates/clarion-storage/src/lib.rs` to add the new module: +Modify `/home/john/loomweave/crates/loomweave-storage/src/lib.rs` to add the new module: ```rust -//! clarion-storage — SQLite layer, writer-actor, reader pool. +//! loomweave-storage — SQLite layer, writer-actor, reader pool. pub mod error; pub mod pragma; @@ -1422,7 +1422,7 @@ pub use reader::ReaderPool; - [ ] **Step 3: Write the failing integration test** -Write `/home/john/clarion/crates/clarion-storage/tests/reader_pool.rs`: +Write `/home/john/loomweave/crates/loomweave-storage/tests/reader_pool.rs`: ```rust //! Reader-pool concurrency tests. @@ -1431,10 +1431,10 @@ use std::sync::Arc; use rusqlite::Connection; -use clarion_storage::{pragma, schema, ReaderPool}; +use loomweave_storage::{pragma, schema, ReaderPool}; fn prepared_db(dir: &tempfile::TempDir) -> std::path::PathBuf { - let path = dir.path().join("clarion.db"); + let path = dir.path().join("loomweave.db"); let mut conn = Connection::open(&path).expect("open"); pragma::apply_write_pragmas(&conn).expect("write pragmas"); schema::apply_migrations(&mut conn).expect("migrate"); @@ -1499,7 +1499,7 @@ async fn reader_sees_committed_data() { - [ ] **Step 4: Run the tests** ```bash -cd /home/john/clarion && cargo nextest run -p clarion-storage --test reader_pool +cd /home/john/loomweave && cargo nextest run -p loomweave-storage --test reader_pool ``` Expected: 2 tests pass. @@ -1507,13 +1507,13 @@ Expected: 2 tests pass. - [ ] **Step 5: Clippy clean** ```bash -cd /home/john/clarion && cargo clippy -p clarion-storage --all-targets -- -D warnings +cd /home/john/loomweave && cargo clippy -p loomweave-storage --all-targets -- -D warnings ``` - [ ] **Step 6: Commit** ```bash -cd /home/john/clarion && git add crates/clarion-storage/ && git commit -m "$(cat <<'EOF' +cd /home/john/loomweave && git add crates/loomweave-storage/ && git commit -m "$(cat <<'EOF' feat(wp1): reader pool for concurrent read connections ReaderPool wraps deadpool-sqlite (ADR-011 default max 16, configurable @@ -1530,19 +1530,19 @@ EOF --- -## Task 5: `clarion install` subcommand +## Task 5: `loomweave install` subcommand **Files:** -- Create: `/home/john/clarion/crates/clarion-cli/src/install.rs` -- Create: `/home/john/clarion/crates/clarion-cli/src/cli.rs` -- Modify: `/home/john/clarion/crates/clarion-cli/src/main.rs` -- Create: `/home/john/clarion/crates/clarion-cli/tests/install.rs` -- Create: `/home/john/clarion/docs/clarion/adr/ADR-005-clarion-dir-tracking.md` -- Modify: `/home/john/clarion/docs/clarion/adr/README.md` +- Create: `/home/john/loomweave/crates/loomweave-cli/src/install.rs` +- Create: `/home/john/loomweave/crates/loomweave-cli/src/cli.rs` +- Modify: `/home/john/loomweave/crates/loomweave-cli/src/main.rs` +- Create: `/home/john/loomweave/crates/loomweave-cli/tests/install.rs` +- Create: `/home/john/loomweave/docs/loomweave/adr/ADR-005-loomweave-dir-tracking.md` +- Modify: `/home/john/loomweave/docs/loomweave/adr/README.md` - [ ] **Step 1: Write the CLI command enum** -Write `/home/john/clarion/crates/clarion-cli/src/cli.rs`: +Write `/home/john/loomweave/crates/loomweave-cli/src/cli.rs`: ```rust use std::path::PathBuf; @@ -1550,7 +1550,7 @@ use std::path::PathBuf; use clap::{Parser, Subcommand}; #[derive(Parser)] -#[command(name = "clarion", version, about = "Clarion code-archaeology tool")] +#[command(name = "loomweave", version, about = "Loomweave code-archaeology tool")] pub struct Cli { #[command(subcommand)] pub command: Command, @@ -1558,9 +1558,9 @@ pub struct Cli { #[derive(Subcommand)] pub enum Command { - /// Initialise .clarion/ in the current directory. + /// Initialise .loomweave/ in the current directory. Install { - /// Overwrite an existing .clarion/ (not implemented in Sprint 1). + /// Overwrite an existing .loomweave/ (not implemented in Sprint 1). #[arg(long)] force: bool, @@ -1581,19 +1581,19 @@ pub enum Command { - [ ] **Step 2: Write the install command implementation** -Write `/home/john/clarion/crates/clarion-cli/src/install.rs`: +Write `/home/john/loomweave/crates/loomweave-cli/src/install.rs`: ```rust -//! `clarion install` — initialise .clarion/ in the target directory. +//! `loomweave install` — initialise .loomweave/ in the target directory. //! //! Creates: -//! - `.clarion/clarion.db` (migrated) -//! - `.clarion/config.json` (internal state stub) -//! - `.clarion/.gitignore` (UQ-WP1-04 rules; ADR-005) -//! - `/clarion.yaml` (user-edited config stub at project root +//! - `.loomweave/loomweave.db` (migrated) +//! - `.loomweave/config.json` (internal state stub) +//! - `.loomweave/.gitignore` (UQ-WP1-04 rules; ADR-005) +//! - `/loomweave.yaml` (user-edited config stub at project root //! per detailed-design.md §File layout) //! -//! Refuses if `.clarion/` already exists (UQ-WP1-08). `--force` is accepted +//! Refuses if `.loomweave/` already exists (UQ-WP1-08). `--force` is accepted //! by the CLI but currently returns an error — Sprint 1 does not implement //! overwrite. @@ -1603,7 +1603,7 @@ use std::path::{Path, PathBuf}; use anyhow::{bail, Context, Result}; use rusqlite::Connection; -use clarion_storage::{pragma, schema}; +use loomweave_storage::{pragma, schema}; const CONFIG_JSON_STUB: &str = r#"{ "schema_version": 1, @@ -1611,15 +1611,15 @@ const CONFIG_JSON_STUB: &str = r#"{ } "#; -const CLARION_YAML_STUB: &str = "# clarion.yaml — user-edited config.\n\ -# Full schema TBD; see docs/clarion/1.0 design. Sprint 1 walking skeleton\n\ +const LOOMWEAVE_YAML_STUB: &str = "# loomweave.yaml — user-edited config.\n\ +# Full schema TBD; see docs/loomweave/1.0 design. Sprint 1 walking skeleton\n\ # ignores most fields. Do not delete this file: later versions will require\n\ # it for model-tier mappings and analysis knobs.\n\ version: 1\n"; const GITIGNORE_CONTENTS: &str = "\ -# Clarion .gitignore — ADR-005 tracked-vs-excluded list. -# Tracked (committed): clarion.db, config.json, .gitignore itself. +# Loomweave .gitignore — ADR-005 tracked-vs-excluded list. +# Tracked (committed): loomweave.db, config.json, .gitignore itself. # Excluded (ignored): WAL sidecars, shadow DB, per-run logs, tmp scratch. # SQLite write-ahead files never belong in the repo. @@ -1645,7 +1645,7 @@ runs/*/log.jsonl pub fn run(path: PathBuf, force: bool) -> Result<()> { if force { bail!( - "--force is not implemented in Sprint 1. Remove .clarion/ manually \ + "--force is not implemented in Sprint 1. Remove .loomweave/ manually \ if you need a clean reinit." ); } @@ -1653,40 +1653,40 @@ pub fn run(path: PathBuf, force: bool) -> Result<()> { let project_root = path.canonicalize().with_context(|| { format!("cannot canonicalise --path {}", path.display()) })?; - let clarion_dir = project_root.join(".clarion"); - if clarion_dir.exists() { + let loomweave_dir = project_root.join(".loomweave"); + if loomweave_dir.exists() { bail!( - ".clarion/ already exists at {}. Delete it (or pass --force when \ + ".loomweave/ already exists at {}. Delete it (or pass --force when \ Sprint 2+ implements overwrite) and try again.", - clarion_dir.display() + loomweave_dir.display() ); } - fs::create_dir_all(&clarion_dir) - .with_context(|| format!("mkdir {}", clarion_dir.display()))?; + fs::create_dir_all(&loomweave_dir) + .with_context(|| format!("mkdir {}", loomweave_dir.display()))?; - let db_path = clarion_dir.join("clarion.db"); - initialise_db(&db_path).context("initialise clarion.db")?; + let db_path = loomweave_dir.join("loomweave.db"); + initialise_db(&db_path).context("initialise loomweave.db")?; - let config_path = clarion_dir.join("config.json"); + let config_path = loomweave_dir.join("config.json"); fs::write(&config_path, CONFIG_JSON_STUB) .with_context(|| format!("write {}", config_path.display()))?; - let gitignore_path = clarion_dir.join(".gitignore"); + let gitignore_path = loomweave_dir.join(".gitignore"); fs::write(&gitignore_path, GITIGNORE_CONTENTS) .with_context(|| format!("write {}", gitignore_path.display()))?; - let yaml_path = project_root.join("clarion.yaml"); + let yaml_path = project_root.join("loomweave.yaml"); if !yaml_path.exists() { - fs::write(&yaml_path, CLARION_YAML_STUB) + fs::write(&yaml_path, LOOMWEAVE_YAML_STUB) .with_context(|| format!("write {}", yaml_path.display()))?; } tracing::info!( - clarion_dir = %clarion_dir.display(), - "clarion install complete" + loomweave_dir = %loomweave_dir.display(), + "loomweave install complete" ); - println!("Initialised {}", clarion_dir.display()); + println!("Initialised {}", loomweave_dir.display()); Ok(()) } @@ -1700,7 +1700,7 @@ fn initialise_db(path: &Path) -> Result<()> { - [ ] **Step 3: Wire the CLI into `main.rs`** -Replace `/home/john/clarion/crates/clarion-cli/src/main.rs` with: +Replace `/home/john/loomweave/crates/loomweave-cli/src/main.rs` with: ```rust mod cli; @@ -1715,8 +1715,8 @@ fn main() -> Result<()> { match cli.command { cli::Command::Install { force, path } => install::run(path, force), cli::Command::Analyze { path: _ } => { - // Task 7 implements this. Stubbed so `clarion analyze` is reachable. - anyhow::bail!("clarion analyze — unimplemented (landing in Task 7)"); + // Task 7 implements this. Stubbed so `loomweave analyze` is reachable. + anyhow::bail!("loomweave analyze — unimplemented (landing in Task 7)"); } } } @@ -1730,44 +1730,44 @@ fn init_tracing() { - [ ] **Step 4: Write the integration tests** -Write `/home/john/clarion/crates/clarion-cli/tests/install.rs`: +Write `/home/john/loomweave/crates/loomweave-cli/tests/install.rs`: ```rust -//! `clarion install` integration tests. +//! `loomweave install` integration tests. use std::fs; use assert_cmd::Command; use rusqlite::Connection; -fn clarion_bin() -> Command { - Command::cargo_bin("clarion").expect("clarion binary") +fn loomweave_bin() -> Command { + Command::cargo_bin("loomweave").expect("loomweave binary") } #[test] -fn install_creates_clarion_dir_with_expected_contents() { +fn install_creates_loomweave_dir_with_expected_contents() { let dir = tempfile::tempdir().unwrap(); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .assert() .success(); - let clarion = dir.path().join(".clarion"); - assert!(clarion.join("clarion.db").exists(), "clarion.db missing"); - assert!(clarion.join("config.json").exists(), "config.json missing"); - assert!(clarion.join(".gitignore").exists(), ".gitignore missing"); + let loomweave = dir.path().join(".loomweave"); + assert!(loomweave.join("loomweave.db").exists(), "loomweave.db missing"); + assert!(loomweave.join("config.json").exists(), "config.json missing"); + assert!(loomweave.join(".gitignore").exists(), ".gitignore missing"); assert!( - dir.path().join("clarion.yaml").exists(), - "clarion.yaml not at project root" + dir.path().join("loomweave.yaml").exists(), + "loomweave.yaml not at project root" ); - let config = fs::read_to_string(clarion.join("config.json")).unwrap(); + let config = fs::read_to_string(loomweave.join("config.json")).unwrap(); let parsed: serde_json::Value = serde_json::from_str(&config).unwrap(); assert_eq!(parsed["schema_version"], 1); assert!(parsed["last_run_id"].is_null()); - let gitignore = fs::read_to_string(clarion.join(".gitignore")).unwrap(); + let gitignore = fs::read_to_string(loomweave.join(".gitignore")).unwrap(); for rule in &["*.shadow.db", "tmp/", "logs/", "runs/*/log.jsonl", "*-wal", "*-shm"] { assert!( gitignore.contains(rule), @@ -1779,13 +1779,13 @@ fn install_creates_clarion_dir_with_expected_contents() { #[test] fn install_applies_migration_0001_exactly_once() { let dir = tempfile::tempdir().unwrap(); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .assert() .success(); - let conn = Connection::open(dir.path().join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(dir.path().join(".loomweave/loomweave.db")).unwrap(); let count: i64 = conn .query_row("SELECT COUNT(*) FROM schema_migrations", [], |row| row.get(0)) .unwrap(); @@ -1797,16 +1797,16 @@ fn install_applies_migration_0001_exactly_once() { } #[test] -fn install_refuses_to_overwrite_existing_clarion_dir() { +fn install_refuses_to_overwrite_existing_loomweave_dir() { let dir = tempfile::tempdir().unwrap(); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .assert() .success(); // Second install must fail with a clear message. - let out = clarion_bin() + let out = loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .assert() @@ -1825,7 +1825,7 @@ fn install_refuses_to_overwrite_existing_clarion_dir() { #[test] fn install_force_returns_unimplemented_in_sprint_one() { let dir = tempfile::tempdir().unwrap(); - let out = clarion_bin() + let out = loomweave_bin() .args(["install", "--force", "--path"]) .arg(dir.path()) .assert() @@ -1840,38 +1840,38 @@ fn install_force_returns_unimplemented_in_sprint_one() { - [ ] **Step 5: Write ADR-005** -Write `/home/john/clarion/docs/clarion/adr/ADR-005-clarion-dir-tracking.md`: +Write `/home/john/loomweave/docs/loomweave/adr/ADR-005-loomweave-dir-tracking.md`: ```markdown -# ADR-005: `.clarion/` Directory Git-Tracking Policy +# ADR-005: `.loomweave/` Directory Git-Tracking Policy **Status**: Accepted **Date**: 2026-04-18 **Deciders**: qacona@gmail.com -**Context**: `clarion install` must write a `.gitignore` inside `.clarion/` that +**Context**: `loomweave install` must write a `.gitignore` inside `.loomweave/` that separates committed analysis state from volatile per-run artefacts. Sprint 1 WP1 Task 5 is the authoring trigger; before this ADR, the rules were only proposed in `docs/implementation/sprint-1/wp1-scaffold.md §UQ-WP1-04`. ## Summary -`.clarion/clarion.db` and `.clarion/config.json` are committed. WAL sidecars, +`.loomweave/loomweave.db` and `.loomweave/config.json` are committed. WAL sidecars, the shadow-DB intermediate, `tmp/`, `logs/`, and per-run raw LLM request/response -logs (`runs/*/log.jsonl`) are `.gitignore`d. `clarion.yaml` lives at the project +logs (`runs/*/log.jsonl`) are `.gitignore`d. `loomweave.yaml` lives at the project root and is tracked under the user's existing repo-root `.gitignore`, not under -`.clarion/.gitignore` (it's a user-edited config, not analysis state). +`.loomweave/.gitignore` (it's a user-edited config, not analysis state). ## Context -`.clarion/` mixes artefact kinds that want different tracking posture: +`.loomweave/` mixes artefact kinds that want different tracking posture: - **Shared analysis state** (entities, edges, briefings, guidance) — diff-friendly - via `clarion db export --textual`; solo-developer and small-team cases benefit + via `loomweave db export --textual`; solo-developer and small-team cases benefit from having briefings versioned alongside the code they describe (`detailed-design.md §3 File layout`). - **Runtime write-ahead files** (`*-wal`, `*-shm`) — SQLite bookkeeping that is process-local and meaningless on a different machine. -- **Shadow DB** (`clarion.db.new`, `*.shadow.db`) — ADR-011's `--shadow-db` +- **Shadow DB** (`loomweave.db.new`, `*.shadow.db`) — ADR-011's `--shadow-db` intermediate; deleted on successful atomic rename, would leak as junk otherwise. - **Per-run LLM bodies** (`runs//log.jsonl`) — raw request/response @@ -1879,14 +1879,14 @@ root and is tracked under the user's existing repo-root `.gitignore`, not under but not appropriate to commit to a public repo. - **Scratch** (`tmp/`, `logs/`) — volatile by definition. -Without this ADR, `clarion install` has no normative place to look up the rules, +Without this ADR, `loomweave install` has no normative place to look up the rules, and every developer's install produces their own variant `.gitignore` by accident. ## Decision -`clarion install` writes `.clarion/.gitignore` with the following contents +`loomweave install` writes `.loomweave/.gitignore` with the following contents (verbatim — the literal file lives at -`crates/clarion-cli/src/install.rs` and ships as the v0.1 baseline): +`crates/loomweave-cli/src/install.rs` and ships as the v0.1 baseline): ``` *-wal @@ -1902,16 +1902,16 @@ runs/*/log.jsonl ### Tracked -- `.clarion/clarion.db` — the main analysis store. SQLite diffs poorly; the - `clarion db export --textual` + `clarion db merge-helper` pattern (detailed +- `.loomweave/loomweave.db` — the main analysis store. SQLite diffs poorly; the + `loomweave db export --textual` + `loomweave db merge-helper` pattern (detailed design §3 File layout) handles the team case. -- `.clarion/config.json` — small, human-readable internal state (schema +- `.loomweave/config.json` — small, human-readable internal state (schema version, last run IDs). -- `.clarion/.gitignore` itself — this file. -- `.clarion/runs//config.yaml` — the snapshot of `clarion.yaml` at run +- `.loomweave/.gitignore` itself — this file. +- `.loomweave/runs//config.yaml` — the snapshot of `loomweave.yaml` at run time. Material for provenance replay. -- `.clarion/runs//stats.json` — run statistics. -- `.clarion/runs//partial.json` — present only for partial runs; +- `.loomweave/runs//stats.json` — run statistics. +- `.loomweave/runs//partial.json` — present only for partial runs; material for `--resume`. ### Excluded @@ -1921,18 +1921,18 @@ runs/*/log.jsonl - `tmp/` and `logs/` (volatile scratch). - `runs/*/log.jsonl` (raw LLM bodies — audit-local, not commit-appropriate). -### Out of scope for `.clarion/.gitignore` +### Out of scope for `.loomweave/.gitignore` -- `clarion.yaml` (the user-edited config) lives at the *project root*, not - inside `.clarion/`. Its tracking is governed by the project's own repo-root +- `loomweave.yaml` (the user-edited config) lives at the *project root*, not + inside `.loomweave/`. Its tracking is governed by the project's own repo-root `.gitignore`, which is the user's concern. Default posture: tracked. ### Opt-out for users who don't want the DB committed -`clarion.yaml:storage.commit_db: false` (post-Sprint-1 knob; WP6 authors the -full `clarion.yaml` schema). When false, Clarion writes an additional -`.clarion/.gitignore` line excluding `clarion.db`, and emits -`clarion db sync push/pull` commands. Not implemented in Sprint 1; the knob +`loomweave.yaml:storage.commit_db: false` (post-Sprint-1 knob; WP6 authors the +full `loomweave.yaml` schema). When false, Loomweave writes an additional +`.loomweave/.gitignore` line excluding `loomweave.db`, and emits +`loomweave db sync push/pull` commands. Not implemented in Sprint 1; the knob is documented here so the future change has a home. ## Alternatives Considered @@ -1949,7 +1949,7 @@ committed is unbounded. ### Alternative 2: commit nothing -**Pros**: simplest — `.clarion/` becomes entirely machine-local. +**Pros**: simplest — `.loomweave/` becomes entirely machine-local. **Cons**: loses the "shared analysis state" benefit — briefings and guidance are derived outputs that are expensive to rebuild. Small teams especially @@ -1963,7 +1963,7 @@ analysis only opt out via `storage.commit_db: false`. **Pros**: keeps small-git-diff UX (LFS handles the binary file). -**Cons**: requires git-lfs installed on every developer machine; makes `clarion +**Cons**: requires git-lfs installed on every developer machine; makes `loomweave install` a multi-tool setup; adds failure modes (lfs server availability, large file policy). v0.1 target workflows are solo/small-team where the straight-commit path works; LFS is a v0.2+ knob. @@ -1974,7 +1974,7 @@ path works; LFS is a v0.2+ knob. ### Positive -- Every `clarion install` produces the same `.gitignore`. Ends per-developer +- Every `loomweave install` produces the same `.gitignore`. Ends per-developer drift on "what should be committed." - WAL sidecars cannot accidentally land in a commit. - Raw LLM bodies stay local to the developer that ran the analysis. @@ -1984,10 +1984,10 @@ path works; LFS is a v0.2+ knob. ### Negative - Committed SQLite DBs diff poorly by default. Mitigation: the - `clarion db export --textual` / merge-helper path (detailed-design §3) is + `loomweave db export --textual` / merge-helper path (detailed-design §3) is the documented escape hatch. -- Adding a new excluded pattern requires either a Clarion release or a - user-side `.clarion/.gitignore` edit. The post-v0.1 plan is to keep this +- Adding a new excluded pattern requires either a Loomweave release or a + user-side `.loomweave/.gitignore` edit. The post-v0.1 plan is to keep this file tool-owned; users adding their own ignores put them in the repo-root `.gitignore`, not here. @@ -1998,10 +1998,10 @@ path works; LFS is a v0.2+ knob. ## Related Decisions -- [ADR-011](../../clarion/adr/ADR-011-writer-actor-concurrency.md) — names the shadow-DB +- [ADR-011](../../loomweave/adr/ADR-011-writer-actor-concurrency.md) — names the shadow-DB intermediate; this ADR excludes it from git. -- [ADR-014](../../clarion/adr/ADR-014-filigree-registry-backend.md) — cross-tool references - rely on `clarion.db` being available to readers (Filigree, Wardline); the +- [ADR-014](../../loomweave/adr/ADR-014-filigree-registry-backend.md) — cross-tool references + rely on `loomweave.db` being available to readers (Filigree, Wardline); the commit-by-default posture keeps those references resolvable across machines. ## References @@ -2015,18 +2015,18 @@ path works; LFS is a v0.2+ knob. - [ ] **Step 6: Update the ADR index** -Edit `/home/john/clarion/docs/clarion/adr/README.md` at line 32 (`| ADR-005 | ... | Backlog |`). Change to: +Edit `/home/john/loomweave/docs/loomweave/adr/README.md` at line 32 (`| ADR-005 | ... | Backlog |`). Change to: ``` -| ADR-005 | `.clarion/` git-committable by default; DB included, run logs excluded | Accepted | +| ADR-005 | `.loomweave/` git-committable by default; DB included, run logs excluded | Accepted | ``` -If the ADR index has a separate "Accepted ADRs" list table elsewhere, also add a row for ADR-005 there (run `grep -n '^| ADR-' docs/clarion/adr/README.md` to locate). The important change is the status moving from `Backlog` to `Accepted`. +If the ADR index has a separate "Accepted ADRs" list table elsewhere, also add a row for ADR-005 there (run `grep -n '^| ADR-' docs/loomweave/adr/README.md` to locate). The important change is the status moving from `Backlog` to `Accepted`. - [ ] **Step 7: Run the tests** ```bash -cd /home/john/clarion && cargo nextest run -p clarion-cli --test install +cd /home/john/loomweave && cargo nextest run -p loomweave-cli --test install ``` Expected: 4 tests pass. @@ -2034,22 +2034,22 @@ Expected: 4 tests pass. - [ ] **Step 8: Run clippy** ```bash -cd /home/john/clarion && cargo clippy -p clarion-cli --all-targets -- -D warnings +cd /home/john/loomweave && cargo clippy -p loomweave-cli --all-targets -- -D warnings ``` - [ ] **Step 9: Commit** ```bash -cd /home/john/clarion && git add crates/clarion-cli/ docs/clarion/adr/ && git commit -m "$(cat <<'EOF' -feat(wp1): clarion install subcommand; author ADR-005 +cd /home/john/loomweave && git add crates/loomweave-cli/ docs/loomweave/adr/ && git commit -m "$(cat <<'EOF' +feat(wp1): loomweave install subcommand; author ADR-005 -clarion install creates .clarion/{clarion.db,config.json,.gitignore} and -a clarion.yaml stub at the project root per detailed-design.md §File -layout. Refuses on existing .clarion/ (UQ-WP1-08). --force is recognised +loomweave install creates .loomweave/{loomweave.db,config.json,.gitignore} and +a loomweave.yaml stub at the project root per detailed-design.md §File +layout. Refuses on existing .loomweave/ (UQ-WP1-08). --force is recognised by clap but errors out — Sprint 1 does not implement overwrite. -ADR-005 moved from Backlog to Accepted: .clarion/ git-tracking policy -(committed: clarion.db, config.json, .gitignore, runs/*/config.yaml, +ADR-005 moved from Backlog to Accepted: .loomweave/ git-tracking policy +(committed: loomweave.db, config.json, .gitignore, runs/*/config.yaml, stats.json, partial.json; excluded: WAL/SHM sidecars, *.shadow.db, runs/*/log.jsonl, tmp/, logs/). UQ-WP1-04 resolved. @@ -2064,14 +2064,14 @@ EOF ## Task 6: Writer-actor (L3) **Files:** -- Create: `/home/john/clarion/crates/clarion-storage/src/commands.rs` -- Create: `/home/john/clarion/crates/clarion-storage/src/writer.rs` -- Modify: `/home/john/clarion/crates/clarion-storage/src/lib.rs` -- Create: `/home/john/clarion/crates/clarion-storage/tests/writer_actor.rs` +- Create: `/home/john/loomweave/crates/loomweave-storage/src/commands.rs` +- Create: `/home/john/loomweave/crates/loomweave-storage/src/writer.rs` +- Modify: `/home/john/loomweave/crates/loomweave-storage/src/lib.rs` +- Create: `/home/john/loomweave/crates/loomweave-storage/tests/writer_actor.rs` - [ ] **Step 1: Write the command enum and entity record** -Write `/home/john/clarion/crates/clarion-storage/src/commands.rs`: +Write `/home/john/loomweave/crates/loomweave-storage/src/commands.rs`: ```rust //! Writer-actor command protocol (L3 lock-in). @@ -2092,7 +2092,7 @@ use crate::error::StorageError; pub type Ack = oneshot::Sender>; /// Run status values. Extended in later WPs; Sprint 1 uses only -/// `SkippedNoPlugins` (from `clarion analyze` without plugins wired) and +/// `SkippedNoPlugins` (from `loomweave analyze` without plugins wired) and /// `Failed` (explicit FailRun). #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum RunStatus { @@ -2183,7 +2183,7 @@ pub enum WriterCmd { - [ ] **Step 2: Write the writer-actor itself** -Write `/home/john/clarion/crates/clarion-storage/src/writer.rs`: +Write `/home/john/loomweave/crates/loomweave-storage/src/writer.rs`: ```rust //! Writer-actor implementation (L3 lock-in) per ADR-011. @@ -2488,10 +2488,10 @@ fn fail_run( - [ ] **Step 3: Export the new modules** -Modify `/home/john/clarion/crates/clarion-storage/src/lib.rs` to: +Modify `/home/john/loomweave/crates/loomweave-storage/src/lib.rs` to: ```rust -//! clarion-storage — SQLite layer, writer-actor, reader pool. +//! loomweave-storage — SQLite layer, writer-actor, reader pool. pub mod commands; pub mod error; @@ -2508,7 +2508,7 @@ pub use writer::{Writer, DEFAULT_BATCH_SIZE, DEFAULT_CHANNEL_CAPACITY}; - [ ] **Step 4: Write the integration tests** -Write `/home/john/clarion/crates/clarion-storage/tests/writer_actor.rs`: +Write `/home/john/loomweave/crates/loomweave-storage/tests/writer_actor.rs`: ```rust //! Writer-actor integration tests. @@ -2520,13 +2520,13 @@ use std::sync::atomic::Ordering; use rusqlite::Connection; use tokio::sync::oneshot; -use clarion_storage::{ +use loomweave_storage::{ commands::{EntityRecord, RunStatus, WriterCmd}, pragma, schema, ReaderPool, Writer, }; fn prepared_db(dir: &tempfile::TempDir) -> std::path::PathBuf { - let path = dir.path().join("clarion.db"); + let path = dir.path().join("loomweave.db"); let mut conn = Connection::open(&path).unwrap(); pragma::apply_write_pragmas(&conn).unwrap(); schema::apply_migrations(&mut conn).unwrap(); @@ -2564,8 +2564,8 @@ fn make_entity(id: &str) -> EntityRecord { async fn send( tx: &tokio::sync::mpsc::Sender, - build: impl FnOnce(oneshot::Sender>) -> WriterCmd, -) -> Result { + build: impl FnOnce(oneshot::Sender>) -> WriterCmd, +) -> Result { let (ack_tx, ack_rx) = oneshot::channel(); tx.send(build(ack_tx)).await.unwrap(); ack_rx.await.unwrap() @@ -2763,7 +2763,7 @@ async fn fail_run_rolls_back_pending_inserts() { - [ ] **Step 5: Run the tests** ```bash -cd /home/john/clarion && cargo nextest run -p clarion-storage --test writer_actor +cd /home/john/loomweave && cargo nextest run -p loomweave-storage --test writer_actor ``` Expected: 3 tests pass. The `batch_size_fifty_commits_every_fifty_inserts` test is the L3 lock-in proof. @@ -2771,7 +2771,7 @@ Expected: 3 tests pass. The `batch_size_fifty_commits_every_fifty_inserts` test - [ ] **Step 6: Run every storage-crate test** ```bash -cd /home/john/clarion && cargo nextest run -p clarion-storage +cd /home/john/loomweave && cargo nextest run -p loomweave-storage ``` Expected: 12 tests pass in total (7 schema + 2 reader + 3 writer). @@ -2779,13 +2779,13 @@ Expected: 12 tests pass in total (7 schema + 2 reader + 3 writer). - [ ] **Step 7: Clippy clean** ```bash -cd /home/john/clarion && cargo clippy -p clarion-storage --all-targets -- -D warnings +cd /home/john/loomweave && cargo clippy -p loomweave-storage --all-targets -- -D warnings ``` - [ ] **Step 8: Commit** ```bash -cd /home/john/clarion && git add crates/clarion-storage/ && git commit -m "$(cat <<'EOF' +cd /home/john/loomweave && git add crates/loomweave-storage/ && git commit -m "$(cat <<'EOF' feat(wp1): L3 writer-actor (tokio::task) with per-N transaction batch Writer::spawn() starts a tokio::spawn_blocking task owning the sole write @@ -2809,21 +2809,21 @@ EOF --- -## Task 7: `clarion analyze` skeleton (no plugin) +## Task 7: `loomweave analyze` skeleton (no plugin) **Files:** -- Create: `/home/john/clarion/crates/clarion-cli/src/analyze.rs` -- Modify: `/home/john/clarion/crates/clarion-cli/src/main.rs` -- Create: `/home/john/clarion/crates/clarion-cli/tests/analyze.rs` +- Create: `/home/john/loomweave/crates/loomweave-cli/src/analyze.rs` +- Modify: `/home/john/loomweave/crates/loomweave-cli/src/main.rs` +- Create: `/home/john/loomweave/crates/loomweave-cli/tests/analyze.rs` - [ ] **Step 1: Implement the analyze subcommand** -Write `/home/john/clarion/crates/clarion-cli/src/analyze.rs`: +Write `/home/john/loomweave/crates/loomweave-cli/src/analyze.rs`: ```rust -//! `clarion analyze` — Sprint 1 skeleton. +//! `loomweave analyze` — Sprint 1 skeleton. //! -//! Opens .clarion/clarion.db, begins a run, logs a warning that no plugins +//! Opens .loomweave/loomweave.db, begins a run, logs a warning that no plugins //! are wired, and commits the run with status `skipped_no_plugins`. WP2 //! replaces this body with real plugin spawning. @@ -2833,7 +2833,7 @@ use anyhow::{bail, Context, Result}; use tokio::sync::oneshot; use uuid::Uuid; -use clarion_storage::{ +use loomweave_storage::{ commands::{RunStatus, WriterCmd}, Writer, DEFAULT_BATCH_SIZE, DEFAULT_CHANNEL_CAPACITY, }; @@ -2842,14 +2842,14 @@ pub async fn run(project_path: PathBuf) -> Result<()> { let project_root = project_path .canonicalize() .with_context(|| format!("cannot canonicalise path {}", project_path.display()))?; - let clarion_dir = project_root.join(".clarion"); - if !clarion_dir.exists() { + let loomweave_dir = project_root.join(".loomweave"); + if !loomweave_dir.exists() { bail!( - "{} has no .clarion/ directory. Run `clarion install` first.", + "{} has no .loomweave/ directory. Run `loomweave install` first.", project_root.display() ); } - let db_path = clarion_dir.join("clarion.db"); + let db_path = loomweave_dir.join("loomweave.db"); let (writer, handle) = Writer::spawn(db_path, DEFAULT_BATCH_SIZE, DEFAULT_CHANNEL_CAPACITY) @@ -2937,7 +2937,7 @@ fn gmtime(mut secs: u64) -> (u32, u32, u32, u32, u32, u32) { } ``` -Add the `uuid` dependency to `/home/john/clarion/crates/clarion-cli/Cargo.toml`: +Add the `uuid` dependency to `/home/john/loomweave/crates/loomweave-cli/Cargo.toml`: ```toml [dependencies] @@ -2951,11 +2951,11 @@ Also add `uuid` to the root `Cargo.toml` `[workspace.dependencies]`: uuid = { version = "1", features = ["v4"] } ``` -Then reference it from `clarion-cli` with `uuid.workspace = true`. (Prefer the workspace form — keeps version bumps centralised.) +Then reference it from `loomweave-cli` with `uuid.workspace = true`. (Prefer the workspace form — keeps version bumps centralised.) - [ ] **Step 2: Wire analyze into `main.rs`** -Replace `/home/john/clarion/crates/clarion-cli/src/main.rs` with: +Replace `/home/john/loomweave/crates/loomweave-cli/src/main.rs` with: ```rust mod analyze; @@ -2988,34 +2988,34 @@ fn init_tracing() { - [ ] **Step 3: Write the integration tests** -Write `/home/john/clarion/crates/clarion-cli/tests/analyze.rs`: +Write `/home/john/loomweave/crates/loomweave-cli/tests/analyze.rs`: ```rust -//! `clarion analyze` Sprint-1 integration test. +//! `loomweave analyze` Sprint-1 integration test. use assert_cmd::Command; use rusqlite::Connection; -fn clarion_bin() -> Command { - Command::cargo_bin("clarion").expect("clarion binary") +fn loomweave_bin() -> Command { + Command::cargo_bin("loomweave").expect("loomweave binary") } #[test] fn analyze_without_plugins_writes_skipped_run_row() { let dir = tempfile::tempdir().unwrap(); - clarion_bin() + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .assert() .success(); - clarion_bin() + loomweave_bin() .args(["analyze"]) .arg(dir.path()) .assert() .success(); - let conn = Connection::open(dir.path().join(".clarion/clarion.db")).unwrap(); + let conn = Connection::open(dir.path().join(".loomweave/loomweave.db")).unwrap(); let (count, status): (i64, String) = conn .query_row( "SELECT COUNT(*), COALESCE(MAX(status), '') FROM runs", @@ -3033,16 +3033,16 @@ fn analyze_without_plugins_writes_skipped_run_row() { } #[test] -fn analyze_fails_cleanly_if_clarion_dir_missing() { +fn analyze_fails_cleanly_if_loomweave_dir_missing() { let dir = tempfile::tempdir().unwrap(); - let out = clarion_bin() + let out = loomweave_bin() .args(["analyze"]) .arg(dir.path()) .assert() .failure(); let stderr = String::from_utf8(out.get_output().stderr.clone()).unwrap(); assert!( - stderr.contains("clarion install"), + stderr.contains("loomweave install"), "error did not point operator at install: {stderr}" ); } @@ -3051,7 +3051,7 @@ fn analyze_fails_cleanly_if_clarion_dir_missing() { - [ ] **Step 4: Run the tests** ```bash -cd /home/john/clarion && cargo nextest run -p clarion-cli --test analyze +cd /home/john/loomweave && cargo nextest run -p loomweave-cli --test analyze ``` Expected: 2 tests pass. @@ -3059,19 +3059,19 @@ Expected: 2 tests pass. - [ ] **Step 5: Clippy clean** ```bash -cd /home/john/clarion && cargo clippy -p clarion-cli --all-targets -- -D warnings +cd /home/john/loomweave && cargo clippy -p loomweave-cli --all-targets -- -D warnings ``` - [ ] **Step 6: Commit** ```bash -cd /home/john/clarion && git add Cargo.toml crates/clarion-cli/ && git commit -m "$(cat <<'EOF' -feat(wp1): clarion analyze skeleton (plugin wiring deferred to WP2) +cd /home/john/loomweave && git add Cargo.toml crates/loomweave-cli/ && git commit -m "$(cat <<'EOF' +feat(wp1): loomweave analyze skeleton (plugin wiring deferred to WP2) -clarion analyze opens .clarion/clarion.db, BeginRun → CommitRun with +loomweave analyze opens .loomweave/loomweave.db, BeginRun → CommitRun with status 'skipped_no_plugins'. Warns via tracing::info! that no plugins -are wired. Fails cleanly with a `clarion install`-pointing message if -.clarion/ is missing. +are wired. Fails cleanly with a `loomweave install`-pointing message if +.loomweave/ is missing. uuid v4 added as a workspace dependency for run-id generation. Minimal inline gmtime() avoids pulling chrono solely for ISO-8601 formatting; @@ -3079,7 +3079,7 @@ later WPs that need richer time handling can promote chrono to a workspace dependency then. 2 integration tests cover the happy path (one runs row, zero entities) -and the missing-.clarion/ error path. +and the missing-.loomweave/ error path. EOF )" ``` @@ -3089,12 +3089,12 @@ EOF ## Task 8: LlmProvider trait stub **Files:** -- Create: `/home/john/clarion/crates/clarion-core/src/llm_provider.rs` -- Modify: `/home/john/clarion/crates/clarion-core/src/lib.rs` +- Create: `/home/john/loomweave/crates/loomweave-core/src/llm_provider.rs` +- Modify: `/home/john/loomweave/crates/loomweave-core/src/lib.rs` - [ ] **Step 1: Write the trait and test** -Write `/home/john/clarion/crates/clarion-core/src/llm_provider.rs`: +Write `/home/john/loomweave/crates/loomweave-core/src/llm_provider.rs`: ```rust //! LlmProvider trait stub. @@ -3143,10 +3143,10 @@ mod tests { - [ ] **Step 2: Export from `lib.rs`** -Modify `/home/john/clarion/crates/clarion-core/src/lib.rs` to: +Modify `/home/john/loomweave/crates/loomweave-core/src/lib.rs` to: ```rust -//! clarion-core — domain types, identifiers, and provider traits. +//! loomweave-core — domain types, identifiers, and provider traits. pub mod entity_id; pub mod llm_provider; @@ -3158,24 +3158,24 @@ pub use llm_provider::{LlmProvider, NoopProvider}; - [ ] **Step 3: Run the tests** ```bash -cd /home/john/clarion && cargo nextest run -p clarion-core +cd /home/john/loomweave && cargo nextest run -p loomweave-core ``` -Expected: all clarion-core tests pass (13 entity_id + 2 llm_provider = 15). +Expected: all loomweave-core tests pass (13 entity_id + 2 llm_provider = 15). - [ ] **Step 4: Clippy clean** ```bash -cd /home/john/clarion && cargo clippy -p clarion-core --all-targets -- -D warnings +cd /home/john/loomweave && cargo clippy -p loomweave-core --all-targets -- -D warnings ``` - [ ] **Step 5: Commit** ```bash -cd /home/john/clarion && git add crates/clarion-core/ && git commit -m "$(cat <<'EOF' +cd /home/john/loomweave && git add crates/loomweave-core/ && git commit -m "$(cat <<'EOF' feat(wp1): LlmProvider trait stub for WP6 -LlmProvider + NoopProvider in clarion-core. NoopProvider::name() panics +LlmProvider + NoopProvider in loomweave-core. NoopProvider::name() panics loudly — if it ever fires, some WP1 code is reaching for a real provider before WP6 is wired. 2 tests: trait implementation and panic contract. EOF @@ -3187,11 +3187,11 @@ EOF ## Task 9: End-to-end WP1 smoke test **Files:** -- Create: `/home/john/clarion/crates/clarion-cli/tests/wp1_e2e.rs` +- Create: `/home/john/loomweave/crates/loomweave-cli/tests/wp1_e2e.rs` - [ ] **Step 1: Write the end-to-end smoke test** -Write `/home/john/clarion/crates/clarion-cli/tests/wp1_e2e.rs`: +Write `/home/john/loomweave/crates/loomweave-cli/tests/wp1_e2e.rs`: ```rust //! End-to-end WP1 smoke test — the minimum that must work at WP1 close. @@ -3202,36 +3202,36 @@ Write `/home/john/clarion/crates/clarion-cli/tests/wp1_e2e.rs`: use assert_cmd::Command; use rusqlite::Connection; -fn clarion_bin() -> Command { - Command::cargo_bin("clarion").expect("clarion binary") +fn loomweave_bin() -> Command { + Command::cargo_bin("loomweave").expect("loomweave binary") } #[test] fn wp1_walking_skeleton_end_to_end() { let dir = tempfile::tempdir().unwrap(); - // Step 1: clarion install - clarion_bin() + // Step 1: loomweave install + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .assert() .success(); - let clarion_dir = dir.path().join(".clarion"); - assert!(clarion_dir.join("clarion.db").exists()); - assert!(clarion_dir.join("config.json").exists()); - assert!(clarion_dir.join(".gitignore").exists()); - assert!(dir.path().join("clarion.yaml").exists()); + let loomweave_dir = dir.path().join(".loomweave"); + assert!(loomweave_dir.join("loomweave.db").exists()); + assert!(loomweave_dir.join("config.json").exists()); + assert!(loomweave_dir.join(".gitignore").exists()); + assert!(dir.path().join("loomweave.yaml").exists()); - // Step 2: clarion analyze (no plugins yet — WP2 wires them) - clarion_bin() + // Step 2: loomweave analyze (no plugins yet — WP2 wires them) + loomweave_bin() .args(["analyze"]) .arg(dir.path()) .assert() .success(); // Step 3: verify expected shape in the DB. - let conn = Connection::open(clarion_dir.join("clarion.db")).unwrap(); + let conn = Connection::open(loomweave_dir.join("loomweave.db")).unwrap(); let migration_version: i64 = conn .query_row("SELECT MAX(version) FROM schema_migrations", [], |row| row.get(0)) @@ -3261,7 +3261,7 @@ fn wp1_walking_skeleton_end_to_end() { - [ ] **Step 2: Run the full workspace test suite one last time** ```bash -cd /home/john/clarion && cargo nextest run --workspace --all-features +cd /home/john/loomweave && cargo nextest run --workspace --all-features ``` Expected: all tests green. Count should be ~20 (15 core + 2 reader + 7 schema + 3 writer + 4 install + 2 analyze + 1 e2e = in that neighbourhood; exact count depends on which negative cases expanded). @@ -3269,20 +3269,20 @@ Expected: all tests green. Count should be ~20 (15 core + 2 reader + 7 schema + - [ ] **Step 3: Release-profile build** ```bash -cd /home/john/clarion && cargo build --workspace --release +cd /home/john/loomweave && cargo build --workspace --release ``` -Expected: clean compile, `target/release/clarion` exists. If any warning-as-error surfaces, fix it in the relevant crate's code path, not by downgrading the lint. +Expected: clean compile, `target/release/loomweave` exists. If any warning-as-error surfaces, fix it in the relevant crate's code path, not by downgrading the lint. - [ ] **Step 4: Full ADR-023 gate sweep before commit** Every gate below must exit 0 before Task 9 commits. CI runs the same set against the PR; running them locally first avoids PR-cycle churn. ```bash -cd /home/john/clarion && cargo fmt --all -- --check -cd /home/john/clarion && cargo clippy --workspace --all-targets --all-features -- -D warnings -cd /home/john/clarion && cargo doc --workspace --no-deps --all-features -cd /home/john/clarion && cargo deny check +cd /home/john/loomweave && cargo fmt --all -- --check +cd /home/john/loomweave && cargo clippy --workspace --all-targets --all-features -- -D warnings +cd /home/john/loomweave && cargo doc --workspace --no-deps --all-features +cd /home/john/loomweave && cargo deny check ``` If any gate fails, fix in-place (don't loosen the lint, don't add allowlist entries without a commit-message justification, don't skip `--all-features`). @@ -3290,11 +3290,11 @@ If any gate fails, fix in-place (don't loosen the lint, don't add allowlist entr - [ ] **Step 5: Commit** ```bash -cd /home/john/clarion && git add crates/clarion-cli/tests/wp1_e2e.rs && git commit -m "$(cat <<'EOF' +cd /home/john/loomweave && git add crates/loomweave-cli/tests/wp1_e2e.rs && git commit -m "$(cat <<'EOF' test(wp1): end-to-end smoke test -wp1_e2e.rs runs the README §3 demo script at WP1 scope: clarion install -creates .clarion/; clarion analyze commits a run with status +wp1_e2e.rs runs the README §3 demo script at WP1 scope: loomweave install +creates .loomweave/; loomweave analyze commits a run with status skipped_no_plugins and zero entities; migration 0001 recorded as schema_version 1. WP2+WP3 extend this test with non-zero entity asserts. EOF @@ -3306,19 +3306,19 @@ EOF ## Task 10: Sign-off ladder updates **Files:** -- Modify: `/home/john/clarion/docs/implementation/sprint-1/signoffs.md` -- Modify: `/home/john/clarion/docs/implementation/sprint-1/README.md` -- Modify: `/home/john/clarion/docs/implementation/sprint-1/wp1-scaffold.md` +- Modify: `/home/john/loomweave/docs/implementation/sprint-1/signoffs.md` +- Modify: `/home/john/loomweave/docs/implementation/sprint-1/README.md` +- Modify: `/home/john/loomweave/docs/implementation/sprint-1/wp1-scaffold.md` - [ ] **Step 1: Tick Tier A.1.* boxes in `signoffs.md`** -In `/home/john/clarion/docs/implementation/sprint-1/signoffs.md`, change each Tier A.1 checkbox from `- [ ]` to `- [x]` after verifying the cited proof (commit hash, test-run log, or doc commit). For lock-in rows A.1.3, A.1.4, A.1.5, fill in the `locked on ______` date — use the date the final WP1 commit lands (execute `git log -1 --format=%as HEAD` to get it). Also tick A.1.6, A.1.7, A.1.8, A.1.9, A.1.10. +In `/home/john/loomweave/docs/implementation/sprint-1/signoffs.md`, change each Tier A.1 checkbox from `- [ ]` to `- [x]` after verifying the cited proof (commit hash, test-run log, or doc commit). For lock-in rows A.1.3, A.1.4, A.1.5, fill in the `locked on ______` date — use the date the final WP1 commit lands (execute `git log -1 --format=%as HEAD` to get it). Also tick A.1.6, A.1.7, A.1.8, A.1.9, A.1.10. Do **not** tick Tier A.2, A.3, A.4, A.5, A.6 — those belong to WP2/WP3/sprint-close. - [ ] **Step 2: Stamp lock-in dates in README.md §4** -In `/home/john/clarion/docs/implementation/sprint-1/README.md` §4 Lock-in summary, annotate L1, L2, L3 rows with the same `locked on ` stamp added to signoffs.md. +In `/home/john/loomweave/docs/implementation/sprint-1/README.md` §4 Lock-in summary, annotate L1, L2, L3 rows with the same `locked on ` stamp added to signoffs.md. - [ ] **Step 3: Mark UQ-WP1-* resolved in `wp1-scaffold.md §5`** @@ -3331,13 +3331,13 @@ Each UQ-WP1-01 through UQ-WP1-09 entry gets a `**Resolved**: ` line tha - UQ-WP1-05 — resolved in Task 3: full `runs` shape; plugin-invocation columns carried as JSON in the `config` column, populated by WP2. - UQ-WP1-06 — resolved in Task 3: `StorageError` wraps `rusqlite::Error` via `thiserror`. - UQ-WP1-07 — resolved in Task 2: `EntityIdError::SegmentContainsColon`. -- UQ-WP1-08 — resolved in Task 5: refuses if `.clarion/` exists; `--force` stub. +- UQ-WP1-08 — resolved in Task 5: refuses if `.loomweave/` exists; `--force` stub. - UQ-WP1-09 — resolved in Task 1: Rust 2021 + stable via `rust-toolchain.toml`. - [ ] **Step 4: Commit** ```bash -cd /home/john/clarion && git add docs/implementation/sprint-1/ && git commit -m "$(cat <<'EOF' +cd /home/john/loomweave && git add docs/implementation/sprint-1/ && git commit -m "$(cat <<'EOF' docs(sprint-1): tick WP1 sign-off and stamp L1/L2/L3 lock-ins Tier A.1 boxes ticked in signoffs.md with the WP1-close commit date. @@ -3361,9 +3361,9 @@ EOF | §6.Task 2 Entity-ID assembler | Task 2 | ✓ | | §6.Task 3 Schema migration | Task 3 | ✓ | | §6.Task 4 Reader pool | Task 4 | ✓ | -| §6.Task 5 `clarion install` + ADR-005 | Task 5 | ✓ | +| §6.Task 5 `loomweave install` + ADR-005 | Task 5 | ✓ | | §6.Task 6 Writer-actor | Task 6 | ✓ | -| §6.Task 7 `clarion analyze` | Task 7 | ✓ | +| §6.Task 7 `loomweave analyze` | Task 7 | ✓ | | §6.Task 8 LlmProvider stub | Task 8 | ✓ | | §6.Task 9 E2E smoke | Task 9 | ✓ | | §8 Exit criteria sign-off | Task 10 | ✓ | @@ -3372,7 +3372,7 @@ Every lock-in (L1/L2/L3) has a Task that lands it. Every UQ-WP1-* has a designat **Type consistency spot-check:** -- `EntityId` / `entity_id()` signature identical between Task 2 definition and Task 8's stub (both re-exported from `clarion-core::lib`). +- `EntityId` / `entity_id()` signature identical between Task 2 definition and Task 8's stub (both re-exported from `loomweave-core::lib`). - `WriterCmd::{BeginRun,InsertEntity,CommitRun,FailRun}` — same four variants in `commands.rs` (Task 6 Step 1), `writer.rs` dispatch (Task 6 Step 2), and the test harness (Task 6 Step 4) and `analyze.rs` (Task 7 Step 1). - `RunStatus::{SkippedNoPlugins,Completed,Failed}` — used in `writer.rs`, `commands.rs`, `analyze.rs`, and the integration tests. Strings match migration `0001` implicit values (`skipped_no_plugins`, `completed`, `failed`, plus `running` from `BeginRun`). - `Writer::commits_observed` is a public `Arc` — accessed by name in Task 6 tests. diff --git a/docs/implementation/agent-plans/2026-04-18-wp2-plugin-host.md b/docs/implementation/agent-plans/2026-04-18-wp2-plugin-host.md index d96421d2..ddcf253b 100644 --- a/docs/implementation/agent-plans/2026-04-18-wp2-plugin-host.md +++ b/docs/implementation/agent-plans/2026-04-18-wp2-plugin-host.md @@ -2,11 +2,11 @@ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. -**Goal:** Ship the Sprint 1 walking-skeleton plugin host — JSON-RPC over Content-Length framed stdio (L4), `plugin.toml` manifest parser (L5), core-enforced minimums (L6, all four controls per ADR-021 §2), plugin discovery (L9), crash-loop breaker, and wired `clarion analyze` that spawns a mock plugin and persists entities through the WP1 writer-actor. +**Goal:** Ship the Sprint 1 walking-skeleton plugin host — JSON-RPC over Content-Length framed stdio (L4), `plugin.toml` manifest parser (L5), core-enforced minimums (L6, all four controls per ADR-021 §2), plugin discovery (L9), crash-loop breaker, and wired `loomweave analyze` that spawns a mock plugin and persists entities through the WP1 writer-actor. -**Architecture:** Plugin host lives inside `clarion-core` under a new `plugin/` module (per WP2 §3). Subprocesses are managed via `tokio::process::Command` with piped stdio; framing is hand-rolled LSP-style Content-Length on top of `tokio::io::AsyncRead`/`AsyncWrite` (UQ-WP2-02 resolution). ADR-021 §2d's `RLIMIT_AS` is applied inside `CommandExt::pre_exec` — the only unsafe call site in the workspace, gated by `#[allow(unsafe_code)]` with a safety comment. A fresh workspace crate `clarion-mock-plugin` supplies the fixture binary that the integration-test subprocess spawns. +**Architecture:** Plugin host lives inside `loomweave-core` under a new `plugin/` module (per WP2 §3). Subprocesses are managed via `tokio::process::Command` with piped stdio; framing is hand-rolled LSP-style Content-Length on top of `tokio::io::AsyncRead`/`AsyncWrite` (UQ-WP2-02 resolution). ADR-021 §2d's `RLIMIT_AS` is applied inside `CommandExt::pre_exec` — the only unsafe call site in the workspace, gated by `#[allow(unsafe_code)]` with a safety comment. A fresh workspace crate `loomweave-mock-plugin` supplies the fixture binary that the integration-test subprocess spawns. -**Tech Stack:** Additions to the WP1 floor — `toml = "0.8"` (manifest parsing); `nix = { version = "0.28", features = ["resource"] }` (`setrlimit(RLIMIT_AS)`); `tokio` features expanded with `"process"` + `"io-util"`; `tokio-util = { version = "0.7", features = ["codec"] }` is **not** adopted (hand-rolled framing keeps the dependency surface small per UQ-WP2-02). New workspace member `crates/clarion-mock-plugin/` (test fixture binary). All ADR-023 gates (fmt / pedantic clippy / nextest / doc / deny / build dev + release) remain green on every commit. +**Tech Stack:** Additions to the WP1 floor — `toml = "0.8"` (manifest parsing); `nix = { version = "0.28", features = ["resource"] }` (`setrlimit(RLIMIT_AS)`); `tokio` features expanded with `"process"` + `"io-util"`; `tokio-util = { version = "0.7", features = ["codec"] }` is **not** adopted (hand-rolled framing keeps the dependency surface small per UQ-WP2-02). New workspace member `crates/loomweave-mock-plugin/` (test fixture binary). All ADR-023 gates (fmt / pedantic clippy / nextest / doc / deny / build dev + release) remain green on every commit. **Source spec:** `docs/implementation/sprint-1/wp2-plugin-host.md`. If this plan and the spec disagree, the spec is authoritative on *what* to build; this plan is authoritative on *how* to build it step-by-step. @@ -14,17 +14,17 @@ **Resolved UQs before starting:** -- **UQ-WP2-01** — Plugin discovery: PATH scan for `clarion-plugin-*` binaries + neighboring `plugin.toml` fallback to `/share/clarion/plugins//plugin.toml`. Sprint 1 hard-codes no env var overrides; `clarion.yaml` discovery config surface is WP6. **Resolved by Task 5.** +- **UQ-WP2-01** — Plugin discovery: PATH scan for `loomweave-plugin-*` binaries + neighboring `plugin.toml` fallback to `/share/loomweave/plugins//plugin.toml`. Sprint 1 hard-codes no env var overrides; `loomweave.yaml` discovery config surface is WP6. **Resolved by Task 5.** - **UQ-WP2-02** — JSON-RPC library: hand-rolled over `serde_json`. Walking skeleton is unidirectional unary; framing is Content-Length + `\r\n\r\n` + body; one request → one response. **Resolved by Task 2.** - **UQ-WP2-03** — Path jail symlink semantics: canonicalise via `std::fs::canonicalize` which follows symlinks. A symlink inside the root that resolves outside is rejected. **Resolved by Task 4.** -- **UQ-WP2-04** — Content-Length ceiling: **8 MiB** per frame (ADR-021 §2b default; floor 1 MiB). Transport parser refuses the frame before body deserialisation; host kills plugin with SIGTERM → SIGKILL; emits `CLA-INFRA-PLUGIN-FRAME-OVERSIZE`. **Resolved by Task 4.** -- **UQ-WP2-05** — Entity-count cap: **500,000** combined `entity + edge + finding` records per run (ADR-021 §2c default; floor 10,000). On trip: in-flight batch flushed, plugin killed, run enters partial-results; `CLA-INFRA-PLUGIN-ENTITY-CAP` emitted. Sprint 1 emits only entity notifications; the cap counts them and leaves the edge/finding path ready for WP3+. **Resolved by Task 4.** +- **UQ-WP2-04** — Content-Length ceiling: **8 MiB** per frame (ADR-021 §2b default; floor 1 MiB). Transport parser refuses the frame before body deserialisation; host kills plugin with SIGTERM → SIGKILL; emits `LMWV-INFRA-PLUGIN-FRAME-OVERSIZE`. **Resolved by Task 4.** +- **UQ-WP2-05** — Entity-count cap: **500,000** combined `entity + edge + finding` records per run (ADR-021 §2c default; floor 10,000). On trip: in-flight batch flushed, plugin killed, run enters partial-results; `LMWV-INFRA-PLUGIN-ENTITY-CAP` emitted. Sprint 1 emits only entity notifications; the cap counts them and leaves the edge/finding path ready for WP3+. **Resolved by Task 4.** - **UQ-WP2-06** — prlimit on non-Linux: `#[cfg(target_os = "linux")]`-gate the `setrlimit` pre_exec; on other targets, log a one-shot tracing warning and proceed without the limit. Sprint 1 scope is Linux per WP1 §1; macOS path (ADR-021 §2d names `setrlimit(RLIMIT_AS)` on POSIX) lands with whichever sprint first adds macOS CI. **Resolved by Task 4.** - **UQ-WP2-07** — Plugin non-entity output: stderr is free-form and forwarded line-by-line to `tracing::info!` target `plugin::`. Stdout is JSON-RPC only. Progress notifications are deferred. **Resolved by Task 3.** - **UQ-WP2-08** — Plugin stdout discipline: documented in the plugin-author guide (WP3 scope); host does not enforce. A stray `print()` in a Python plugin corrupts framing → transport parse error → plugin killed → crash-loop counter ticks. **Resolved by Task 3** (doc note in module rustdoc). -- **UQ-WP2-09** — Manifest hot-reload: Sprint 1 always re-reads on `clarion analyze`. Caching belongs to WP8 `serve`. **Resolved by Task 2** (manifest is re-parsed on every `PluginHost::spawn`). -- **UQ-WP2-10** — Crash-loop breaker parameters: **>3 crashes in 60s** → plugin disabled for the run (`CLA-INFRA-PLUGIN-DISABLED-CRASH-LOOP`, per ADR-002 + ADR-021 Layer 3). Path-escape sub-breaker: **>10 escapes in 60s** → plugin killed (`CLA-INFRA-PLUGIN-DISABLED-PATH-ESCAPE`, per ADR-021 §2a). Both hard-coded in Sprint 1; config surface deferred to WP6. **Resolved by Task 7.** -- **UQ-WP2-11** — Identity-mismatch rejection: host reconstructs `entity_id(plugin_id, kind, qualified_name)` and compares byte-for-byte against the plugin's returned `id`. Mismatch → drop entity, emit `CLA-INFRA-PLUGIN-ENTITY-ID-MISMATCH`, plugin stays alive. **Resolved by Task 6.** +- **UQ-WP2-09** — Manifest hot-reload: Sprint 1 always re-reads on `loomweave analyze`. Caching belongs to WP8 `serve`. **Resolved by Task 2** (manifest is re-parsed on every `PluginHost::spawn`). +- **UQ-WP2-10** — Crash-loop breaker parameters: **>3 crashes in 60s** → plugin disabled for the run (`LMWV-INFRA-PLUGIN-DISABLED-CRASH-LOOP`, per ADR-002 + ADR-021 Layer 3). Path-escape sub-breaker: **>10 escapes in 60s** → plugin killed (`LMWV-INFRA-PLUGIN-DISABLED-PATH-ESCAPE`, per ADR-021 §2a). Both hard-coded in Sprint 1; config surface deferred to WP6. **Resolved by Task 7.** +- **UQ-WP2-11** — Identity-mismatch rejection: host reconstructs `entity_id(plugin_id, kind, qualified_name)` and compares byte-for-byte against the plugin's returned `id`. Mismatch → drop entity, emit `LMWV-INFRA-PLUGIN-ENTITY-ID-MISMATCH`, plugin stays alive. **Resolved by Task 6.** **Scope note on "unsafe_code = forbid":** WP1's workspace `[lints.rust]` sets `unsafe_code = "forbid"`. ADR-021 §2d's enforcement point is `CommandExt::pre_exec` — an unsafe API because the closure runs in the fork'd child before exec. Task 4 relaxes the workspace-level lint from `"forbid"` to `"deny"` and places a narrow `#[allow(unsafe_code)]` at the single pre_exec call site in `plugin/limits.rs` with a safety-justifying comment. No other unsafe is introduced. The workspace guarantee becomes: "unsafe is denied except at one audited call site that is the only way to express the ADR-021 §2d enforcement." @@ -32,35 +32,35 @@ | Rule ID | Trigger | |---|---| -| `CLA-INFRA-PLUGIN-PATH-ESCAPE` | Plugin-returned path canonicalises outside `project_root`; entity dropped, plugin alive. | -| `CLA-INFRA-PLUGIN-DISABLED-PATH-ESCAPE` | >10 path-escapes in 60s; plugin killed. | -| `CLA-INFRA-PLUGIN-FRAME-OVERSIZE` | Inbound Content-Length header above the 8 MiB ceiling; plugin killed. | -| `CLA-INFRA-PLUGIN-ENTITY-CAP` | Per-run cumulative record count exceeds 500k; plugin killed, run partial. | -| `CLA-INFRA-PLUGIN-OOM-KILLED` | Plugin exit was `WIFSIGNALED && WTERMSIG == 9` after `RLIMIT_AS` hit. | -| `CLA-INFRA-PLUGIN-DISABLED-CRASH-LOOP` | >3 crashes in 60s; plugin disabled for the run. | -| `CLA-INFRA-PLUGIN-UNDECLARED-KIND` | Entity emitted with `kind` not in manifest's `[ontology].entity_kinds`. | -| `CLA-INFRA-PLUGIN-ENTITY-ID-MISMATCH` | Entity's `id` does not match `entity_id(plugin_id, kind, qualified_name)`. | -| `CLA-INFRA-MANIFEST-MALFORMED` | Manifest fails grammar or required-field validation at `initialize`. | -| `CLA-INFRA-MANIFEST-RESERVED-KIND` | Manifest declares a core-reserved kind (`file`, `subsystem`, `guidance`). | - -**Sprint 1 scope on findings emission:** Sprint 1 does NOT create a `findings` table row for these rule IDs — that path is ADR-013's scanner-ingest API and arrives in WP6. For Sprint 1, each trigger logs via `tracing::warn!(rule_id = "CLA-INFRA-...", ...)` with the offending context as fields. The strings are still authoritative — WP6 reads them when wiring the `Finding` record emission. Tests assert on the log lines via `tracing-test` or captured output, not on DB rows. +| `LMWV-INFRA-PLUGIN-PATH-ESCAPE` | Plugin-returned path canonicalises outside `project_root`; entity dropped, plugin alive. | +| `LMWV-INFRA-PLUGIN-DISABLED-PATH-ESCAPE` | >10 path-escapes in 60s; plugin killed. | +| `LMWV-INFRA-PLUGIN-FRAME-OVERSIZE` | Inbound Content-Length header above the 8 MiB ceiling; plugin killed. | +| `LMWV-INFRA-PLUGIN-ENTITY-CAP` | Per-run cumulative record count exceeds 500k; plugin killed, run partial. | +| `LMWV-INFRA-PLUGIN-OOM-KILLED` | Plugin exit was `WIFSIGNALED && WTERMSIG == 9` after `RLIMIT_AS` hit. | +| `LMWV-INFRA-PLUGIN-DISABLED-CRASH-LOOP` | >3 crashes in 60s; plugin disabled for the run. | +| `LMWV-INFRA-PLUGIN-UNDECLARED-KIND` | Entity emitted with `kind` not in manifest's `[ontology].entity_kinds`. | +| `LMWV-INFRA-PLUGIN-ENTITY-ID-MISMATCH` | Entity's `id` does not match `entity_id(plugin_id, kind, qualified_name)`. | +| `LMWV-INFRA-MANIFEST-MALFORMED` | Manifest fails grammar or required-field validation at `initialize`. | +| `LMWV-INFRA-MANIFEST-RESERVED-KIND` | Manifest declares a core-reserved kind (`file`, `subsystem`, `guidance`). | + +**Sprint 1 scope on findings emission:** Sprint 1 does NOT create a `findings` table row for these rule IDs — that path is ADR-013's scanner-ingest API and arrives in WP6. For Sprint 1, each trigger logs via `tracing::warn!(rule_id = "LMWV-INFRA-...", ...)` with the offending context as fields. The strings are still authoritative — WP6 reads them when wiring the `Finding` record emission. Tests assert on the log lines via `tracing-test` or captured output, not on DB rows. --- ## Task 1: Workspace prep + L5 manifest parser **Files:** -- Modify: `/home/john/clarion/Cargo.toml` (workspace deps: add `toml`, extend `tokio` features) -- Modify: `/home/john/clarion/crates/clarion-core/Cargo.toml` (add `toml`, `serde`, `thiserror`) -- Create: `/home/john/clarion/crates/clarion-core/src/plugin/mod.rs` -- Create: `/home/john/clarion/crates/clarion-core/src/plugin/manifest.rs` -- Modify: `/home/john/clarion/crates/clarion-core/src/lib.rs` (add `pub mod plugin;` re-exports) -- Create: `/home/john/clarion/crates/clarion-core/tests/fixtures/manifest_valid.toml` -- Create: `/home/john/clarion/crates/clarion-core/tests/fixtures/manifest_missing_name.toml` +- Modify: `/home/john/loomweave/Cargo.toml` (workspace deps: add `toml`, extend `tokio` features) +- Modify: `/home/john/loomweave/crates/loomweave-core/Cargo.toml` (add `toml`, `serde`, `thiserror`) +- Create: `/home/john/loomweave/crates/loomweave-core/src/plugin/mod.rs` +- Create: `/home/john/loomweave/crates/loomweave-core/src/plugin/manifest.rs` +- Modify: `/home/john/loomweave/crates/loomweave-core/src/lib.rs` (add `pub mod plugin;` re-exports) +- Create: `/home/john/loomweave/crates/loomweave-core/tests/fixtures/manifest_valid.toml` +- Create: `/home/john/loomweave/crates/loomweave-core/tests/fixtures/manifest_missing_name.toml` - [ ] **Step 1: Extend workspace dependencies** -Modify the top `[workspace.dependencies]` block in `/home/john/clarion/Cargo.toml` — **add** two entries, **modify** the `tokio` line to include `"process"` and `"io-util"` features: +Modify the top `[workspace.dependencies]` block in `/home/john/loomweave/Cargo.toml` — **add** two entries, **modify** the `tokio` line to include `"process"` and `"io-util"` features: ```toml # Replace the existing tokio line with: @@ -72,9 +72,9 @@ toml = "0.8" Do NOT add `nix` here — that arrives in Task 4 where it's first used. -- [ ] **Step 2: Extend `clarion-core`'s Cargo.toml** +- [ ] **Step 2: Extend `loomweave-core`'s Cargo.toml** -Modify `/home/john/clarion/crates/clarion-core/Cargo.toml` — add `toml`, `serde`, and `thiserror` to `[dependencies]`. They may already be present from WP1 (entity_id uses serde + thiserror); add only what's missing. The end state is: +Modify `/home/john/loomweave/crates/loomweave-core/Cargo.toml` — add `toml`, `serde`, and `thiserror` to `[dependencies]`. They may already be present from WP1 (entity_id uses serde + thiserror); add only what's missing. The end state is: ```toml [dependencies] @@ -88,7 +88,7 @@ toml.workspace = true - [ ] **Step 3: Write the module skeleton** -Create `/home/john/clarion/crates/clarion-core/src/plugin/mod.rs`: +Create `/home/john/loomweave/crates/loomweave-core/src/plugin/mod.rs`: ```rust //! Plugin host — subprocess supervision, JSON-RPC transport, manifest @@ -96,12 +96,12 @@ Create `/home/john/clarion/crates/clarion-core/src/plugin/mod.rs`: //! //! # Scope //! -//! This module is the Clarion-side end of the plugin transport defined in -//! [ADR-002](../../clarion/adr/ADR-002-plugin-transport-json-rpc.md) +//! This module is the Loomweave-side end of the plugin transport defined in +//! [ADR-002](../../loomweave/adr/ADR-002-plugin-transport-json-rpc.md) //! and the enforcement surface for -//! [ADR-021](../../clarion/adr/ADR-021-plugin-authority-hybrid.md) +//! [ADR-021](../../loomweave/adr/ADR-021-plugin-authority-hybrid.md) //! §2a-d. The ontology boundary rules from -//! [ADR-022](../../clarion/adr/ADR-022-core-plugin-ontology.md) +//! [ADR-022](../../loomweave/adr/ADR-022-core-plugin-ontology.md) //! are enforced by [`host`] against the manifest parsed by [`manifest`]. //! //! # Sub-modules @@ -119,10 +119,10 @@ pub use manifest::{ - [ ] **Step 4: Export from `lib.rs`** -Modify `/home/john/clarion/crates/clarion-core/src/lib.rs` to declare the new module. The final file body: +Modify `/home/john/loomweave/crates/loomweave-core/src/lib.rs` to declare the new module. The final file body: ```rust -//! clarion-core — domain types, identifiers, provider traits, and plugin host. +//! loomweave-core — domain types, identifiers, provider traits, and plugin host. //! //! WP2 introduces the `plugin` module which drives subprocess plugins //! via `JSON-RPC` over Content-Length framed stdio. The module's public @@ -138,14 +138,14 @@ pub use llm_provider::{LlmProvider, NoopProvider}; - [ ] **Step 5: Add the valid-manifest fixture** -Create `/home/john/clarion/crates/clarion-core/tests/fixtures/manifest_valid.toml`: +Create `/home/john/loomweave/crates/loomweave-core/tests/fixtures/manifest_valid.toml`: ```toml [plugin] -name = "clarion-plugin-python" +name = "loomweave-plugin-python" version = "0.1.0" protocol_version = "1.0" -executable = "clarion-plugin-python" +executable = "loomweave-plugin-python" language = "python" extensions = ["py"] @@ -158,19 +158,19 @@ max_entities_per_run = 100000 [ontology] entity_kinds = ["function", "class", "module", "decorator"] edge_kinds = ["imports", "calls", "decorates", "contains"] -rule_id_prefix = "CLA-PY-" +rule_id_prefix = "LMWV-PY-" ontology_version = "0.1.0" ``` - [ ] **Step 6: Add the missing-name fixture** -Create `/home/john/clarion/crates/clarion-core/tests/fixtures/manifest_missing_name.toml`: +Create `/home/john/loomweave/crates/loomweave-core/tests/fixtures/manifest_missing_name.toml`: ```toml [plugin] version = "0.1.0" protocol_version = "1.0" -executable = "clarion-plugin-python" +executable = "loomweave-plugin-python" language = "python" extensions = ["py"] @@ -183,22 +183,22 @@ max_entities_per_run = 100000 [ontology] entity_kinds = ["function"] edge_kinds = ["imports"] -rule_id_prefix = "CLA-PY-" +rule_id_prefix = "LMWV-PY-" ontology_version = "0.1.0" ``` - [ ] **Step 7: Write the failing tests** -Create `/home/john/clarion/crates/clarion-core/src/plugin/manifest.rs` with the test skeleton first (TDD — tests before implementation): +Create `/home/john/loomweave/crates/loomweave-core/src/plugin/manifest.rs` with the test skeleton first (TDD — tests before implementation): ```rust //! `plugin.toml` parser + validator (L5). //! //! Parses the manifest shape locked by WP2 §L5 and validates against -//! [ADR-022](../../clarion/adr/ADR-022-core-plugin-ontology.md): +//! [ADR-022](../../loomweave/adr/ADR-022-core-plugin-ontology.md): //! plugin `name` must match the identifier grammar; entity kinds cannot //! shadow the core-reserved set (`file`, `subsystem`, `guidance`); rule-ID -//! prefix must be `CLA--` and end with `-`. +//! prefix must be `LMWV--` and end with `-`. //! //! # Sprint 1 scope note //! @@ -221,10 +221,10 @@ mod tests { #[test] fn parses_valid_manifest() { let m = parse_manifest(VALID).expect("valid fixture must parse"); - assert_eq!(m.plugin.name, "clarion-plugin-python"); + assert_eq!(m.plugin.name, "loomweave-plugin-python"); assert_eq!(m.plugin.version, "0.1.0"); assert_eq!(m.plugin.protocol_version, "1.0"); - assert_eq!(m.plugin.executable, "clarion-plugin-python"); + assert_eq!(m.plugin.executable, "loomweave-plugin-python"); assert_eq!(m.plugin.language, "python"); assert_eq!(m.plugin.extensions, vec!["py".to_string()]); assert_eq!(m.capabilities.max_rss_mb, 512); @@ -239,7 +239,7 @@ mod tests { m.ontology.edge_kinds, vec!["imports", "calls", "decorates", "contains"] ); - assert_eq!(m.ontology.rule_id_prefix, "CLA-PY-"); + assert_eq!(m.ontology.rule_id_prefix, "LMWV-PY-"); assert_eq!(m.ontology.ontology_version, "0.1.0"); } @@ -256,10 +256,10 @@ mod tests { fn rejects_zero_rss() { let input = br#" [plugin] -name = "clarion-plugin-x" +name = "loomweave-plugin-x" version = "0.1.0" protocol_version = "1.0" -executable = "clarion-plugin-x" +executable = "loomweave-plugin-x" language = "x" extensions = ["x"] @@ -272,7 +272,7 @@ max_entities_per_run = 100000 [ontology] entity_kinds = ["function"] edge_kinds = ["imports"] -rule_id_prefix = "CLA-X-" +rule_id_prefix = "LMWV-X-" ontology_version = "0.1.0" "#; let err = parse_manifest(input).expect_err("zero RSS must fail"); @@ -286,10 +286,10 @@ ontology_version = "0.1.0" fn rejects_empty_entity_kinds() { let input = br#" [plugin] -name = "clarion-plugin-x" +name = "loomweave-plugin-x" version = "0.1.0" protocol_version = "1.0" -executable = "clarion-plugin-x" +executable = "loomweave-plugin-x" language = "x" extensions = ["x"] @@ -302,7 +302,7 @@ max_entities_per_run = 100000 [ontology] entity_kinds = [] edge_kinds = ["imports"] -rule_id_prefix = "CLA-X-" +rule_id_prefix = "LMWV-X-" ontology_version = "0.1.0" "#; let err = parse_manifest(input).expect_err("empty entity_kinds must fail"); @@ -316,10 +316,10 @@ ontology_version = "0.1.0" fn rejects_rule_id_prefix_without_trailing_dash() { let input = br#" [plugin] -name = "clarion-plugin-x" +name = "loomweave-plugin-x" version = "0.1.0" protocol_version = "1.0" -executable = "clarion-plugin-x" +executable = "loomweave-plugin-x" language = "x" extensions = ["x"] @@ -332,7 +332,7 @@ max_entities_per_run = 100000 [ontology] entity_kinds = ["function"] edge_kinds = ["imports"] -rule_id_prefix = "CLA-X" +rule_id_prefix = "LMWV-X" ontology_version = "0.1.0" "#; let err = parse_manifest(input).expect_err("prefix without '-' must fail"); @@ -346,10 +346,10 @@ ontology_version = "0.1.0" fn rejects_reserved_entity_kind_file() { let input = br#" [plugin] -name = "clarion-plugin-x" +name = "loomweave-plugin-x" version = "0.1.0" protocol_version = "1.0" -executable = "clarion-plugin-x" +executable = "loomweave-plugin-x" language = "x" extensions = ["x"] @@ -362,7 +362,7 @@ max_entities_per_run = 100000 [ontology] entity_kinds = ["function", "file"] edge_kinds = ["imports"] -rule_id_prefix = "CLA-X-" +rule_id_prefix = "LMWV-X-" ontology_version = "0.1.0" "#; let err = parse_manifest(input).expect_err("reserved kind must fail"); @@ -376,10 +376,10 @@ ontology_version = "0.1.0" fn rejects_plugin_name_uppercase() { let input = br#" [plugin] -name = "Clarion-Plugin-X" +name = "Loomweave-Plugin-X" version = "0.1.0" protocol_version = "1.0" -executable = "clarion-plugin-x" +executable = "loomweave-plugin-x" language = "x" extensions = ["x"] @@ -392,7 +392,7 @@ max_entities_per_run = 100000 [ontology] entity_kinds = ["function"] edge_kinds = ["imports"] -rule_id_prefix = "CLA-X-" +rule_id_prefix = "LMWV-X-" ontology_version = "0.1.0" "#; let err = parse_manifest(input).expect_err("uppercase name must fail"); @@ -406,10 +406,10 @@ ontology_version = "0.1.0" fn rejects_empty_extensions() { let input = br#" [plugin] -name = "clarion-plugin-x" +name = "loomweave-plugin-x" version = "0.1.0" protocol_version = "1.0" -executable = "clarion-plugin-x" +executable = "loomweave-plugin-x" language = "x" extensions = [] @@ -422,7 +422,7 @@ max_entities_per_run = 100000 [ontology] entity_kinds = ["function"] edge_kinds = ["imports"] -rule_id_prefix = "CLA-X-" +rule_id_prefix = "LMWV-X-" ontology_version = "0.1.0" "#; let err = parse_manifest(input).expect_err("empty extensions must fail"); @@ -437,7 +437,7 @@ ontology_version = "0.1.0" - [ ] **Step 8: Run tests; expect failure (no types defined)** ```bash -cd /home/john/clarion && cargo nextest run -p clarion-core manifest --no-tests=pass 2>&1 | head -40 +cd /home/john/loomweave && cargo nextest run -p loomweave-core manifest --no-tests=pass 2>&1 | head -40 ``` Expected: compile error `cannot find type 'Manifest'` (and several similar). That's the "red" state. @@ -497,7 +497,7 @@ pub enum ManifestError { #[error( "[plugin].name {value:?} violates ADR-022 grammar \ - (identifier `[a-z][a-z0-9_-]*`, must start with `clarion-plugin-`)" + (identifier `[a-z][a-z0-9_-]*`, must start with `loomweave-plugin-`)" )] InvalidPluginName { value: String }, @@ -514,7 +514,7 @@ pub enum ManifestError { EmptyOntologyField { field: &'static str }, #[error( - "[ontology].rule_id_prefix {value:?} must start with `CLA-` and end \ + "[ontology].rule_id_prefix {value:?} must start with `LMWV-` and end \ with `-` (ADR-022 rule-ID namespace contract)" )] RuleIdPrefixFormat { value: String }, @@ -527,7 +527,7 @@ pub enum ManifestError { } /// Core-reserved entity kinds per -/// [ADR-022](../../clarion/adr/ADR-022-core-plugin-ontology.md). +/// [ADR-022](../../loomweave/adr/ADR-022-core-plugin-ontology.md). /// These are produced by core-owned algorithms (file-discovery, Leiden /// clustering, guidance composition). A plugin declaring any of them in /// its `entity_kinds` list is rejected at manifest parse. @@ -584,7 +584,7 @@ fn validate(m: &Manifest) -> Result<(), ManifestError> { fn validate_plugin_name(name: &str) -> Result<(), ManifestError> { // ADR-022 grammar for plugin identifiers is `[a-z][a-z0-9_]*`. WP2 §L9 - // requires PATH-installable binaries prefixed `clarion-plugin-`, which + // requires PATH-installable binaries prefixed `loomweave-plugin-`, which // broadens the grammar to include `-`. We accept the broader form for // the manifest `name` (which is also the binary name) and narrow it // back to `[a-z][a-z0-9_]*` when deriving `plugin_id` for `EntityId` @@ -608,7 +608,7 @@ fn validate_plugin_name(name: &str) -> Result<(), ManifestError> { }); } } - if !name.starts_with("clarion-plugin-") { + if !name.starts_with("loomweave-plugin-") { return Err(ManifestError::InvalidPluginName { value: name.to_owned(), }); @@ -624,7 +624,7 @@ fn validate_capability(field: &'static str, value: u64) -> Result<(), ManifestEr } fn validate_rule_id_prefix(value: &str) -> Result<(), ManifestError> { - if !value.starts_with("CLA-") || !value.ends_with('-') || value.len() < 6 { + if !value.starts_with("LMWV-") || !value.ends_with('-') || value.len() < 6 { return Err(ManifestError::RuleIdPrefixFormat { value: value.to_owned(), }); @@ -636,7 +636,7 @@ fn validate_rule_id_prefix(value: &str) -> Result<(), ManifestError> { - [ ] **Step 10: Run tests; expect pass** ```bash -cd /home/john/clarion && cargo nextest run -p clarion-core manifest --no-tests=pass +cd /home/john/loomweave && cargo nextest run -p loomweave-core manifest --no-tests=pass ``` Expected: 8 tests pass (`parses_valid_manifest`, `rejects_missing_name`, `rejects_zero_rss`, `rejects_empty_entity_kinds`, `rejects_rule_id_prefix_without_trailing_dash`, `rejects_reserved_entity_kind_file`, `rejects_plugin_name_uppercase`, `rejects_empty_extensions`). @@ -644,11 +644,11 @@ Expected: 8 tests pass (`parses_valid_manifest`, `rejects_missing_name`, `reject - [ ] **Step 11: Full ADR-023 gate sweep** ```bash -cd /home/john/clarion && cargo fmt --all -- --check -cd /home/john/clarion && cargo clippy --workspace --all-targets --all-features -- -D warnings -cd /home/john/clarion && cargo nextest run --workspace --all-features --no-tests=pass -cd /home/john/clarion && cargo doc --workspace --no-deps --all-features -cd /home/john/clarion && cargo deny check +cd /home/john/loomweave && cargo fmt --all -- --check +cd /home/john/loomweave && cargo clippy --workspace --all-targets --all-features -- -D warnings +cd /home/john/loomweave && cargo nextest run --workspace --all-features --no-tests=pass +cd /home/john/loomweave && cargo doc --workspace --no-deps --all-features +cd /home/john/loomweave && cargo deny check ``` All five must exit 0. Pedantic expectations: `doc_markdown` may flag bare `TOML` / `JSON-RPC` tokens — backtick them (`` `TOML` ``, `` `JSON-RPC` ``) in the doc comments above before re-running clippy. @@ -656,19 +656,19 @@ All five must exit 0. Pedantic expectations: `doc_markdown` may flag bare `TOML` - [ ] **Step 12: Commit** ```bash -cd /home/john/clarion && git add Cargo.toml crates/clarion-core/ && git commit -m "$(cat <<'EOF' +cd /home/john/loomweave && git add Cargo.toml crates/loomweave-core/ && git commit -m "$(cat <<'EOF' feat(wp2): L5 plugin.toml manifest parser and validator -Adds clarion-core::plugin::{manifest,mod} with parse_manifest(&[u8]) -> +Adds loomweave-core::plugin::{manifest,mod} with parse_manifest(&[u8]) -> Result. Validates against ADR-022: plugin name -grammar [a-z][a-z0-9_-]* with required `clarion-plugin-` prefix; rule-ID -prefix must be CLA-- and end with `-`; entity_kinds cannot shadow +grammar [a-z][a-z0-9_-]* with required `loomweave-plugin-` prefix; rule-ID +prefix must be LMWV-- and end with `-`; entity_kinds cannot shadow the core-reserved set (file, subsystem, guidance); required fields present; capabilities strictly positive. Workspace: toml = "0.8" added; tokio features extended with "process" + "io-util" (no runtime effect until Task 2). Fixtures live under -crates/clarion-core/tests/fixtures/ and are include_bytes!-loaded into the +crates/loomweave-core/tests/fixtures/ and are include_bytes!-loaded into the unit tests to keep the positive-path assertion a single source of truth. 8 tests: positive, 7 negatives covering every ManifestError variant. @@ -681,9 +681,9 @@ EOF ## Task 2: L4 JSON-RPC Content-Length transport **Files:** -- Create: `/home/john/clarion/crates/clarion-core/src/plugin/transport.rs` -- Create: `/home/john/clarion/crates/clarion-core/src/plugin/protocol.rs` -- Modify: `/home/john/clarion/crates/clarion-core/src/plugin/mod.rs` (add module decls + re-exports) +- Create: `/home/john/loomweave/crates/loomweave-core/src/plugin/transport.rs` +- Create: `/home/john/loomweave/crates/loomweave-core/src/plugin/protocol.rs` +- Modify: `/home/john/loomweave/crates/loomweave-core/src/plugin/mod.rs` (add module decls + re-exports) - [ ] **Step 1: Extend `plugin/mod.rs`** @@ -716,7 +716,7 @@ pub use transport::{Frame, TransportError, read_frame, write_frame}; - [ ] **Step 2: Write `protocol.rs`** -Create `/home/john/clarion/crates/clarion-core/src/plugin/protocol.rs`: +Create `/home/john/loomweave/crates/loomweave-core/src/plugin/protocol.rs`: ```rust //! Typed `JSON-RPC` 2.0 shapes for the L4 method set. @@ -731,7 +731,7 @@ Create `/home/john/clarion/crates/clarion-core/src/plugin/protocol.rs`: //! | `shutdown` | Core → plugin | Graceful stop | //! | `exit` | Core → plugin (note) | Forceful termination notification | //! -//! Error codes follow the JSON-RPC 2.0 spec plus Clarion-reserved +//! Error codes follow the JSON-RPC 2.0 spec plus Loomweave-reserved //! [-32000, -32099] range for transport/host-side violations. use serde::{Deserialize, Serialize}; @@ -791,7 +791,7 @@ pub struct JsonRpcError { pub data: Option, } -/// Error codes used by the Clarion host/plugin protocol. The standard +/// Error codes used by the Loomweave host/plugin protocol. The standard /// `JSON-RPC` 2.0 codes are included for completeness. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(i32)] @@ -801,9 +801,9 @@ pub enum JsonRpcErrorCode { MethodNotFound = -32_601, InvalidParams = -32_602, InternalError = -32_603, - /// Clarion-reserved: plugin refused the manifest handshake. + /// Loomweave-reserved: plugin refused the manifest handshake. ManifestRejected = -32_000, - /// Clarion-reserved: plugin reports an analysis-level error. + /// Loomweave-reserved: plugin reports an analysis-level error. AnalyzeFailed = -32_001, } @@ -870,7 +870,7 @@ pub struct AnalyzeFileResult { pub struct ShutdownParams {} /// Entity as emitted by the plugin. The host validates this shape before -/// translating it into [`clarion_storage::EntityRecord`]: +/// translating it into [`loomweave_storage::EntityRecord`]: /// /// - `id` must equal `entity_id(plugin_id, kind, qualified_name)` (ADR-003 /// + ADR-022; enforced by [`super::host`] per UQ-WP2-11). @@ -907,7 +907,7 @@ pub struct PluginSource { - [ ] **Step 3: Write `transport.rs` — tests first** -Create `/home/john/clarion/crates/clarion-core/src/plugin/transport.rs`: +Create `/home/john/loomweave/crates/loomweave-core/src/plugin/transport.rs`: ```rust //! Content-Length framed `JSON-RPC` codec (L4). @@ -961,7 +961,7 @@ pub enum TransportError { #[error( "frame size {observed} exceeds ADR-021 §2b ceiling {ceiling} \ - (rule-id CLA-INFRA-PLUGIN-FRAME-OVERSIZE)" + (rule-id LMWV-INFRA-PLUGIN-FRAME-OVERSIZE)" )] FrameTooLarge { observed: usize, ceiling: usize }, } @@ -1178,9 +1178,9 @@ mod tests { } ``` -- [ ] **Step 4: Add `tokio/macros` + `tokio/io-util` to `clarion-core`'s dev-dependencies** +- [ ] **Step 4: Add `tokio/macros` + `tokio/io-util` to `loomweave-core`'s dev-dependencies** -Modify `/home/john/clarion/crates/clarion-core/Cargo.toml` to ensure `tokio` (with `macros`, `io-util`, `rt`) is available under `[dev-dependencies]` for the `#[tokio::test]` attribute used above: +Modify `/home/john/loomweave/crates/loomweave-core/Cargo.toml` to ensure `tokio` (with `macros`, `io-util`, `rt`) is available under `[dev-dependencies]` for the `#[tokio::test]` attribute used above: ```toml [dev-dependencies] @@ -1192,7 +1192,7 @@ tokio = { workspace = true, features = ["macros", "io-util", "rt", "rt-multi-thr - [ ] **Step 5: Run the transport tests** ```bash -cd /home/john/clarion && cargo nextest run -p clarion-core transport --no-tests=pass +cd /home/john/loomweave && cargo nextest run -p loomweave-core transport --no-tests=pass ``` Expected: 7 tests pass. If any fail with `doc_markdown`-style clippy complaints during compile, backtick `LSP` / `JSON-RPC` / `Content-Length` / `UTF-8` tokens in the module-level doc comments. @@ -1200,17 +1200,17 @@ Expected: 7 tests pass. If any fail with `doc_markdown`-style clippy complaints - [ ] **Step 6: Full ADR-023 gate sweep** ```bash -cd /home/john/clarion && cargo fmt --all -- --check -cd /home/john/clarion && cargo clippy --workspace --all-targets --all-features -- -D warnings -cd /home/john/clarion && cargo nextest run --workspace --all-features --no-tests=pass -cd /home/john/clarion && cargo doc --workspace --no-deps --all-features -cd /home/john/clarion && cargo deny check +cd /home/john/loomweave && cargo fmt --all -- --check +cd /home/john/loomweave && cargo clippy --workspace --all-targets --all-features -- -D warnings +cd /home/john/loomweave && cargo nextest run --workspace --all-features --no-tests=pass +cd /home/john/loomweave && cargo doc --workspace --no-deps --all-features +cd /home/john/loomweave && cargo deny check ``` - [ ] **Step 7: Commit** ```bash -cd /home/john/clarion && git add crates/clarion-core/ && git commit -m "$(cat <<'EOF' +cd /home/john/loomweave && git add crates/loomweave-core/ && git commit -m "$(cat <<'EOF' feat(wp2): L4 JSON-RPC Content-Length transport + typed protocol shapes plugin/transport.rs implements read_frame/write_frame over any @@ -1222,7 +1222,7 @@ consuming the payload (ADR-021 §2b enforcement point). plugin/protocol.rs defines the JSON-RPC 2.0 envelope plus typed params and results for every L4 method: initialize, initialized, analyze_file, shutdown, exit. PluginEntity carries the on-wire shape the host -validates before translating to clarion_storage::EntityRecord in Task 6. +validates before translating to loomweave_storage::EntityRecord in Task 6. 7 transport tests: round-trip empty object, round-trip real JSON-RPC request, two frames back-to-back, ceiling refused before body, missing @@ -1237,8 +1237,8 @@ EOF ## Task 3: In-process mock plugin test harness **Files:** -- Create: `/home/john/clarion/crates/clarion-core/src/plugin/mock.rs` -- Modify: `/home/john/clarion/crates/clarion-core/src/plugin/mod.rs` (add `mock` module, re-export `MockPlugin` + variant enum under `#[cfg(any(test, feature = "mock"))]` — Sprint 1 uses plain `#[cfg(test)]`) +- Create: `/home/john/loomweave/crates/loomweave-core/src/plugin/mock.rs` +- Modify: `/home/john/loomweave/crates/loomweave-core/src/plugin/mod.rs` (add `mock` module, re-export `MockPlugin` + variant enum under `#[cfg(any(test, feature = "mock"))]` — Sprint 1 uses plain `#[cfg(test)]`) - [ ] **Step 1: Extend `plugin/mod.rs`** @@ -1268,7 +1268,7 @@ pub use transport::{Frame, TransportError, read_frame, write_frame}; - [ ] **Step 2: Write the mock module** -Create `/home/john/clarion/crates/clarion-core/src/plugin/mock.rs`: +Create `/home/john/loomweave/crates/loomweave-core/src/plugin/mock.rs`: ```rust //! In-process mock plugin for unit tests. @@ -1279,7 +1279,7 @@ Create `/home/john/clarion/crates/clarion-core/src/plugin/mock.rs`: //! handshake logic without a real subprocess. //! //! Task 6's integration tests use a real subprocess fixture -//! (`clarion-mock-plugin` crate). This module is for unit-level coverage. +//! (`loomweave-mock-plugin` crate). This module is for unit-level coverage. //! //! # UQ-WP2-07 note //! @@ -1490,7 +1490,7 @@ mod tests { Method::Initialize, &InitializeParams { protocol_version: "1.0".to_owned(), - plugin_name: "clarion-plugin-mock".to_owned(), + plugin_name: "loomweave-plugin-mock".to_owned(), plugin_version: "0.1.0".to_owned(), project_root: "/tmp".to_owned(), }, @@ -1547,7 +1547,7 @@ Note the minor type-level annoyance: we `use super::super::protocol::{Initialize - [ ] **Step 3: Run the mock test** ```bash -cd /home/john/clarion && cargo nextest run -p clarion-core mock --no-tests=pass +cd /home/john/loomweave && cargo nextest run -p loomweave-core mock --no-tests=pass ``` Expected: 1 test (`compliant_mock_completes_handshake`) passes. @@ -1555,13 +1555,13 @@ Expected: 1 test (`compliant_mock_completes_handshake`) passes. - [ ] **Step 4: Full ADR-023 gate sweep + flake-check** ```bash -cd /home/john/clarion && cargo fmt --all -- --check -cd /home/john/clarion && cargo clippy --workspace --all-targets --all-features -- -D warnings -cd /home/john/clarion && cargo nextest run --workspace --all-features --no-tests=pass -cd /home/john/clarion && cargo nextest run --workspace --all-features --no-tests=pass -cd /home/john/clarion && cargo nextest run --workspace --all-features --no-tests=pass -cd /home/john/clarion && cargo doc --workspace --no-deps --all-features -cd /home/john/clarion && cargo deny check +cd /home/john/loomweave && cargo fmt --all -- --check +cd /home/john/loomweave && cargo clippy --workspace --all-targets --all-features -- -D warnings +cd /home/john/loomweave && cargo nextest run --workspace --all-features --no-tests=pass +cd /home/john/loomweave && cargo nextest run --workspace --all-features --no-tests=pass +cd /home/john/loomweave && cargo nextest run --workspace --all-features --no-tests=pass +cd /home/john/loomweave && cargo doc --workspace --no-deps --all-features +cd /home/john/loomweave && cargo deny check ``` Three nextest runs in a row verify the async mock doesn't flake (timing-dependent tests are a WP2 hotspot). If any run hangs beyond 30s, kill it and investigate — the duplex-drop ordering is the usual suspect. @@ -1569,7 +1569,7 @@ Three nextest runs in a row verify the async mock doesn't flake (timing-dependen - [ ] **Step 5: Commit** ```bash -cd /home/john/clarion && git add crates/clarion-core/ && git commit -m "$(cat <<'EOF' +cd /home/john/loomweave && git add crates/loomweave-core/ && git commit -m "$(cat <<'EOF' feat(wp2): in-process mock plugin test harness plugin/mock.rs provides MockPlugin::spawn(variant) over tokio::io::duplex @@ -1578,7 +1578,7 @@ CrashingAfterHandshake (drops pipes post-handshake), Oversize (writes a 1024-byte frame so tests with ceiling=128 trip FrameTooLarge). Unit-level coverage: Task 6's real-subprocess integration tests use the -separate clarion-mock-plugin crate spawned via assert_cmd::cargo_bin. +separate loomweave-mock-plugin crate spawned via assert_cmd::cargo_bin. 1 test asserts the compliant variant's full handshake + analyze_file round trip. Module is #[cfg(test)] pub(crate) — no runtime impact on @@ -1592,15 +1592,15 @@ EOF ## Task 4: L6 core-enforced minimums — jail, limits, prlimit **Files:** -- Modify: `/home/john/clarion/Cargo.toml` (add `nix` workspace dep; relax `unsafe_code` from `"forbid"` to `"deny"`) -- Modify: `/home/john/clarion/crates/clarion-core/Cargo.toml` (add `nix` as a `[target.'cfg(target_os = "linux")'.dependencies]` entry; add `tracing`) -- Create: `/home/john/clarion/crates/clarion-core/src/plugin/jail.rs` -- Create: `/home/john/clarion/crates/clarion-core/src/plugin/limits.rs` -- Modify: `/home/john/clarion/crates/clarion-core/src/plugin/mod.rs` (declare `jail` + `limits`; add re-exports) +- Modify: `/home/john/loomweave/Cargo.toml` (add `nix` workspace dep; relax `unsafe_code` from `"forbid"` to `"deny"`) +- Modify: `/home/john/loomweave/crates/loomweave-core/Cargo.toml` (add `nix` as a `[target.'cfg(target_os = "linux")'.dependencies]` entry; add `tracing`) +- Create: `/home/john/loomweave/crates/loomweave-core/src/plugin/jail.rs` +- Create: `/home/john/loomweave/crates/loomweave-core/src/plugin/limits.rs` +- Modify: `/home/john/loomweave/crates/loomweave-core/src/plugin/mod.rs` (declare `jail` + `limits`; add re-exports) - [ ] **Step 1: Relax workspace `unsafe_code` lint** -In `/home/john/clarion/Cargo.toml`, change the `[workspace.lints.rust]` block from: +In `/home/john/loomweave/Cargo.toml`, change the `[workspace.lints.rust]` block from: ```toml [workspace.lints.rust] @@ -1614,14 +1614,14 @@ to: # ADR-021 §2d enforcement requires CommandExt::pre_exec (unsafe because the # closure runs in the fork'd child before exec). Relaxed from "forbid" to # "deny" so the single audited call site in -# crates/clarion-core/src/plugin/limits.rs can use `#[allow(unsafe_code)]` +# crates/loomweave-core/src/plugin/limits.rs can use `#[allow(unsafe_code)]` # with a safety-justifying comment. No other unsafe is permitted. unsafe_code = "deny" ``` - [ ] **Step 2: Add `nix` workspace dep** -Append to `[workspace.dependencies]` in `/home/john/clarion/Cargo.toml`: +Append to `[workspace.dependencies]` in `/home/john/loomweave/Cargo.toml`: ```toml nix = { version = "0.28", default-features = false, features = ["resource"] } @@ -1630,9 +1630,9 @@ tracing-test = "0.2" `tracing-test` is a dev-only helper that captures `tracing` events for assertion; we pin it at workspace level for reuse across crates. -- [ ] **Step 3: Extend `clarion-core/Cargo.toml`** +- [ ] **Step 3: Extend `loomweave-core/Cargo.toml`** -Modify `/home/john/clarion/crates/clarion-core/Cargo.toml` to add `tracing` + the target-scoped `nix`: +Modify `/home/john/loomweave/crates/loomweave-core/Cargo.toml` to add `tracing` + the target-scoped `nix`: ```toml [dependencies] @@ -1656,7 +1656,7 @@ tracing-test.workspace = true - [ ] **Step 4: Write `jail.rs` tests first** -Create `/home/john/clarion/crates/clarion-core/src/plugin/jail.rs`: +Create `/home/john/loomweave/crates/loomweave-core/src/plugin/jail.rs`: ```rust //! Path-jail helper — ADR-021 §2a enforcement primitive. @@ -1684,7 +1684,7 @@ pub enum JailError { #[error( "{candidate:?} canonicalises outside project root {root:?} \ - (rule-id CLA-INFRA-PLUGIN-PATH-ESCAPE)" + (rule-id LMWV-INFRA-PLUGIN-PATH-ESCAPE)" )] EscapedRoot { candidate: PathBuf, root: PathBuf }, } @@ -1784,7 +1784,7 @@ mod tests { - [ ] **Step 5: Write `limits.rs` tests first** -Create `/home/john/clarion/crates/clarion-core/src/plugin/limits.rs`: +Create `/home/john/loomweave/crates/loomweave-core/src/plugin/limits.rs`: ```rust //! Core-enforced ceilings + rolling breakers — ADR-021 §2b/§2c/§2d + @@ -1859,7 +1859,7 @@ pub const ENTITY_COUNT_CAP_FLOOR: u64 = 10_000; #[derive(Debug, Error)] #[error( "per-run entity-count cap exceeded: {observed} > {cap} \ - (rule-id CLA-INFRA-PLUGIN-ENTITY-CAP)" + (rule-id LMWV-INFRA-PLUGIN-ENTITY-CAP)" )] pub struct CapExceeded { pub observed: u64, @@ -2176,8 +2176,8 @@ pub use transport::{Frame, TransportError, read_frame, write_frame}; - [ ] **Step 7: Run the jail + limits tests** ```bash -cd /home/john/clarion && cargo nextest run -p clarion-core jail --no-tests=pass -cd /home/john/clarion && cargo nextest run -p clarion-core limits --no-tests=pass +cd /home/john/loomweave && cargo nextest run -p loomweave-core jail --no-tests=pass +cd /home/john/loomweave && cargo nextest run -p loomweave-core limits --no-tests=pass ``` Expected: 5 jail tests pass, 8 limits tests pass (one of which is Linux-gated). @@ -2185,13 +2185,13 @@ Expected: 5 jail tests pass, 8 limits tests pass (one of which is Linux-gated). - [ ] **Step 8: Full ADR-023 gate sweep + breaker flake-check** ```bash -cd /home/john/clarion && cargo fmt --all -- --check -cd /home/john/clarion && cargo clippy --workspace --all-targets --all-features -- -D warnings -cd /home/john/clarion && cargo nextest run --workspace --all-features --no-tests=pass -cd /home/john/clarion && cargo nextest run --workspace --all-features --no-tests=pass -cd /home/john/clarion && cargo nextest run --workspace --all-features --no-tests=pass -cd /home/john/clarion && cargo doc --workspace --no-deps --all-features -cd /home/john/clarion && cargo deny check +cd /home/john/loomweave && cargo fmt --all -- --check +cd /home/john/loomweave && cargo clippy --workspace --all-targets --all-features -- -D warnings +cd /home/john/loomweave && cargo nextest run --workspace --all-features --no-tests=pass +cd /home/john/loomweave && cargo nextest run --workspace --all-features --no-tests=pass +cd /home/john/loomweave && cargo nextest run --workspace --all-features --no-tests=pass +cd /home/john/loomweave && cargo doc --workspace --no-deps --all-features +cd /home/john/loomweave && cargo deny check ``` Three nextest runs because the breaker tests are timing-adjacent (they use synthetic `Instant`s — should be deterministic — but the triple run catches any accidental `Instant::now()` slippage). @@ -2201,7 +2201,7 @@ If `cargo deny check` complains about `nix`'s license or dependencies, add the s - [ ] **Step 9: Commit** ```bash -cd /home/john/clarion && git add Cargo.toml crates/clarion-core/ && git commit -m "$(cat <<'EOF' +cd /home/john/loomweave && git add Cargo.toml crates/loomweave-core/ && git commit -m "$(cat <<'EOF' feat(wp2): L6 core-enforced minimums — path jail, ceilings, prlimit (ADR-021 defaults) plugin/jail.rs — `jail(root, candidate) -> Result` @@ -2239,8 +2239,8 @@ EOF ## Task 5: L9 plugin discovery **Files:** -- Create: `/home/john/clarion/crates/clarion-core/src/plugin/discovery.rs` -- Modify: `/home/john/clarion/crates/clarion-core/src/plugin/mod.rs` (declare + re-export) +- Create: `/home/john/loomweave/crates/loomweave-core/src/plugin/discovery.rs` +- Modify: `/home/john/loomweave/crates/loomweave-core/src/plugin/mod.rs` (declare + re-export) - [ ] **Step 1: Extend `plugin/mod.rs`** @@ -2258,15 +2258,15 @@ pub use discovery::{DiscoveredPlugin, DiscoveryError, discover, discover_with_pa - [ ] **Step 2: Write `discovery.rs` with tests first** -Create `/home/john/clarion/crates/clarion-core/src/plugin/discovery.rs`: +Create `/home/john/loomweave/crates/loomweave-core/src/plugin/discovery.rs`: ```rust //! Plugin discovery (L9). //! //! Convention: the core scans `$PATH` for executable files whose basename -//! matches `clarion-plugin-*`. For each candidate binary, it looks for a +//! matches `loomweave-plugin-*`. For each candidate binary, it looks for a //! `plugin.toml` alongside it; if absent, it falls back to -//! `/../share/clarion/plugins//plugin.toml`. +//! `/../share/loomweave/plugins//plugin.toml`. //! //! UQ-WP2-01 proposal: PATH-based with neighboring-manifest fallback. //! Resolved here. @@ -2277,7 +2277,7 @@ Create `/home/john/clarion/crates/clarion-core/src/plugin/discovery.rs`: //! - First-match-wins on duplicate basenames (stable PATH order). //! - A candidate with no resolvable `plugin.toml` is silently skipped and //! logged at `tracing::debug!`. It is not an error — the user may have -//! a `clarion-plugin-*` binary on PATH that isn't a Clarion plugin. +//! a `loomweave-plugin-*` binary on PATH that isn't a Loomweave plugin. use std::path::{Path, PathBuf}; @@ -2326,7 +2326,7 @@ pub fn discover_with_path(path: &str) -> Vec { let Some(name_str) = file_name.to_str() else { continue; }; - if !name_str.starts_with("clarion-plugin-") { + if !name_str.starts_with("loomweave-plugin-") { continue; } if seen.iter().any(|s| s == name_str) { @@ -2348,7 +2348,7 @@ pub fn discover_with_path(path: &str) -> Vec { None => { tracing::debug!( executable = %exec_path.display(), - "clarion-plugin-* candidate has no resolvable plugin.toml; skipping" + "loomweave-plugin-* candidate has no resolvable plugin.toml; skipping" ); } } @@ -2375,11 +2375,11 @@ fn locate_manifest(exec: &Path, binary_name: &str) -> Option<(PathBuf, Manifest) if let Some(manifest) = read_and_parse(&candidate) { return Some((candidate, manifest)); } - // Fallback: /../share/clarion/plugins//plugin.toml + // Fallback: /../share/loomweave/plugins//plugin.toml let candidate = parent .join("..") .join("share") - .join("clarion") + .join("loomweave") .join("plugins") .join(binary_name) .join("plugin.toml"); @@ -2430,33 +2430,33 @@ mod tests { #[test] fn finds_plugin_with_neighboring_manifest() { let tmp = tempfile::tempdir().unwrap(); - let bin = mk_exec(tmp.path(), "clarion-plugin-python"); + let bin = mk_exec(tmp.path(), "loomweave-plugin-python"); fs::write(tmp.path().join("plugin.toml"), VALID).unwrap(); let path = tmp.path().to_string_lossy().into_owned(); let plugins = discover_with_path(&path); assert_eq!(plugins.len(), 1); assert_eq!(plugins[0].executable, bin); - assert_eq!(plugins[0].manifest.plugin.name, "clarion-plugin-python"); + assert_eq!(plugins[0].manifest.plugin.name, "loomweave-plugin-python"); } #[test] fn finds_plugin_via_share_fallback() { let tmp = tempfile::tempdir().unwrap(); // Simulated install layout: - // /bin/clarion-plugin-python - // /share/clarion/plugins/clarion-plugin-python/plugin.toml + // /bin/loomweave-plugin-python + // /share/loomweave/plugins/loomweave-plugin-python/plugin.toml let bin_dir = tmp.path().join("bin"); fs::create_dir(&bin_dir).unwrap(); let share_dir = tmp .path() .join("share") - .join("clarion") + .join("loomweave") .join("plugins") - .join("clarion-plugin-python"); + .join("loomweave-plugin-python"); fs::create_dir_all(&share_dir).unwrap(); fs::write(share_dir.join("plugin.toml"), VALID).unwrap(); - let bin = mk_exec(&bin_dir, "clarion-plugin-python"); + let bin = mk_exec(&bin_dir, "loomweave-plugin-python"); let path = bin_dir.to_string_lossy().into_owned(); let plugins = discover_with_path(&path); @@ -2465,7 +2465,7 @@ mod tests { } #[test] - fn skips_non_clarion_prefixed_binaries() { + fn skips_non_loomweave_prefixed_binaries() { let tmp = tempfile::tempdir().unwrap(); mk_exec(tmp.path(), "some-other-binary"); fs::write(tmp.path().join("plugin.toml"), VALID).unwrap(); @@ -2474,17 +2474,17 @@ mod tests { } #[test] - fn skips_clarion_candidate_without_manifest() { + fn skips_loomweave_candidate_without_manifest() { let tmp = tempfile::tempdir().unwrap(); - mk_exec(tmp.path(), "clarion-plugin-python"); + mk_exec(tmp.path(), "loomweave-plugin-python"); let path = tmp.path().to_string_lossy().into_owned(); assert!(discover_with_path(&path).is_empty()); } #[test] - fn skips_non_executable_clarion_file() { + fn skips_non_executable_loomweave_file() { let tmp = tempfile::tempdir().unwrap(); - let p = tmp.path().join("clarion-plugin-python"); + let p = tmp.path().join("loomweave-plugin-python"); fs::write(&p, b"not executable").unwrap(); let mut perm = fs::metadata(&p).unwrap().permissions(); perm.set_mode(0o644); @@ -2498,9 +2498,9 @@ mod tests { fn duplicate_basename_first_wins() { let tmp1 = tempfile::tempdir().unwrap(); let tmp2 = tempfile::tempdir().unwrap(); - mk_exec(tmp1.path(), "clarion-plugin-python"); + mk_exec(tmp1.path(), "loomweave-plugin-python"); fs::write(tmp1.path().join("plugin.toml"), VALID).unwrap(); - mk_exec(tmp2.path(), "clarion-plugin-python"); + mk_exec(tmp2.path(), "loomweave-plugin-python"); fs::write(tmp2.path().join("plugin.toml"), VALID).unwrap(); let path = format!( "{}:{}", @@ -2517,7 +2517,7 @@ mod tests { - [ ] **Step 3: Run discovery tests** ```bash -cd /home/john/clarion && cargo nextest run -p clarion-core discovery --no-tests=pass +cd /home/john/loomweave && cargo nextest run -p loomweave-core discovery --no-tests=pass ``` Expected: 6 tests pass. @@ -2525,27 +2525,27 @@ Expected: 6 tests pass. - [ ] **Step 4: Full ADR-023 gate sweep** ```bash -cd /home/john/clarion && cargo fmt --all -- --check -cd /home/john/clarion && cargo clippy --workspace --all-targets --all-features -- -D warnings -cd /home/john/clarion && cargo nextest run --workspace --all-features --no-tests=pass -cd /home/john/clarion && cargo doc --workspace --no-deps --all-features -cd /home/john/clarion && cargo deny check +cd /home/john/loomweave && cargo fmt --all -- --check +cd /home/john/loomweave && cargo clippy --workspace --all-targets --all-features -- -D warnings +cd /home/john/loomweave && cargo nextest run --workspace --all-features --no-tests=pass +cd /home/john/loomweave && cargo doc --workspace --no-deps --all-features +cd /home/john/loomweave && cargo deny check ``` - [ ] **Step 5: Commit** ```bash -cd /home/john/clarion && git add crates/clarion-core/ && git commit -m "$(cat <<'EOF' +cd /home/john/loomweave && git add crates/loomweave-core/ && git commit -m "$(cat <<'EOF' feat(wp2): L9 plugin discovery convention (PATH + neighboring manifest) plugin/discovery.rs scans $PATH for executable files prefixed -`clarion-plugin-`. For each, it looks for plugin.toml beside the binary -first, then falls back to /../share/clarion/plugins// +`loomweave-plugin-`. For each, it looks for plugin.toml beside the binary +first, then falls back to /../share/loomweave/plugins// plugin.toml. Non-matching names are skipped; candidates without a resolvable manifest are skipped with a debug trace; duplicate basenames across PATH entries keep the first-found wins. -6 tests: neighboring manifest, share-fallback manifest, non-clarion prefix +6 tests: neighboring manifest, share-fallback manifest, non-loomweave prefix skipped, missing-manifest skipped, non-executable file skipped, PATH-order deduplication. EOF @@ -2554,27 +2554,27 @@ EOF --- -## Task 6: Plugin-host supervisor + `clarion-mock-plugin` fixture +## Task 6: Plugin-host supervisor + `loomweave-mock-plugin` fixture **Files:** -- Create: `/home/john/clarion/crates/clarion-mock-plugin/` (new workspace crate) -- Create: `/home/john/clarion/crates/clarion-mock-plugin/Cargo.toml` -- Create: `/home/john/clarion/crates/clarion-mock-plugin/src/main.rs` -- Create: `/home/john/clarion/crates/clarion-mock-plugin/fixtures/plugin.toml` -- Modify: `/home/john/clarion/Cargo.toml` (add `clarion-mock-plugin` to `members`) -- Create: `/home/john/clarion/crates/clarion-core/src/plugin/host.rs` -- Modify: `/home/john/clarion/crates/clarion-core/src/plugin/mod.rs` (declare + re-export `host`) -- Create: `/home/john/clarion/crates/clarion-core/tests/host_integration.rs` +- Create: `/home/john/loomweave/crates/loomweave-mock-plugin/` (new workspace crate) +- Create: `/home/john/loomweave/crates/loomweave-mock-plugin/Cargo.toml` +- Create: `/home/john/loomweave/crates/loomweave-mock-plugin/src/main.rs` +- Create: `/home/john/loomweave/crates/loomweave-mock-plugin/fixtures/plugin.toml` +- Modify: `/home/john/loomweave/Cargo.toml` (add `loomweave-mock-plugin` to `members`) +- Create: `/home/john/loomweave/crates/loomweave-core/src/plugin/host.rs` +- Modify: `/home/john/loomweave/crates/loomweave-core/src/plugin/mod.rs` (declare + re-export `host`) +- Create: `/home/john/loomweave/crates/loomweave-core/tests/host_integration.rs` - [ ] **Step 1: Create the fixture crate's manifest** -Add `"crates/clarion-mock-plugin"` to the `members` array in the root `Cargo.toml`. +Add `"crates/loomweave-mock-plugin"` to the `members` array in the root `Cargo.toml`. -Create `/home/john/clarion/crates/clarion-mock-plugin/Cargo.toml`: +Create `/home/john/loomweave/crates/loomweave-mock-plugin/Cargo.toml`: ```toml [package] -name = "clarion-mock-plugin" +name = "loomweave-mock-plugin" version.workspace = true edition.workspace = true license.workspace = true @@ -2585,12 +2585,12 @@ rust-version.workspace = true workspace = true [[bin]] -name = "clarion-mock-plugin" +name = "loomweave-mock-plugin" path = "src/main.rs" [dependencies] anyhow.workspace = true -clarion-core = { path = "../clarion-core", version = "0.1.0-dev" } +loomweave-core = { path = "../loomweave-core", version = "0.1.0-dev" } serde_json.workspace = true tokio = { workspace = true, features = ["rt-multi-thread", "macros", "io-util", "io-std"] } ``` @@ -2601,11 +2601,11 @@ Note: we need tokio's `io-std` feature for `tokio::io::{stdin, stdout}`. The fea tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "time", "process", "io-util", "io-std"] } ``` -(Update the workspace `tokio` line in `/home/john/clarion/Cargo.toml` accordingly.) +(Update the workspace `tokio` line in `/home/john/loomweave/Cargo.toml` accordingly.) - [ ] **Step 2: Write the fixture plugin binary** -Create `/home/john/clarion/crates/clarion-mock-plugin/src/main.rs`: +Create `/home/john/loomweave/crates/loomweave-mock-plugin/src/main.rs`: ```rust //! Fixture binary for WP2 host integration tests. @@ -2613,23 +2613,23 @@ Create `/home/john/clarion/crates/clarion-mock-plugin/src/main.rs`: //! Behaviour is driven by a single CLI mode argument: //! //! ```text -//! clarion-mock-plugin compliant # handshake + 1 valid entity per analyze_file -//! clarion-mock-plugin undeclared-kind # emits an entity with kind="widget" -//! clarion-mock-plugin id-mismatch # emits an entity whose `id` != entity_id(...) -//! clarion-mock-plugin path-escape # emits an entity with file_path outside project_root -//! clarion-mock-plugin repeat-path-escape # emits n escape entities across one analyze_file -//! clarion-mock-plugin crash # crashes immediately after handshake +//! loomweave-mock-plugin compliant # handshake + 1 valid entity per analyze_file +//! loomweave-mock-plugin undeclared-kind # emits an entity with kind="widget" +//! loomweave-mock-plugin id-mismatch # emits an entity whose `id` != entity_id(...) +//! loomweave-mock-plugin path-escape # emits an entity with file_path outside project_root +//! loomweave-mock-plugin repeat-path-escape # emits n escape entities across one analyze_file +//! loomweave-mock-plugin crash # crashes immediately after handshake //! ``` //! //! Reads `JSON-RPC` frames from stdin, writes frames to stdout, logs //! free-form text to stderr (host forwards to `tracing::info!`). use anyhow::{Context, Result}; -use clarion_core::plugin::protocol::{ +use loomweave_core::plugin::protocol::{ AnalyzeFileParams, AnalyzeFileResult, InitializeParams, InitializeResult, JsonRpcRequest, JsonRpcResponse, Method, PluginEntity, PluginSource, }; -use clarion_core::plugin::transport::{read_frame, write_frame}; +use loomweave_core::plugin::transport::{read_frame, write_frame}; use tokio::io::{stdin, stdout}; const CEILING: usize = 8 * 1024 * 1024; @@ -2671,7 +2671,7 @@ async fn main() -> Result<()> { let mut stdin = stdin(); let mut stdout = stdout(); - eprintln!("clarion-mock-plugin: started in mode {mode:?}"); + eprintln!("loomweave-mock-plugin: started in mode {mode:?}"); // initialize let frame = read_frame(&mut stdin, CEILING).await?; @@ -2694,7 +2694,7 @@ async fn main() -> Result<()> { let _ = read_frame(&mut stdin, CEILING).await?; if matches!(mode, Mode::Crash) { - eprintln!("clarion-mock-plugin: crash mode — exit 1"); + eprintln!("loomweave-mock-plugin: crash mode — exit 1"); std::process::exit(1); } @@ -2788,14 +2788,14 @@ fn build_response(mode: Mode, path: &str) -> AnalyzeFileResult { - [ ] **Step 3: Create the fixture manifest** -Create `/home/john/clarion/crates/clarion-mock-plugin/fixtures/plugin.toml`: +Create `/home/john/loomweave/crates/loomweave-mock-plugin/fixtures/plugin.toml`: ```toml [plugin] -name = "clarion-plugin-mock" +name = "loomweave-plugin-mock" version = "0.1.0" protocol_version = "1.0" -executable = "clarion-mock-plugin" +executable = "loomweave-mock-plugin" language = "mock" extensions = ["mock"] @@ -2808,18 +2808,18 @@ max_entities_per_run = 100000 [ontology] entity_kinds = ["function"] edge_kinds = ["calls"] -rule_id_prefix = "CLA-MOCK-" +rule_id_prefix = "LMWV-MOCK-" ontology_version = "0.1.0" ``` - [ ] **Step 4: Write the host module with integration tests first (TDD)** -Create `/home/john/clarion/crates/clarion-core/tests/host_integration.rs`. Tests here are red until Step 5 lands the implementation. +Create `/home/john/loomweave/crates/loomweave-core/tests/host_integration.rs`. Tests here are red until Step 5 lands the implementation. ```rust //! WP2 Task 6 host-supervisor integration tests. //! -//! Uses the `clarion-mock-plugin` fixture binary spawned via cargo-bin. +//! Uses the `loomweave-mock-plugin` fixture binary spawned via cargo-bin. //! //! Asserts: //! - handshake + analyze_file round-trip + clean shutdown (happy path) @@ -2830,16 +2830,16 @@ Create `/home/john/clarion/crates/clarion-core/tests/host_integration.rs`. Tests use std::path::PathBuf; -use clarion_core::plugin::host::{HostError, PluginHost}; -use clarion_core::plugin::manifest::parse_manifest; +use loomweave_core::plugin::host::{HostError, PluginHost}; +use loomweave_core::plugin::manifest::parse_manifest; fn mock_plugin_binary() -> PathBuf { - PathBuf::from(env!("CARGO_BIN_EXE_clarion-mock-plugin")) + PathBuf::from(env!("CARGO_BIN_EXE_loomweave-mock-plugin")) } -fn mock_manifest() -> clarion_core::plugin::Manifest { +fn mock_manifest() -> loomweave_core::plugin::Manifest { let bytes = std::fs::read( - concat!(env!("CARGO_MANIFEST_DIR"), "/../clarion-mock-plugin/fixtures/plugin.toml"), + concat!(env!("CARGO_MANIFEST_DIR"), "/../loomweave-mock-plugin/fixtures/plugin.toml"), ) .expect("read fixture manifest"); parse_manifest(&bytes).expect("fixture manifest must parse") @@ -2932,7 +2932,7 @@ Note on fixture spawn args: `PluginHost::spawn` takes `Vec` extra args. - [ ] **Step 5: Write `plugin/host.rs`** -Create `/home/john/clarion/crates/clarion-core/src/plugin/host.rs`: +Create `/home/john/loomweave/crates/loomweave-core/src/plugin/host.rs`: ```rust //! Plugin-host supervisor (WP2 Task 6). @@ -2991,7 +2991,7 @@ pub enum HostError { #[error( "plugin path-escape sub-breaker tripped after {count} escapes — \ - plugin killed (rule-id CLA-INFRA-PLUGIN-DISABLED-PATH-ESCAPE)" + plugin killed (rule-id LMWV-INFRA-PLUGIN-DISABLED-PATH-ESCAPE)" )] PathEscapeBreakerTripped { count: usize }, @@ -3019,9 +3019,9 @@ const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5); /// A validated entity ready for persistence. `source_file_id` / /// `content_hash` carry through from the plugin; translating to -/// `clarion_storage::EntityRecord` is the caller's concern (Task 8) — +/// `loomweave_storage::EntityRecord` is the caller's concern (Task 8) — /// this struct deliberately mirrors the plugin shape so the host -/// doesn't need a direct dependency on `clarion-storage`. +/// doesn't need a direct dependency on `loomweave-storage`. #[derive(Debug, Clone)] pub struct ValidatedEntity { pub id: String, @@ -3199,7 +3199,7 @@ impl PluginHost { .any(|k| k == &e.kind) { tracing::warn!( - rule_id = "CLA-INFRA-PLUGIN-UNDECLARED-KIND", + rule_id = "LMWV-INFRA-PLUGIN-UNDECLARED-KIND", plugin = %self.manifest.plugin.name, kind = %e.kind, entity_id = %e.id, @@ -3209,7 +3209,7 @@ impl PluginHost { } // ADR-003 + UQ-WP2-11: id must match entity_id(plugin_id, kind, qualified_name). - // plugin_id narrows `clarion-plugin-` to `` with dashes + // plugin_id narrows `loomweave-plugin-` to `` with dashes // replaced by underscores — the grammar in ADR-022 forbids dashes in // plugin_id. let derived_plugin_id = derive_plugin_id(&self.manifest.plugin.name); @@ -3217,7 +3217,7 @@ impl PluginHost { Ok(id) => id, Err(err) => { tracing::warn!( - rule_id = "CLA-INFRA-PLUGIN-ENTITY-ID-MISMATCH", + rule_id = "LMWV-INFRA-PLUGIN-ENTITY-ID-MISMATCH", plugin = %self.manifest.plugin.name, entity_id = %e.id, error = %err, @@ -3228,7 +3228,7 @@ impl PluginHost { }; if expected_id.as_str() != e.id || e.plugin_id != derived_plugin_id { tracing::warn!( - rule_id = "CLA-INFRA-PLUGIN-ENTITY-ID-MISMATCH", + rule_id = "LMWV-INFRA-PLUGIN-ENTITY-ID-MISMATCH", plugin = %self.manifest.plugin.name, expected = %expected_id, observed = %e.id, @@ -3264,7 +3264,7 @@ impl PluginHost { } Err(JailError::EscapedRoot { candidate, .. }) => { tracing::warn!( - rule_id = "CLA-INFRA-PLUGIN-PATH-ESCAPE", + rule_id = "LMWV-INFRA-PLUGIN-PATH-ESCAPE", plugin = %self.manifest.plugin.name, offending_path = %candidate.display(), "dropping entity whose source path escapes project_root" @@ -3272,7 +3272,7 @@ impl PluginHost { if self.escape_breaker.record_escape() == BreakerState::Tripped { let count = self.escape_breaker.events_in_window(); tracing::warn!( - rule_id = "CLA-INFRA-PLUGIN-DISABLED-PATH-ESCAPE", + rule_id = "LMWV-INFRA-PLUGIN-DISABLED-PATH-ESCAPE", plugin = %self.manifest.plugin.name, count = count, "path-escape sub-breaker tripped; killing plugin" @@ -3312,11 +3312,11 @@ impl PluginHost { } /// Derive the ADR-022-compliant plugin_id from the manifest name. -/// `clarion-plugin-python` → `python`. Dashes are replaced with +/// `loomweave-plugin-python` → `python`. Dashes are replaced with /// underscores so the grammar `[a-z][a-z0-9_]*` is satisfied. fn derive_plugin_id(manifest_name: &str) -> String { manifest_name - .strip_prefix("clarion-plugin-") + .strip_prefix("loomweave-plugin-") .unwrap_or(manifest_name) .replace('-', "_") } @@ -3327,8 +3327,8 @@ mod tests { #[test] fn derive_plugin_id_strips_prefix() { - assert_eq!(derive_plugin_id("clarion-plugin-python"), "python"); - assert_eq!(derive_plugin_id("clarion-plugin-foo-bar"), "foo_bar"); + assert_eq!(derive_plugin_id("loomweave-plugin-python"), "python"); + assert_eq!(derive_plugin_id("loomweave-plugin-foo-bar"), "foo_bar"); } } @@ -3352,7 +3352,7 @@ pub use host::{HostError, PluginHost, ValidatedEntity}; - [ ] **Step 7: Run host integration tests** ```bash -cd /home/john/clarion && cargo nextest run -p clarion-core --test host_integration --no-tests=pass +cd /home/john/loomweave && cargo nextest run -p loomweave-core --test host_integration --no-tests=pass ``` Expected: 5 tests pass (`happy_path_handshake_and_analyze_file`, `undeclared_kind_entity_dropped`, `id_mismatch_entity_dropped`, `path_escape_drops_entity_plugin_stays_alive`, `eleven_escapes_trip_sub_breaker_and_kill`). @@ -3364,19 +3364,19 @@ If the last test flakes, it's usually one of: - [ ] **Step 8: Full ADR-023 gate sweep + flake-check x3** ```bash -cd /home/john/clarion && cargo fmt --all -- --check -cd /home/john/clarion && cargo clippy --workspace --all-targets --all-features -- -D warnings -cd /home/john/clarion && cargo nextest run --workspace --all-features --no-tests=pass -cd /home/john/clarion && cargo nextest run --workspace --all-features --no-tests=pass -cd /home/john/clarion && cargo nextest run --workspace --all-features --no-tests=pass -cd /home/john/clarion && cargo doc --workspace --no-deps --all-features -cd /home/john/clarion && cargo deny check +cd /home/john/loomweave && cargo fmt --all -- --check +cd /home/john/loomweave && cargo clippy --workspace --all-targets --all-features -- -D warnings +cd /home/john/loomweave && cargo nextest run --workspace --all-features --no-tests=pass +cd /home/john/loomweave && cargo nextest run --workspace --all-features --no-tests=pass +cd /home/john/loomweave && cargo nextest run --workspace --all-features --no-tests=pass +cd /home/john/loomweave && cargo doc --workspace --no-deps --all-features +cd /home/john/loomweave && cargo deny check ``` - [ ] **Step 9: Commit** ```bash -cd /home/john/clarion && git add Cargo.toml crates/ && git commit -m "$(cat <<'EOF' +cd /home/john/loomweave && git add Cargo.toml crates/ && git commit -m "$(cat <<'EOF' feat(wp2): plugin-host supervisor with ADR-021 enforcement + ADR-022 ontology plugin/host.rs — PluginHost::spawn(executable, args, manifest, project_root) @@ -3385,11 +3385,11 @@ exposes analyze_file + shutdown. Per-entity validation on each analyze_file response: * ADR-022: kind must be in manifest.ontology.entity_kinds (drop + log - CLA-INFRA-PLUGIN-UNDECLARED-KIND). + LMWV-INFRA-PLUGIN-UNDECLARED-KIND). * UQ-WP2-11: id must equal entity_id(plugin_id, kind, qualified_name) - (drop + log CLA-INFRA-PLUGIN-ENTITY-ID-MISMATCH). + (drop + log LMWV-INFRA-PLUGIN-ENTITY-ID-MISMATCH). * ADR-021 §2a: source.file_path must canonicalise inside project_root - (drop + log CLA-INFRA-PLUGIN-PATH-ESCAPE + tick sub-breaker; trip on + (drop + log LMWV-INFRA-PLUGIN-PATH-ESCAPE + tick sub-breaker; trip on 11th escape kills the plugin + returns HostError). * ADR-021 §2c: per-run entity-count cap consulted after each batch. @@ -3397,10 +3397,10 @@ stderr is forwarded line-by-line to tracing::info! target plugin::stderr (UQ-WP2-07 resolution). shutdown consumes self, sends shutdown+exit, waits with a 5s timeout, SIGKILLs on timeout. -crates/clarion-mock-plugin — new workspace fixture binary. Modes: compliant, +crates/loomweave-mock-plugin — new workspace fixture binary. Modes: compliant, undeclared-kind, id-mismatch, path-escape, repeat-path-escape , crash. Each writes JSON-RPC frames on stdout, reads from stdin, logs free-form to -stderr. Host integration tests drive it via CARGO_BIN_EXE_clarion-mock-plugin. +stderr. Host integration tests drive it via CARGO_BIN_EXE_loomweave-mock-plugin. 5 host integration tests: happy path, undeclared kind dropped, id mismatch dropped, single escape dropped + plugin alive, 11 escapes trip the @@ -3414,8 +3414,8 @@ EOF ## Task 7: Crash-loop breaker **Files:** -- Create: `/home/john/clarion/crates/clarion-core/src/plugin/breaker.rs` -- Modify: `/home/john/clarion/crates/clarion-core/src/plugin/mod.rs` (declare + re-export) +- Create: `/home/john/loomweave/crates/loomweave-core/src/plugin/breaker.rs` +- Modify: `/home/john/loomweave/crates/loomweave-core/src/plugin/mod.rs` (declare + re-export) - [ ] **Step 1: Extend `plugin/mod.rs`** @@ -3428,14 +3428,14 @@ pub use breaker::{CrashLoopBreaker, CrashLoopState, DEFAULT_CRASH_LIMIT, DEFAULT - [ ] **Step 2: Write `breaker.rs` tests first** -Create `/home/john/clarion/crates/clarion-core/src/plugin/breaker.rs`: +Create `/home/john/loomweave/crates/loomweave-core/src/plugin/breaker.rs`: ```rust //! Per-plugin crash-loop breaker (ADR-002 + ADR-021 Layer 3). //! //! Default: >3 crashes in 60s trips the breaker and disables the plugin //! for the run. Sprint 1 hard-codes these values (UQ-WP2-10); config -//! surface lives in `clarion.yaml:plugin_limits.*` from WP6 onwards. +//! surface lives in `loomweave.yaml:plugin_limits.*` from WP6 onwards. //! //! The breaker's only job is to answer "can I spawn this plugin right //! now?" and "record that this plugin just crashed". Actual spawn, @@ -3564,19 +3564,19 @@ mod tests { - [ ] **Step 3: Run breaker tests; expect pass** ```bash -cd /home/john/clarion && cargo nextest run -p clarion-core breaker --no-tests=pass +cd /home/john/loomweave && cargo nextest run -p loomweave-core breaker --no-tests=pass ``` Expected: 3 tests pass. - [ ] **Step 4: Add an integration test using the crashing mock fixture** -Append to `/home/john/clarion/crates/clarion-core/tests/host_integration.rs`: +Append to `/home/john/loomweave/crates/loomweave-core/tests/host_integration.rs`: ```rust #[tokio::test] async fn crashing_plugin_trips_breaker_across_spawns() { - use clarion_core::plugin::breaker::{CrashLoopBreaker, CrashLoopState}; + use loomweave_core::plugin::breaker::{CrashLoopBreaker, CrashLoopState}; use std::time::Duration; let tmp = tempfile::tempdir().unwrap(); @@ -3603,9 +3603,9 @@ Note: the `crash` mode in the fixture exits 1 immediately after handshake, so `s - [ ] **Step 5: Run the updated integration test** ```bash -cd /home/john/clarion && cargo nextest run -p clarion-core --test host_integration --no-tests=pass -cd /home/john/clarion && cargo nextest run -p clarion-core --test host_integration --no-tests=pass -cd /home/john/clarion && cargo nextest run -p clarion-core --test host_integration --no-tests=pass +cd /home/john/loomweave && cargo nextest run -p loomweave-core --test host_integration --no-tests=pass +cd /home/john/loomweave && cargo nextest run -p loomweave-core --test host_integration --no-tests=pass +cd /home/john/loomweave && cargo nextest run -p loomweave-core --test host_integration --no-tests=pass ``` Three runs for timing flake check. All 6 integration tests should pass each run. @@ -3613,17 +3613,17 @@ Three runs for timing flake check. All 6 integration tests should pass each run. - [ ] **Step 6: Full ADR-023 gate sweep** ```bash -cd /home/john/clarion && cargo fmt --all -- --check -cd /home/john/clarion && cargo clippy --workspace --all-targets --all-features -- -D warnings -cd /home/john/clarion && cargo nextest run --workspace --all-features --no-tests=pass -cd /home/john/clarion && cargo doc --workspace --no-deps --all-features -cd /home/john/clarion && cargo deny check +cd /home/john/loomweave && cargo fmt --all -- --check +cd /home/john/loomweave && cargo clippy --workspace --all-targets --all-features -- -D warnings +cd /home/john/loomweave && cargo nextest run --workspace --all-features --no-tests=pass +cd /home/john/loomweave && cargo doc --workspace --no-deps --all-features +cd /home/john/loomweave && cargo deny check ``` - [ ] **Step 7: Commit** ```bash -cd /home/john/clarion && git add crates/clarion-core/ && git commit -m "$(cat <<'EOF' +cd /home/john/loomweave && git add crates/loomweave-core/ && git commit -m "$(cat <<'EOF' feat(wp2): crash-loop breaker plugin/breaker.rs — CrashLoopBreaker with ADR-002 / ADR-021 Layer 3 @@ -3643,13 +3643,13 @@ EOF --- -## Task 8: Wire `clarion analyze` to use the plugin host +## Task 8: Wire `loomweave analyze` to use the plugin host **Files:** -- Modify: `/home/john/clarion/crates/clarion-cli/Cargo.toml` (add `clarion-core` plugin surface, `walkdir`) -- Modify: `/home/john/clarion/crates/clarion-cli/src/analyze.rs` (replace Sprint-1 stub) -- Modify: `/home/john/clarion/Cargo.toml` (add `walkdir` workspace dep) -- Create: `/home/john/clarion/crates/clarion-cli/tests/analyze_with_plugin.rs` +- Modify: `/home/john/loomweave/crates/loomweave-cli/Cargo.toml` (add `loomweave-core` plugin surface, `walkdir`) +- Modify: `/home/john/loomweave/crates/loomweave-cli/src/analyze.rs` (replace Sprint-1 stub) +- Modify: `/home/john/loomweave/Cargo.toml` (add `walkdir` workspace dep) +- Create: `/home/john/loomweave/crates/loomweave-cli/tests/analyze_with_plugin.rs` - [ ] **Step 1: Add `walkdir` workspace dep** @@ -3659,7 +3659,7 @@ Append to `[workspace.dependencies]`: walkdir = "2" ``` -- [ ] **Step 2: Extend `clarion-cli/Cargo.toml`** +- [ ] **Step 2: Extend `loomweave-cli/Cargo.toml`** Add to `[dependencies]`: @@ -3667,16 +3667,16 @@ Add to `[dependencies]`: walkdir.workspace = true ``` -Nothing else needs adding — `clarion-core` is already a path dependency and the plugin module is exposed at the crate root through the re-exports in `plugin/mod.rs`. +Nothing else needs adding — `loomweave-core` is already a path dependency and the plugin module is exposed at the crate root through the re-exports in `plugin/mod.rs`. - [ ] **Step 3: Rewrite `analyze.rs` to use the plugin host** -Replace `/home/john/clarion/crates/clarion-cli/src/analyze.rs` (keep the helpers `iso8601_now` + `civil_from_unix_secs` from Sprint 1 — they're still needed for timestamps): +Replace `/home/john/loomweave/crates/loomweave-cli/src/analyze.rs` (keep the helpers `iso8601_now` + `civil_from_unix_secs` from Sprint 1 — they're still needed for timestamps): ```rust -//! `clarion analyze` — WP2-wired walking skeleton. +//! `loomweave analyze` — WP2-wired walking skeleton. //! -//! Discovers plugins via [`clarion_core::plugin::discover`], spawns each, +//! Discovers plugins via [`loomweave_core::plugin::discover`], spawns each, //! walks the project tree, calls `analyze_file` per matching file, and //! persists returned entities through the WP1 writer-actor. @@ -3686,9 +3686,9 @@ use anyhow::{Context, Result, bail}; use uuid::Uuid; use walkdir::WalkDir; -use clarion_core::plugin::host::{PluginHost, ValidatedEntity}; -use clarion_core::plugin::{discovery, manifest::Manifest}; -use clarion_storage::{ +use loomweave_core::plugin::host::{PluginHost, ValidatedEntity}; +use loomweave_core::plugin::{discovery, manifest::Manifest}; +use loomweave_storage::{ DEFAULT_BATCH_SIZE, DEFAULT_CHANNEL_CAPACITY, EntityRecord, RunStatus, Writer, commands::WriterCmd, }; @@ -3698,7 +3698,7 @@ use clarion_storage::{ /// # Errors /// /// Returns an error if the target directory does not exist, has no -/// `.clarion/` directory, or if any subsystem (discovery, spawn, +/// `.loomweave/` directory, or if any subsystem (discovery, spawn, /// writer-actor) fails fatally. pub async fn run(project_path: PathBuf) -> Result<()> { if !project_path.exists() { @@ -3710,14 +3710,14 @@ pub async fn run(project_path: PathBuf) -> Result<()> { let project_root = project_path .canonicalize() .with_context(|| format!("cannot canonicalise path {}", project_path.display()))?; - let clarion_dir = project_root.join(".clarion"); - if !clarion_dir.exists() { + let loomweave_dir = project_root.join(".loomweave"); + if !loomweave_dir.exists() { bail!( - "{} has no .clarion/ directory. Run `clarion install` first.", + "{} has no .loomweave/ directory. Run `loomweave install` first.", project_root.display() ); } - let db_path = clarion_dir.join("clarion.db"); + let db_path = loomweave_dir.join("loomweave.db"); let plugins = discovery::discover().context("plugin discovery")?; @@ -3861,8 +3861,8 @@ fn walk_files(project_root: &Path, manifest: &Manifest) -> Vec { None } }) - // Skip anything inside the .clarion/ state directory. - .filter(|p| !p.components().any(|c| c.as_os_str() == ".clarion")) + // Skip anything inside the .loomweave/ state directory. + .filter(|p| !p.components().any(|c| c.as_os_str() == ".loomweave")) .collect() } @@ -3935,14 +3935,14 @@ fn civil_from_unix_secs(mut secs: u64) -> (u32, u32, u32, u32, u32, u32) { - [ ] **Step 4: Write the plugin-wired integration test** -Create `/home/john/clarion/crates/clarion-cli/tests/analyze_with_plugin.rs`: +Create `/home/john/loomweave/crates/loomweave-cli/tests/analyze_with_plugin.rs`: ```rust -//! `clarion analyze` with a mock plugin on PATH produces persisted entities. +//! `loomweave analyze` with a mock plugin on PATH produces persisted entities. //! -//! Assembles a temporary `$PATH` containing the `clarion-mock-plugin` -//! fixture binary and a neighboring `plugin.toml`. Runs `clarion install` -//! then `clarion analyze` and asserts the DB row shape. +//! Assembles a temporary `$PATH` containing the `loomweave-mock-plugin` +//! fixture binary and a neighboring `plugin.toml`. Runs `loomweave install` +//! then `loomweave analyze` and asserts the DB row shape. use std::fs; use std::path::PathBuf; @@ -3950,18 +3950,18 @@ use std::path::PathBuf; use assert_cmd::Command; use rusqlite::Connection; -fn clarion_bin() -> Command { - Command::cargo_bin("clarion").expect("clarion binary") +fn loomweave_bin() -> Command { + Command::cargo_bin("loomweave").expect("loomweave binary") } fn mock_plugin_bin() -> PathBuf { - PathBuf::from(env!("CARGO_BIN_EXE_clarion-mock-plugin")) + PathBuf::from(env!("CARGO_BIN_EXE_loomweave-mock-plugin")) } fn mock_manifest_bytes() -> Vec { let fixture = concat!( env!("CARGO_MANIFEST_DIR"), - "/../clarion-mock-plugin/fixtures/plugin.toml" + "/../loomweave-mock-plugin/fixtures/plugin.toml" ); std::fs::read(fixture).expect("read mock manifest fixture") } @@ -3972,7 +3972,7 @@ fn analyze_with_mock_plugin_persists_entities() { let bin_dir = tempfile::tempdir().unwrap(); // Symlink the real mock-plugin binary into bin_dir under the expected name. - let symlinked = bin_dir.path().join("clarion-plugin-mock"); + let symlinked = bin_dir.path().join("loomweave-plugin-mock"); #[cfg(unix)] std::os::unix::fs::symlink(mock_plugin_bin(), &symlinked).unwrap(); #[cfg(not(unix))] @@ -3984,15 +3984,15 @@ fn analyze_with_mock_plugin_persists_entities() { // One `.mock` file for the plugin to pick up. fs::write(project_dir.path().join("demo.mock"), b"sample\n").unwrap(); - // clarion install - clarion_bin() + // loomweave install + loomweave_bin() .args(["install", "--path"]) .arg(project_dir.path()) .assert() .success(); - // clarion analyze with our custom PATH - clarion_bin() + // loomweave analyze with our custom PATH + loomweave_bin() .env("PATH", bin_dir.path()) .args(["analyze"]) .arg(project_dir.path()) @@ -4000,7 +4000,7 @@ fn analyze_with_mock_plugin_persists_entities() { .success(); // Assert the DB row shape. - let db = project_dir.path().join(".clarion").join("clarion.db"); + let db = project_dir.path().join(".loomweave").join("loomweave.db"); let conn = Connection::open(&db).unwrap(); let (count, status): (i64, String) = conn .query_row( @@ -4027,14 +4027,14 @@ fn analyze_with_mock_plugin_persists_entities() { } ``` -**Integration-test machinery caveat:** `Command::cargo_bin("clarion")` requires the `clarion` binary to exist in a known location. `CARGO_BIN_EXE_clarion-mock-plugin` is set automatically because `clarion-cli` depends on `clarion-mock-plugin` via its dev-dependencies only if declared. Add the fixture as a dev-dependency of `clarion-cli` so the env var is populated: +**Integration-test machinery caveat:** `Command::cargo_bin("loomweave")` requires the `loomweave` binary to exist in a known location. `CARGO_BIN_EXE_loomweave-mock-plugin` is set automatically because `loomweave-cli` depends on `loomweave-mock-plugin` via its dev-dependencies only if declared. Add the fixture as a dev-dependency of `loomweave-cli` so the env var is populated: -Modify `/home/john/clarion/crates/clarion-cli/Cargo.toml` `[dev-dependencies]`: +Modify `/home/john/loomweave/crates/loomweave-cli/Cargo.toml` `[dev-dependencies]`: ```toml [dev-dependencies] assert_cmd.workspace = true -clarion-mock-plugin = { path = "../clarion-mock-plugin", version = "0.1.0-dev" } +loomweave-mock-plugin = { path = "../loomweave-mock-plugin", version = "0.1.0-dev" } rusqlite.workspace = true serde_json.workspace = true tempfile.workspace = true @@ -4043,31 +4043,31 @@ tempfile.workspace = true - [ ] **Step 5: Run the analyze tests** ```bash -cd /home/john/clarion && cargo nextest run -p clarion-cli --no-tests=pass +cd /home/john/loomweave && cargo nextest run -p loomweave-cli --no-tests=pass ``` -Expected: all `clarion-cli` tests pass, including the new `analyze_with_mock_plugin_persists_entities` (plus WP1's pre-existing install + analyze tests). +Expected: all `loomweave-cli` tests pass, including the new `analyze_with_mock_plugin_persists_entities` (plus WP1's pre-existing install + analyze tests). - [ ] **Step 6: Full ADR-023 gate sweep** ```bash -cd /home/john/clarion && cargo fmt --all -- --check -cd /home/john/clarion && cargo clippy --workspace --all-targets --all-features -- -D warnings -cd /home/john/clarion && cargo nextest run --workspace --all-features --no-tests=pass -cd /home/john/clarion && cargo nextest run --workspace --all-features --no-tests=pass -cd /home/john/clarion && cargo nextest run --workspace --all-features --no-tests=pass -cd /home/john/clarion && cargo doc --workspace --no-deps --all-features -cd /home/john/clarion && cargo deny check +cd /home/john/loomweave && cargo fmt --all -- --check +cd /home/john/loomweave && cargo clippy --workspace --all-targets --all-features -- -D warnings +cd /home/john/loomweave && cargo nextest run --workspace --all-features --no-tests=pass +cd /home/john/loomweave && cargo nextest run --workspace --all-features --no-tests=pass +cd /home/john/loomweave && cargo nextest run --workspace --all-features --no-tests=pass +cd /home/john/loomweave && cargo doc --workspace --no-deps --all-features +cd /home/john/loomweave && cargo deny check ``` - [ ] **Step 7: Commit** ```bash -cd /home/john/clarion && git add Cargo.toml crates/ && git commit -m "$(cat <<'EOF' -feat(wp2): wire clarion analyze to plugin host +cd /home/john/loomweave && git add Cargo.toml crates/ && git commit -m "$(cat <<'EOF' +feat(wp2): wire loomweave analyze to plugin host analyze.rs — replaces the Sprint-1 skipped_no_plugins stub. Discovers -plugins via clarion_core::plugin::discover, spawns each PluginHost, walks +plugins via loomweave_core::plugin::discover, spawns each PluginHost, walks the project tree filtering by the manifest's [plugin].extensions, calls analyze_file per file, and persists each ValidatedEntity via the WP1 writer-actor InsertEntity path. Per-plugin shutdown is clean (shutdown + @@ -4079,12 +4079,12 @@ Run status wiring: * Failed when any plugin errored fatally (the other plugins still run; this matches "partial-results" framing from ADR-021 §2c). -walkdir = "2" added as a workspace dep. clarion-mock-plugin added as a -dev-dependency of clarion-cli so integration tests get the fixture's +walkdir = "2" added as a workspace dep. loomweave-mock-plugin added as a +dev-dependency of loomweave-cli so integration tests get the fixture's CARGO_BIN_EXE_ env var. 1 new integration test assembles a temp PATH dir with a symlinked -clarion-mock-plugin + neighboring plugin.toml, runs clarion install + +loomweave-mock-plugin + neighboring plugin.toml, runs loomweave install + analyze, and asserts status=completed + entities >= 1 + id prefix "mock:function:". EOF @@ -4096,11 +4096,11 @@ EOF ## Task 9: WP2 end-to-end smoke test **Files:** -- Create: `/home/john/clarion/crates/clarion-cli/tests/wp2_e2e.rs` +- Create: `/home/john/loomweave/crates/loomweave-cli/tests/wp2_e2e.rs` - [ ] **Step 1: Write the E2E smoke test** -Create `/home/john/clarion/crates/clarion-cli/tests/wp2_e2e.rs`: +Create `/home/john/loomweave/crates/loomweave-cli/tests/wp2_e2e.rs`: ```rust //! WP2 end-to-end smoke test — mirrors the README §3 demo script at WP2 @@ -4112,18 +4112,18 @@ use std::path::PathBuf; use assert_cmd::Command; use rusqlite::Connection; -fn clarion_bin() -> Command { - Command::cargo_bin("clarion").expect("clarion binary") +fn loomweave_bin() -> Command { + Command::cargo_bin("loomweave").expect("loomweave binary") } fn mock_plugin_bin() -> PathBuf { - PathBuf::from(env!("CARGO_BIN_EXE_clarion-mock-plugin")) + PathBuf::from(env!("CARGO_BIN_EXE_loomweave-mock-plugin")) } fn mock_manifest_bytes() -> Vec { std::fs::read(concat!( env!("CARGO_MANIFEST_DIR"), - "/../clarion-mock-plugin/fixtures/plugin.toml" + "/../loomweave-mock-plugin/fixtures/plugin.toml" )) .expect("read mock manifest") } @@ -4143,33 +4143,33 @@ fn wp2_walking_skeleton_end_to_end() { #[cfg(unix)] std::os::unix::fs::symlink( mock_plugin_bin(), - path_dir.path().join("clarion-plugin-mock"), + path_dir.path().join("loomweave-plugin-mock"), ) .unwrap(); fs::write(path_dir.path().join("plugin.toml"), mock_manifest_bytes()).unwrap(); - // clarion install - clarion_bin() + // loomweave install + loomweave_bin() .args(["install", "--path"]) .arg(project.path()) .assert() .success(); - let clarion_dir = project.path().join(".clarion"); - assert!(clarion_dir.join("clarion.db").exists()); - assert!(clarion_dir.join("config.json").exists()); - assert!(clarion_dir.join(".gitignore").exists()); - assert!(project.path().join("clarion.yaml").exists()); + let loomweave_dir = project.path().join(".loomweave"); + assert!(loomweave_dir.join("loomweave.db").exists()); + assert!(loomweave_dir.join("config.json").exists()); + assert!(loomweave_dir.join(".gitignore").exists()); + assert!(project.path().join("loomweave.yaml").exists()); - // clarion analyze with the mock plugin on PATH - clarion_bin() + // loomweave analyze with the mock plugin on PATH + loomweave_bin() .env("PATH", path_dir.path()) .args(["analyze"]) .arg(project.path()) .assert() .success(); - let conn = Connection::open(clarion_dir.join("clarion.db")).unwrap(); + let conn = Connection::open(loomweave_dir.join("loomweave.db")).unwrap(); let migration_version: i64 = conn .query_row("SELECT MAX(version) FROM schema_migrations", [], |row| { @@ -4211,15 +4211,15 @@ fn wp2_walking_skeleton_end_to_end() { - [ ] **Step 2: Run the E2E test three times (flake check)** ```bash -cd /home/john/clarion && cargo nextest run -p clarion-cli --test wp2_e2e --no-tests=pass -cd /home/john/clarion && cargo nextest run -p clarion-cli --test wp2_e2e --no-tests=pass -cd /home/john/clarion && cargo nextest run -p clarion-cli --test wp2_e2e --no-tests=pass +cd /home/john/loomweave && cargo nextest run -p loomweave-cli --test wp2_e2e --no-tests=pass +cd /home/john/loomweave && cargo nextest run -p loomweave-cli --test wp2_e2e --no-tests=pass +cd /home/john/loomweave && cargo nextest run -p loomweave-cli --test wp2_e2e --no-tests=pass ``` - [ ] **Step 3: Release-profile build** ```bash -cd /home/john/clarion && cargo build --workspace --release +cd /home/john/loomweave && cargo build --workspace --release ``` Any warning-as-error surfacing here must be fixed in-code, not by loosening lints. @@ -4227,13 +4227,13 @@ Any warning-as-error surfacing here must be fixed in-code, not by loosening lint - [ ] **Step 4: Final ADR-023 gate sweep (all 7 gates)** ```bash -cd /home/john/clarion && cargo fmt --all -- --check -cd /home/john/clarion && cargo clippy --workspace --all-targets --all-features -- -D warnings -cd /home/john/clarion && cargo nextest run --workspace --all-features --no-tests=pass -cd /home/john/clarion && cargo doc --workspace --no-deps --all-features -cd /home/john/clarion && cargo deny check -cd /home/john/clarion && cargo build --workspace -cd /home/john/clarion && cargo build --workspace --release +cd /home/john/loomweave && cargo fmt --all -- --check +cd /home/john/loomweave && cargo clippy --workspace --all-targets --all-features -- -D warnings +cd /home/john/loomweave && cargo nextest run --workspace --all-features --no-tests=pass +cd /home/john/loomweave && cargo doc --workspace --no-deps --all-features +cd /home/john/loomweave && cargo deny check +cd /home/john/loomweave && cargo build --workspace +cd /home/john/loomweave && cargo build --workspace --release ``` All 7 exit 0. This is the WP2 closing-commit gate; CI runs the same set. @@ -4241,12 +4241,12 @@ All 7 exit 0. This is the WP2 closing-commit gate; CI runs the same set. - [ ] **Step 5: Commit** ```bash -cd /home/john/clarion && git add crates/clarion-cli/ && git commit -m "$(cat <<'EOF' +cd /home/john/loomweave && git add crates/loomweave-cli/ && git commit -m "$(cat <<'EOF' test(wp2): end-to-end smoke with mock plugin -wp2_e2e.rs runs the README §3 demo script at WP2 scope: clarion install -+ clarion analyze against a project containing 2 .mock files, with the -clarion-plugin-mock fixture on a synthetic PATH + neighboring plugin.toml. +wp2_e2e.rs runs the README §3 demo script at WP2 scope: loomweave install ++ loomweave analyze against a project containing 2 .mock files, with the +loomweave-plugin-mock fixture on a synthetic PATH + neighboring plugin.toml. Asserts runs.status = 'completed', entity_count = 2, all IDs are the 3-segment form `mock:function:` per L2. @@ -4261,9 +4261,9 @@ EOF ## Task 10: Sign-off ladder + lock-in stamps + UQ resolutions **Files:** -- Modify: `/home/john/clarion/docs/implementation/sprint-1/signoffs.md` -- Modify: `/home/john/clarion/docs/implementation/sprint-1/README.md` -- Modify: `/home/john/clarion/docs/implementation/sprint-1/wp2-plugin-host.md` +- Modify: `/home/john/loomweave/docs/implementation/sprint-1/signoffs.md` +- Modify: `/home/john/loomweave/docs/implementation/sprint-1/README.md` +- Modify: `/home/john/loomweave/docs/implementation/sprint-1/wp2-plugin-host.md` - [ ] **Step 1: Tick Tier A.2 boxes in `signoffs.md`** @@ -4273,7 +4273,7 @@ Do NOT tick A.3 / A.4 / A.5 / A.6 — those belong to WP3 / sprint-close. - [ ] **Step 2: Stamp lock-in dates in `README.md` §4** -In `/home/john/clarion/docs/implementation/sprint-1/README.md` §4 "Lock-in summary", annotate L4, L5, L6, L9 with the same `locked on ` stamp used in signoffs.md. +In `/home/john/loomweave/docs/implementation/sprint-1/README.md` §4 "Lock-in summary", annotate L4, L5, L6, L9 with the same `locked on ` stamp used in signoffs.md. - [ ] **Step 3: Mark UQ-WP2-* resolved in `wp2-plugin-host.md §5`** @@ -4289,12 +4289,12 @@ For each UQ-WP2-01 through UQ-WP2-11, append a `**Resolved**: ` - UQ-WP2-08 — resolved in Task 3 (docs): plugin-author discipline; documented in the mock plugin's module rustdoc and inherited by WP3's plugin-author guide. - UQ-WP2-09 — resolved in Task 6: manifest is re-parsed on every `PluginHost::spawn`. Caching is a `serve` concern (WP8). - UQ-WP2-10 — resolved in Task 7: >3 crashes/60s crash-loop breaker + >10 escapes/60s path-escape sub-breaker hard-coded; config surface deferred to WP6. -- UQ-WP2-11 — resolved in Task 6: host reconstructs `entity_id(derived_plugin_id, kind, qualified_name)` and compares against the returned `id`; mismatch drops the entity, logs `CLA-INFRA-PLUGIN-ENTITY-ID-MISMATCH`, plugin stays alive. +- UQ-WP2-11 — resolved in Task 6: host reconstructs `entity_id(derived_plugin_id, kind, qualified_name)` and compares against the returned `id`; mismatch drops the entity, logs `LMWV-INFRA-PLUGIN-ENTITY-ID-MISMATCH`, plugin stays alive. - [ ] **Step 4: Commit** ```bash -cd /home/john/clarion && git add docs/implementation/sprint-1/ && git commit -m "$(cat <<'EOF' +cd /home/john/loomweave && git add docs/implementation/sprint-1/ && git commit -m "$(cat <<'EOF' docs(sprint-1): tick WP2 sign-off and stamp L4/L5/L6/L9 lock-ins Tier A.2 boxes ticked in signoffs.md with the WP2 closing-commit date. @@ -4321,7 +4321,7 @@ EOF | §6.Task 5 Plugin discovery (L9) | Task 5 | ✓ | | §6.Task 6 Plugin-host supervisor | Task 6 | ✓ | | §6.Task 7 Crash-loop breaker | Task 7 | ✓ | -| §6.Task 8 Wire `clarion analyze` | Task 8 | ✓ | +| §6.Task 8 Wire `loomweave analyze` | Task 8 | ✓ | | §6.Task 9 E2E smoke | Task 9 | ✓ | | §8 Exit criteria sign-off | Task 10 | ✓ | @@ -4333,7 +4333,7 @@ Every lock-in (L4/L5/L6/L9) has a dedicated Task that lands it. Every UQ-WP2-* h - `ValidatedEntity` shape identical between Task 6 emission and Task 8 `EntityRecord` translation (Task 8's `persist_entity` maps field-for-field). - `ContentLengthCeiling::default()` = 8 MiB; `read_frame(reader, ceiling.bytes())` called with the struct's `bytes()` accessor — no `usize` literal drift. - `EntityCountCap::new(DEFAULT_ENTITY_COUNT_CAP)` uses the same const in Task 4 and Task 6. -- `entity_id()` (WP1 Task 2) and Task 6's `derive_plugin_id` produce a plugin_id satisfying ADR-022 grammar; Task 6 tests explicitly exercise `clarion-plugin-foo-bar` → `foo_bar`. +- `entity_id()` (WP1 Task 2) and Task 6's `derive_plugin_id` produce a plugin_id satisfying ADR-022 grammar; Task 6 tests explicitly exercise `loomweave-plugin-foo-bar` → `foo_bar`. - `Method::{Initialize, Initialized, AnalyzeFile, Shutdown, Exit}` — same five variants in `protocol.rs` (Task 2), `mock.rs` (Task 3), `host.rs` (Task 6), and the fixture binary's `main.rs` (Task 6). - `WriterCmd::InsertEntity { entity: Box, ack }` — Task 8's `persist_entity` wraps the record in `Box::new(record)` per WP1's L3 locked shape. @@ -4346,15 +4346,15 @@ Every lock-in (L4/L5/L6/L9) has a dedicated Task that lands it. Every UQ-WP2-* h - The safety comment addresses fork-safety (post-fork, pre-exec, async-signal-safe only). - `setrlimit` is on the POSIX.1-2017 §2.4.3 AS-safe list. -2. **`plugin_id` derivation narrowing**. Manifest `plugin.name` uses grammar `[a-z][a-z0-9_-]*` (dashes permitted) because it doubles as the PATH binary name. ADR-022's EntityId grammar for the `plugin_id` segment is stricter: `[a-z][a-z0-9_]*`. The host derives `plugin_id = manifest.name.strip_prefix("clarion-plugin-").replace('-', '_')`. This is a WP2 convention not stated verbatim in ADR-022; Task 6's commit message cites the derivation, and Task 10 should mention it in the UQ-WP2-11 resolution line. +2. **`plugin_id` derivation narrowing**. Manifest `plugin.name` uses grammar `[a-z][a-z0-9_-]*` (dashes permitted) because it doubles as the PATH binary name. ADR-022's EntityId grammar for the `plugin_id` segment is stricter: `[a-z][a-z0-9_]*`. The host derives `plugin_id = manifest.name.strip_prefix("loomweave-plugin-").replace('-', '_')`. This is a WP2 convention not stated verbatim in ADR-022; Task 6's commit message cites the derivation, and Task 10 should mention it in the UQ-WP2-11 resolution line. 3. **`source_file_id` carries the canonical file path string, not a file-entity ID**. WP1's `EntityRecord.source_file_id: Option` is a foreign-key placeholder. Task 8's `persist_entity` puts the canonical file path string into it. WP4/WP5's file-discovery pass will replace that placeholder with the real core-minted file-entity ID; the schema column is permissive (TEXT). Flag to the design-doc author post-Sprint-1 if a stricter foreign-key constraint is wanted. -4. **Sprint 1 findings are log-only**. The `CLA-INFRA-PLUGIN-*` rule IDs are emitted via `tracing::warn!(rule_id = "...", ...)` rather than as DB `findings` rows. WP6's scanner-ingest API (ADR-013) and the `POST /api/v1/scan-results` Filigree endpoint are where these logs become persisted findings. The rule IDs and field names locked in Task 6 are the forward-compatible contract. +4. **Sprint 1 findings are log-only**. The `LMWV-INFRA-PLUGIN-*` rule IDs are emitted via `tracing::warn!(rule_id = "...", ...)` rather than as DB `findings` rows. WP6's scanner-ingest API (ADR-013) and the `POST /api/v1/scan-results` Filigree endpoint are where these logs become persisted findings. The rule IDs and field names locked in Task 6 are the forward-compatible contract. 5. **`nix = "0.28"` with `features = ["resource"]` + `default-features = false`** — minimises the dependency surface. Older nix versions had a different `setrlimit` signature; the plan pins 0.28 exactly. If a reviewer bumps this, re-verify the pre_exec closure compiles. -6. **Fixture binary lives under `crates/clarion-mock-plugin/`** as a full workspace member, not an example. Reason: `assert_cmd::Command::cargo_bin` requires a real bin target, and `CARGO_BIN_EXE_clarion-mock-plugin` is only set when the consuming crate declares it as a dev-dependency. Tasks 6 + 8 both declare it. +6. **Fixture binary lives under `crates/loomweave-mock-plugin/`** as a full workspace member, not an example. Reason: `assert_cmd::Command::cargo_bin` requires a real bin target, and `CARGO_BIN_EXE_loomweave-mock-plugin` is only set when the consuming crate declares it as a dev-dependency. Tasks 6 + 8 both declare it. --- diff --git a/docs/implementation/agent-plans/2026-05-05-b2-class-module-entities.md b/docs/implementation/agent-plans/2026-05-05-b2-class-module-entities.md index ba261e53..dbc2555b 100644 --- a/docs/implementation/agent-plans/2026-05-05-b2-class-module-entities.md +++ b/docs/implementation/agent-plans/2026-05-05-b2-class-module-entities.md @@ -20,11 +20,11 @@ Files this plan touches: | File | Role | Tasks | |---|---|---| -| `plugins/python/src/clarion_plugin_python/extractor.py` | Add TypedDict shapes; add `_build_module_entity`, `_build_class_entity`; rename `_build_entity` → `_build_function_entity`; switch `_walk` to `match` dispatch; emit module entity (prepended); skip top-level `__init__.py` | 1, 2 | +| `plugins/python/src/loomweave_plugin_python/extractor.py` | Add TypedDict shapes; add `_build_module_entity`, `_build_class_entity`; rename `_build_entity` → `_build_function_entity`; switch `_walk` to `match` dispatch; emit module entity (prepended); skip top-level `__init__.py` | 1, 2 | | `plugins/python/tests/test_extractor.py` | Add 11+ new tests for module / class / source-range / __init__.py-skip; rename two empty-file tests | 1, 2, 7 | | `plugins/python/plugin.toml` | Add `class`, `module` to `entity_kinds`; bump `ontology_version` to 0.2.0 | 3 | -| `plugins/python/src/clarion_plugin_python/server.py` | Bump `ONTOLOGY_VERSION` constant to 0.2.0 | 3 | -| `plugins/python/src/clarion_plugin_python/__init__.py` | Bump package `__version__` to 0.1.1 (patch — different concept from ontology_version) | 3 | +| `plugins/python/src/loomweave_plugin_python/server.py` | Bump `ONTOLOGY_VERSION` constant to 0.2.0 | 3 | +| `plugins/python/src/loomweave_plugin_python/__init__.py` | Bump package `__version__` to 0.1.1 (patch — different concept from ontology_version) | 3 | | `fixtures/entity_id.json` | Add 5 new rows: 2 module + 3 class | 4 | | `plugins/python/tests/test_round_trip.py` | Replace exact-total assertions with by-kind invariants | 5 | | `tests/e2e/sprint_1_walking_skeleton.sh` | Update entity-count assertion: 1 → 2 (now includes module entity) | 6 | @@ -35,7 +35,7 @@ Files this plan touches: ## Task 1: TypedDict shapes + module entity emission + SyntaxError + top-level `__init__.py` skip **Files:** -- Modify: `plugins/python/src/clarion_plugin_python/extractor.py` +- Modify: `plugins/python/src/loomweave_plugin_python/extractor.py` - Modify: `plugins/python/tests/test_extractor.py` This is the meatiest task. It introduces the TypedDict wire-shape, the `_module_source_range` helper, the `_build_module_entity` builder, the SyntaxError-degraded-emission branch, and the top-level `__init__.py` skip — all behind failing tests written first. The `_walk` loop is left function-only here; class emission lands in Task 2. @@ -50,7 +50,7 @@ def test_module_source_range_no_trailing_newline() -> None: `"a\\nb"` has one newline → end_line = 2. """ - from clarion_plugin_python.extractor import _module_source_range + from loomweave_plugin_python.extractor import _module_source_range rng = _module_source_range("a\nb") assert rng == {"start_line": 1, "start_col": 0, "end_line": 2, "end_col": 0} @@ -58,7 +58,7 @@ def test_module_source_range_no_trailing_newline() -> None: def test_module_source_range_crlf() -> None: """CRLF-terminated file produces same end_line as LF (count('\\n') handles both).""" - from clarion_plugin_python.extractor import _module_source_range + from loomweave_plugin_python.extractor import _module_source_range rng = _module_source_range("a\r\nb\r\n") # Two `\n`s → end_line = 3 (one past the last terminator). @@ -67,7 +67,7 @@ def test_module_source_range_crlf() -> None: def test_module_source_range_empty_string() -> None: """Empty source → end_line = 1 (count is 0; +1).""" - from clarion_plugin_python.extractor import _module_source_range + from loomweave_plugin_python.extractor import _module_source_range rng = _module_source_range("") assert rng == {"start_line": 1, "start_col": 0, "end_line": 1, "end_col": 0} @@ -81,7 +81,7 @@ Expected: FAIL with `ImportError: cannot import name '_module_source_range'`. - [ ] **Step 1.3: Add TypedDict imports + `_module_source_range` helper to extractor.py** -In `plugins/python/src/clarion_plugin_python/extractor.py`, replace the imports block (lines 45-53) and append the new TypedDicts + helper. The replacement block: +In `plugins/python/src/loomweave_plugin_python/extractor.py`, replace the imports block (lines 45-53) and append the new TypedDicts + helper. The replacement block: ```python from __future__ import annotations @@ -91,8 +91,8 @@ import sys from pathlib import PurePosixPath from typing import Any, Literal, NotRequired, TypedDict -from clarion_plugin_python.entity_id import entity_id -from clarion_plugin_python.qualname import reconstruct_qualname +from loomweave_plugin_python.entity_id import entity_id +from loomweave_plugin_python.qualname import reconstruct_qualname _PLUGIN_ID = "python" @@ -286,10 +286,10 @@ def extract( dotted_module = module_dotted_name(prefix_source) # Top-level __init__.py would resolve to "" — entity_id() rejects that - # (crates/clarion-core/src/entity_id.rs:97-101). Skip with stderr. + # (crates/loomweave-core/src/entity_id.rs:97-101). Skip with stderr. if not dotted_module: sys.stderr.write( - f"clarion-plugin-python: skipping {file_path}: " + f"loomweave-plugin-python: skipping {file_path}: " f"top-level __init__.py has no package name\n", ) return [] @@ -298,7 +298,7 @@ def extract( tree = ast.parse(source) except SyntaxError as exc: sys.stderr.write( - f"clarion-plugin-python: skipping {file_path}: syntax error at " + f"loomweave-plugin-python: skipping {file_path}: syntax error at " f"line {exc.lineno}: {exc.msg}\n", ) return [_build_module_entity(source, dotted_module, file_path, "syntax_error")] @@ -403,7 +403,7 @@ def test_top_level_init_py_skipped_with_stderr( `module_dotted_name("__init__.py")` returns "" (the empty stem case). Emitting an entity with empty qualified_name would crash the entity-ID - assembler at crates/clarion-core/src/entity_id.rs:97-101. + assembler at crates/loomweave-core/src/entity_id.rs:97-101. """ entities = extract("def helper():\n pass\n", "__init__.py") assert entities == [] @@ -499,7 +499,7 @@ Expected: all PASS. If ruff format complains, run `ruff format plugins/python` t - [ ] **Step 1.21: Commit** ```bash -git add plugins/python/src/clarion_plugin_python/extractor.py plugins/python/tests/test_extractor.py +git add plugins/python/src/loomweave_plugin_python/extractor.py plugins/python/tests/test_extractor.py git commit -m "$(cat <<'EOF' feat(wp3): module entity emission with parse_status (B.2 Q1) @@ -526,7 +526,7 @@ EOF ## Task 2: Per-kind builder split + `_build_class_entity` **Files:** -- Modify: `plugins/python/src/clarion_plugin_python/extractor.py` +- Modify: `plugins/python/src/loomweave_plugin_python/extractor.py` - Modify: `plugins/python/tests/test_extractor.py` Refactor the dispatcher and add class entity emission. After this task, all three kinds (function, class, module) ship. @@ -715,7 +715,7 @@ Expected: all PASS. - [ ] **Step 2.9: Commit** ```bash -git add plugins/python/src/clarion_plugin_python/extractor.py plugins/python/tests/test_extractor.py +git add plugins/python/src/loomweave_plugin_python/extractor.py plugins/python/tests/test_extractor.py git commit -m "$(cat <<'EOF' feat(wp3): class entity emission + per-kind builders (B.2 Q3) @@ -736,8 +736,8 @@ EOF **Files:** - Modify: `plugins/python/plugin.toml` -- Modify: `plugins/python/src/clarion_plugin_python/server.py` -- Modify: `plugins/python/src/clarion_plugin_python/__init__.py` +- Modify: `plugins/python/src/loomweave_plugin_python/server.py` +- Modify: `plugins/python/src/loomweave_plugin_python/__init__.py` §10 resolution: package `__version__` → `0.1.1` (patch); ontology_version → `0.2.0` (matches the kind-set expansion). The two are different concepts. @@ -752,9 +752,9 @@ Old: # decorators are WP3-feature-complete kinds. entity_kinds = ["function"] edge_kinds = [] -# Per ADR-022: uppercase `CLA-{PLUGIN_ID_UPPER}-`. Reserved at parse -# against the CLA-INFRA-* and CLA-FACT-* namespaces. -rule_id_prefix = "CLA-PY-" +# Per ADR-022: uppercase `LMWV-{PLUGIN_ID_UPPER}-`. Reserved at parse +# against the LMWV-INFRA-* and LMWV-FACT-* namespaces. +rule_id_prefix = "LMWV-PY-" # Feeds ADR-007 cache keying; bump when the entity/edge/rule set shifts. ontology_version = "0.1.0" ``` @@ -766,9 +766,9 @@ New: # imports, calls remain WP3-feature-complete (later Sprint 2+). entity_kinds = ["function", "class", "module"] edge_kinds = [] -# Per ADR-022: uppercase `CLA-{PLUGIN_ID_UPPER}-`. Reserved at parse -# against the CLA-INFRA-* and CLA-FACT-* namespaces. -rule_id_prefix = "CLA-PY-" +# Per ADR-022: uppercase `LMWV-{PLUGIN_ID_UPPER}-`. Reserved at parse +# against the LMWV-INFRA-* and LMWV-FACT-* namespaces. +rule_id_prefix = "LMWV-PY-" # Bumps when the entity/edge/rule set shifts. NOTE: ADR-007's summary-cache # key is the 5-tuple (entity_id, content_hash, prompt_template_id, # model_tier, guidance_fingerprint) — ontology_version is handshake-validation, @@ -779,7 +779,7 @@ ontology_version = "0.2.0" - [ ] **Step 3.2: Update `server.py` constant** -In `plugins/python/src/clarion_plugin_python/server.py:35`: +In `plugins/python/src/loomweave_plugin_python/server.py:35`: Old: `ONTOLOGY_VERSION = "0.1.0"` @@ -787,7 +787,7 @@ New: `ONTOLOGY_VERSION = "0.2.0"` - [ ] **Step 3.3: Update package `__version__`** -In `plugins/python/src/clarion_plugin_python/__init__.py`: +In `plugins/python/src/loomweave_plugin_python/__init__.py`: Old: `__version__ = "0.1.0"` @@ -821,7 +821,7 @@ Expected: all PASS. - [ ] **Step 3.6: Commit** ```bash -git add plugins/python/plugin.toml plugins/python/src/clarion_plugin_python/server.py plugins/python/src/clarion_plugin_python/__init__.py +git add plugins/python/plugin.toml plugins/python/src/loomweave_plugin_python/server.py plugins/python/src/loomweave_plugin_python/__init__.py git commit -m "$(cat <<'EOF' feat(wp3): ontology v0.2.0 — entity_kinds += class, module (B.2) @@ -903,7 +903,7 @@ Mind JSON commas — the existing module row currently ends with a comma before - [ ] **Step 4.3: Validate JSON syntax** -Run: `python3 -c 'import json; json.load(open("/home/john/clarion/fixtures/entity_id.json"))'` +Run: `python3 -c 'import json; json.load(open("/home/john/loomweave/fixtures/entity_id.json"))'` Expected: no output (valid JSON). @@ -911,7 +911,7 @@ Expected: no output (valid JSON). Run: ```bash -cargo nextest run -p clarion-core entity_id::tests::shared_fixture_byte_for_byte_parity +cargo nextest run -p loomweave-core entity_id::tests::shared_fixture_byte_for_byte_parity plugins/python/.venv/bin/pytest plugins/python/tests/test_entity_id.py::test_matches_shared_fixture -v ``` @@ -929,7 +929,7 @@ simple class, nested class, class-in-function. The existing `pkg.submodule` module row's "future" annotation is updated since B.2 makes module a real Python-plugin kind. -Both crates/clarion-core/src/entity_id.rs::tests::shared_fixture_byte_for_byte_parity +Both crates/loomweave-core/src/entity_id.rs::tests::shared_fixture_byte_for_byte_parity and plugins/python/tests/test_entity_id.py::test_matches_shared_fixture consume this file; divergence fails CI on both sides. @@ -956,11 +956,11 @@ Old (lines ~115-129): entities = response["result"]["entities"] ids = {e["id"] for e in entities} # Public extractor API must be present. - assert "python:function:clarion_plugin_python.extractor.module_dotted_name" in ids - assert "python:function:clarion_plugin_python.extractor.extract" in ids + assert "python:function:loomweave_plugin_python.extractor.module_dotted_name" in ids + assert "python:function:loomweave_plugin_python.extractor.extract" in ids # Private walker is a FunctionDef too, so it emits. - assert "python:function:clarion_plugin_python.extractor._walk" in ids - assert "python:function:clarion_plugin_python.extractor._build_entity" in ids + assert "python:function:loomweave_plugin_python.extractor._walk" in ids + assert "python:function:loomweave_plugin_python.extractor._build_entity" in ids # Every entity should carry kind="function" and the absolute # source.file_path we sent (project_root relativisation only affects @@ -981,19 +981,19 @@ New: # Invariants — no exact totals (those become merge-conflict generators # the moment someone adds a private helper to extractor.py). assert len(module_entities) == 1, "exactly one module entity per analyzed file" - assert module_entities[0]["id"] == "python:module:clarion_plugin_python.extractor" + assert module_entities[0]["id"] == "python:module:loomweave_plugin_python.extractor" assert module_entities[0].get("parse_status") == "ok" # Public extractor API must be present. - assert "python:function:clarion_plugin_python.extractor.module_dotted_name" in function_ids - assert "python:function:clarion_plugin_python.extractor.extract" in function_ids + assert "python:function:loomweave_plugin_python.extractor.module_dotted_name" in function_ids + assert "python:function:loomweave_plugin_python.extractor.extract" in function_ids # Private walker is a FunctionDef too, so it emits. - assert "python:function:clarion_plugin_python.extractor._walk" in function_ids + assert "python:function:loomweave_plugin_python.extractor._walk" in function_ids # B.2 renamed `_build_entity` → `_build_function_entity` and added # `_build_class_entity` + `_build_module_entity` (and `_module_source_range`). - assert "python:function:clarion_plugin_python.extractor._build_function_entity" in function_ids - assert "python:function:clarion_plugin_python.extractor._build_class_entity" in function_ids - assert "python:function:clarion_plugin_python.extractor._build_module_entity" in function_ids + assert "python:function:loomweave_plugin_python.extractor._build_function_entity" in function_ids + assert "python:function:loomweave_plugin_python.extractor._build_class_entity" in function_ids + assert "python:function:loomweave_plugin_python.extractor._build_module_entity" in function_ids # Extractor has no top-level classes (module-level functions only), # so class_entities should be empty for this specific target. @@ -1007,7 +1007,7 @@ New: - [ ] **Step 5.2: Reinstall plugin so the binary picks up Task 1+2 changes** -The round-trip test invokes the *installed* `clarion-plugin-python` binary. Editable install (`pip install -e`) makes the *source* changes take effect immediately, but verify: +The round-trip test invokes the *installed* `loomweave-plugin-python` binary. Editable install (`pip install -e`) makes the *source* changes take effect immediately, but verify: Run: `plugins/python/.venv/bin/pip install -e plugins/python[dev]` (idempotent reinstall — no-op if up to date). @@ -1017,7 +1017,7 @@ Run: `plugins/python/.venv/bin/pytest plugins/python/tests/test_round_trip.py -v Expected: PASS. -If FAIL with "binary not found," check `which clarion-plugin-python` from the venv. If FAIL with mismatched IDs, the rename in Task 2 (`_build_entity` → `_build_function_entity`) wasn't applied or didn't reach the installed binary; reinstall and re-run. +If FAIL with "binary not found," check `which loomweave-plugin-python` from the venv. If FAIL with mismatched IDs, the rename in Task 2 (`_build_entity` → `_build_function_entity`) wasn't applied or didn't reach the installed binary; reinstall and re-run. - [ ] **Step 5.4: Commit** @@ -1053,12 +1053,12 @@ In `tests/e2e/sprint_1_walking_skeleton.sh`, replace lines 71-78 (the `RESULT=$( Old: ```bash -RESULT=$(sqlite3 "$DEMO_DIR/.clarion/clarion.db" "select id, kind from entities order by id;") +RESULT=$(sqlite3 "$DEMO_DIR/.loomweave/loomweave.db" "select id, kind from entities order by id;") EXPECTED="python:function:demo.hello|function" if [ "$RESULT" != "$EXPECTED" ]; then log "DB contents:" - sqlite3 "$DEMO_DIR/.clarion/clarion.db" "select * from entities;" >&2 || true + sqlite3 "$DEMO_DIR/.loomweave/loomweave.db" "select * from entities;" >&2 || true fail "expected exactly '$EXPECTED', got '$RESULT'" fi @@ -1067,7 +1067,7 @@ log "PASS: walking skeleton persisted $RESULT" New: ```bash -RESULT=$(sqlite3 "$DEMO_DIR/.clarion/clarion.db" "select id, kind from entities order by id;") +RESULT=$(sqlite3 "$DEMO_DIR/.loomweave/loomweave.db" "select id, kind from entities order by id;") # B.2 (Sprint 2): every analyzed file emits a module entity in addition to # its function/class entities. The demo file `def hello(): return "world"` # produces exactly two rows. @@ -1076,7 +1076,7 @@ python:module:demo|module" if [ "$RESULT" != "$EXPECTED" ]; then log "DB contents:" - sqlite3 "$DEMO_DIR/.clarion/clarion.db" "select * from entities;" >&2 || true + sqlite3 "$DEMO_DIR/.loomweave/loomweave.db" "select * from entities;" >&2 || true fail "expected exactly:\n$EXPECTED\ngot:\n$RESULT" fi @@ -1085,7 +1085,7 @@ log "PASS: walking skeleton persisted module + function entities" The newline inside `EXPECTED` matches sqlite3's default output format (one row per line, alphabetic order: `python:function:demo.hello` < `python:module:demo`). -- [ ] **Step 6.2: Build a fresh clarion binary so the script picks up nothing of Rust changes (none in this plan, but the venv has the new Python plugin)** +- [ ] **Step 6.2: Build a fresh loomweave binary so the script picks up nothing of Rust changes (none in this plan, but the venv has the new Python plugin)** The script does `cargo build --workspace --release` itself; ensure the editable plugin install in the venv reflects Task 1+2: @@ -1144,7 +1144,7 @@ Skip — the work is already in Task 1's commit. - [ ] **Step 8.1: Verify forward-pointer exists in the Sprint-1 WP3 doc** -Run: `grep -n "B.2" /home/john/clarion/docs/implementation/sprint-1/wp3-python-plugin.md | head -5` +Run: `grep -n "B.2" /home/john/loomweave/docs/implementation/sprint-1/wp3-python-plugin.md | head -5` Expected: at least one match referencing `b2-class-module-entities.md` or "Sprint 2 / B.2". If none, append the forward-pointer near §1 "out of scope": diff --git a/docs/implementation/agent-plans/2026-05-18-phase3-subsystems.md b/docs/implementation/agent-plans/2026-05-18-phase3-subsystems.md index 2ee037b1..96399383 100644 --- a/docs/implementation/agent-plans/2026-05-18-phase3-subsystems.md +++ b/docs/implementation/agent-plans/2026-05-18-phase3-subsystems.md @@ -7,13 +7,13 @@ > the Phase 1 review artifact; do not start implementation until the human > approves it in writing. -**Goal:** Add WP4 Phase 3 subsystem clustering to `clarion analyze`, satisfying +**Goal:** Add WP4 Phase 3 subsystem clustering to `loomweave analyze`, satisfying `REQ-CATALOG-05`, `REQ-ANALYZE-01`, and ADR-006: module-level clustering over `imports` plus `calls`, one `core:subsystem:{cluster_hash}` entity per cluster, `in_subsystem` edges from member modules, run stats, the weak-modularity fact, MCP membership lookup, determinism coverage, and operator documentation. -**Architecture:** `clarion analyze` remains the single write path. Phase 3 plugs +**Architecture:** `loomweave analyze` remains the single write path. Phase 3 plugs in after plugin entities/edges are inserted and before `CommitRun`. Storage gets read helpers that aggregate module-level dependency edges from the persisted graph. A new CLI-side clustering module turns that graph into @@ -25,8 +25,8 @@ serde_norway, sha2 for ADR-006 subsystem hashes, a graph-clustering crate candidate qualified in Task 1, Python 3.11 AST extraction for import edges, pytest, mypy, ruff, cargo nextest, cargo-deny, and existing shell E2E scripts. -**Spec:** ADR-006, ADR-003, ADR-022, `docs/clarion/1.0/requirements.md`, -`docs/clarion/1.0/system-design.md`, and the Phase 3 handoff +**Spec:** ADR-006, ADR-003, ADR-022, `docs/loomweave/1.0/requirements.md`, +`docs/loomweave/1.0/system-design.md`, and the Phase 3 handoff `docs/superpowers/handoffs/2026-05-18-phase3-subsystems-handoff.md`. **Filigree umbrella:** `clarion-1dfeebfa36` (P1 work_package; @@ -39,7 +39,7 @@ pytest, mypy, ruff, cargo nextest, cargo-deny, and existing shell E2E scripts. 1. **Schema accepts subsystem and in_subsystem without a new migration.** `entities.kind` is plugin-extensible and has no CHECK constraint - (`crates/clarion-storage/migrations/0001_initial_schema.sql:30-37`). + (`crates/loomweave-storage/migrations/0001_initial_schema.sql:30-37`). `edges.kind` is also plugin-extensible with writer-side enforcement rather than a CHECK (`:80-85`). The migration header still documents the ADR-024 edit-in-place policy and the 2026-05-18 CHECK edits (`:1-16`). The existing @@ -47,16 +47,16 @@ pytest, mypy, ruff, cargo nextest, cargo-deny, and existing shell E2E scripts. 2. **Structural ingest ends in `analyze::run` after plugin edges are inserted.** The per-plugin loop collects accepted entities, edges, unresolved sites, and - stats (`crates/clarion-cli/src/analyze.rs:216-405`). Entities are inserted + stats (`crates/loomweave-cli/src/analyze.rs:216-405`). Entities are inserted before unresolved sites, then edges, so edge foreign keys resolve at insert time (`:322-396`). Run-commit outcome handling begins immediately after the plugin loop (`:407-424`). Phase 3 belongs between those two blocks. 3. **Subsystem entities can use the existing writer path.** `EntityRecord` already supports `source_* = None`, `content_hash = None`, and arbitrary - `properties_json` (`crates/clarion-storage/src/commands.rs:48-70`), and + `properties_json` (`crates/loomweave-storage/src/commands.rs:48-70`), and `WriterCmd::InsertEntity` is generic (`:112-174`). `writer.rs` inserts the - generic record after `BeginRun` (`crates/clarion-storage/src/writer.rs:332-385`). + generic record after `BeginRun` (`crates/loomweave-storage/src/writer.rs:332-385`). No `InsertSubsystem` command is required. A new `InsertFinding` command is required for the weak-modularity fact because findings currently have a table but no writer command. @@ -64,24 +64,24 @@ pytest, mypy, ruff, cargo nextest, cargo-deny, and existing shell E2E scripts. 4. **Module-level dependency data is incomplete today.** Module entities and `contains` parent edges exist. `calls` and `references` are emitted at function/entity level. There is no storage helper that aggregates those - edges to module-to-module dependencies (`crates/clarion-storage/src/query.rs` + edges to module-to-module dependencies (`crates/loomweave-storage/src/query.rs` currently has call, reference, and contains helpers but no subsystem graph helper). The Python plugin manifest declares `contains`, `calls`, and `references` only (`plugins/python/plugin.toml:31-46`), and the extractor docs still say import emission is future scope - (`plugins/python/src/clarion_plugin_python/extractor.py:1-44`). Phase 3 + (`plugins/python/src/loomweave_plugin_python/extractor.py:1-44`). Phase 3 therefore needs Python `imports` edge emission plus a storage aggregation helper for both `imports` and function-level `calls`. -5. **Entity ID shape is already valid.** `clarion_core::entity_id` accepts +5. **Entity ID shape is already valid.** `loomweave_core::entity_id` accepts `plugin_id = "core"`, `kind = "subsystem"`, and a 12-hex canonical name; an existing unit test already covers `core:subsystem:a1b2c3d4` - (`crates/clarion-core/src/entity_id.rs:90-105`, `:192-194`). Verification + (`crates/loomweave-core/src/entity_id.rs:90-105`, `:192-194`). Verification run during planning: ```bash - cargo test -p clarion-core core_reserved_subsystem_kind -- --nocapture - cargo test -p clarion-core shared_ -- --nocapture + cargo test -p loomweave-core core_reserved_subsystem_kind -- --nocapture + cargo test -p loomweave-core shared_ -- --nocapture plugins/python/.venv/bin/pytest \ plugins/python/tests/test_entity_id.py::test_matches_shared_fixture \ plugins/python/tests/test_entity_id.py::test_matches_shared_contains_edge_fixture \ @@ -129,7 +129,7 @@ with a fixed seed in tests, stop and amend this plan before Task 2. **Recommendation:** keep `in_subsystem` structural and core-owned. The writer already lists it in `STRUCTURAL_EDGE_KINDS` -(`crates/clarion-storage/src/writer.rs:450-457`) and rejects source ranges on +(`crates/loomweave-storage/src/writer.rs:450-457`) and rejects source ranges on structural edges (`:478-502`). The Python plugin must not declare or emit it; the core clustering module emits it after the subsystem entity is inserted. @@ -140,7 +140,7 @@ Direction: `member_module -> subsystem`. The MCP membership helper queries **Recommendation:** add a new `subsystem_members(id)` MCP tool rather than folding the behavior into `neighborhood`. Requirements already name -`subsystem_members` (`docs/clarion/1.0/requirements.md:365-371`), and a +`subsystem_members` (`docs/loomweave/1.0/requirements.md:365-371`), and a separate tool keeps the response schema narrow: subsystem metadata plus an ordered member list. `neighborhood` can later include subsystem links additively, but that should not be the only way to inspect a subsystem. @@ -168,7 +168,7 @@ dispatch. Subsystem summarization remains out of scope. } ``` -Do not create `CLA-FACT-CLUSTERING-NO-INPUT`; the handoff explicitly limits +Do not create `LMWV-FACT-CLUSTERING-NO-INPUT`; the handoff explicitly limits Phase 7 scope to the weak-modularity finding. Single-module and no-plugin fixtures should remain quiet but inspectable through `runs.stats`. @@ -215,17 +215,17 @@ object. Hard failures before Phase 3 keep the existing failure stats. test fixture needs an index or helper view added; the current implementation should not need that. `entities.kind` and `edges.kind` are already open by policy, and `findings.kind`/`severity` already accept `fact` and `INFO` -(`crates/clarion-storage/migrations/0001_initial_schema.sql:103-136`). +(`crates/loomweave-storage/migrations/0001_initial_schema.sql:103-136`). ### 7. Weak-modularity finding anchor -**Recommendation:** persist `CLA-FACT-CLUSTERING-WEAK-MODULARITY` as a `fact` +**Recommendation:** persist `LMWV-FACT-CLUSTERING-WEAK-MODULARITY` as a `fact` with severity `INFO`, anchored to the largest emitted subsystem entity. Put all subsystem IDs in `related_entities` and include `run_id`, `modularity_score`, `threshold`, and `algorithm` in `properties`. Reason: the current findings schema requires `entity_id NOT NULL` -(`crates/clarion-storage/migrations/0001_initial_schema.sql:119`). Making +(`crates/loomweave-storage/migrations/0001_initial_schema.sql:119`). Making run-level findings nullable or inventing a `core:run:*` entity is a schema and ontology change outside this handoff. If modularity is unavailable because there are no emitted subsystems, record the skip reason in `runs.stats` and do @@ -239,29 +239,29 @@ not emit a finding. |---|---|---| | `Cargo.toml` | Add graph/hash dependencies after Task 1 qualification | 1 | | `Cargo.lock` | Lock dependency graph | 1 | -| `crates/clarion-cli/src/main.rs` | Pass analyze config path into `analyze::run` | 2 | -| `crates/clarion-cli/src/cli.rs` | Add `clarion analyze --config ` | 2 | -| `crates/clarion-cli/src/config.rs` | New analyze config parser and defaults for `analysis.clustering` | 2 | -| `crates/clarion-cli/src/analyze.rs` | Accept config, persist config JSON, filter candidate `imports` before writer insertion, call Phase 3 before commit, merge clustering stats | 2, 3, 5 | -| `crates/clarion-cli/src/clustering.rs` | New Phase 3 graph/algorithm/subsystem writer orchestration | 1, 4, 5 | -| `crates/clarion-cli/Cargo.toml` | Wire dependencies used by config/clustering | 1, 2 | -| `crates/clarion-cli/tests/analyze.rs` | Config/run-stats/import-filter/Phase-3 integration tests | 2, 3, 5 | +| `crates/loomweave-cli/src/main.rs` | Pass analyze config path into `analyze::run` | 2 | +| `crates/loomweave-cli/src/cli.rs` | Add `loomweave analyze --config ` | 2 | +| `crates/loomweave-cli/src/config.rs` | New analyze config parser and defaults for `analysis.clustering` | 2 | +| `crates/loomweave-cli/src/analyze.rs` | Accept config, persist config JSON, filter candidate `imports` before writer insertion, call Phase 3 before commit, merge clustering stats | 2, 3, 5 | +| `crates/loomweave-cli/src/clustering.rs` | New Phase 3 graph/algorithm/subsystem writer orchestration | 1, 4, 5 | +| `crates/loomweave-cli/Cargo.toml` | Wire dependencies used by config/clustering | 1, 2 | +| `crates/loomweave-cli/tests/analyze.rs` | Config/run-stats/import-filter/Phase-3 integration tests | 2, 3, 5 | | `plugins/python/plugin.toml` | Add `imports`; bump plugin and ontology versions | 3 | -| `plugins/python/src/clarion_plugin_python/__init__.py` | Bump package version | 3 | -| `plugins/python/src/clarion_plugin_python/server.py` | Bump ontology constant; pass import resolver inputs if needed | 3 | -| `plugins/python/src/clarion_plugin_python/extractor.py` | Extract AST import sites and emit candidate `imports` edges | 3 | +| `plugins/python/src/loomweave_plugin_python/__init__.py` | Bump package version | 3 | +| `plugins/python/src/loomweave_plugin_python/server.py` | Bump ontology constant; pass import resolver inputs if needed | 3 | +| `plugins/python/src/loomweave_plugin_python/extractor.py` | Extract AST import sites and emit candidate `imports` edges | 3 | | `plugins/python/tests/test_extractor.py` | Import-edge unit coverage | 3 | | `plugins/python/tests/test_round_trip.py` | Manifest/round-trip coverage for `imports` | 3 | | `fixtures/entity_id.json` | Shared import-edge fixture rows if parity helpers grow | 3 | -| `crates/clarion-core/src/entity_id.rs` | Optional shared fixture parity for `imports` edge shape | 3 | -| `crates/clarion-storage/src/query.rs` | Module dependency graph and subsystem membership helpers | 4, 6 | -| `crates/clarion-storage/src/lib.rs` | Re-export new query structs/helpers if needed | 4, 6 | -| `crates/clarion-storage/src/commands.rs` | Add `FindingRecord` and `WriterCmd::InsertFinding` | 5 | -| `crates/clarion-storage/src/writer.rs` | Persist findings through writer actor | 5 | -| `crates/clarion-storage/tests/writer_actor.rs` | Finding writer and subsystem edge contract tests | 5 | -| `crates/clarion-storage/tests/schema_apply.rs` | Guard existing open vocabulary assumptions | 4, 5 | -| `crates/clarion-mcp/src/lib.rs` | Add `subsystem_members`; summary policy branch for subsystems | 6 | -| `crates/clarion-mcp/tests/storage_tools.rs` | MCP tool list, membership response, summary no-LLM tests | 6 | +| `crates/loomweave-core/src/entity_id.rs` | Optional shared fixture parity for `imports` edge shape | 3 | +| `crates/loomweave-storage/src/query.rs` | Module dependency graph and subsystem membership helpers | 4, 6 | +| `crates/loomweave-storage/src/lib.rs` | Re-export new query structs/helpers if needed | 4, 6 | +| `crates/loomweave-storage/src/commands.rs` | Add `FindingRecord` and `WriterCmd::InsertFinding` | 5 | +| `crates/loomweave-storage/src/writer.rs` | Persist findings through writer actor | 5 | +| `crates/loomweave-storage/tests/writer_actor.rs` | Finding writer and subsystem edge contract tests | 5 | +| `crates/loomweave-storage/tests/schema_apply.rs` | Guard existing open vocabulary assumptions | 4, 5 | +| `crates/loomweave-mcp/src/lib.rs` | Add `subsystem_members`; summary policy branch for subsystems | 6 | +| `crates/loomweave-mcp/tests/storage_tools.rs` | MCP tool list, membership response, summary no-LLM tests | 6 | | `tests/e2e/phase3_subsystems.sh` | New multi-module subsystem E2E and determinism check | 7 | | `tests/e2e/sprint_1_walking_skeleton.sh` | Verify unchanged; only edit if necessary to keep current assertions honest | 7 | | `tests/fixtures/phase3_subsystems/` | New multi-module Python fixture, if the E2E does not build it inline | 7 | @@ -275,9 +275,9 @@ not emit a finding. **Files:** - Modify: `Cargo.toml` - Modify: `Cargo.lock` -- Modify: `crates/clarion-cli/Cargo.toml` -- Add: `crates/clarion-cli/src/clustering.rs` -- Modify: `crates/clarion-cli/src/main.rs` only to `mod clustering;` +- Modify: `crates/loomweave-cli/Cargo.toml` +- Add: `crates/loomweave-cli/src/clustering.rs` +- Modify: `crates/loomweave-cli/src/main.rs` only to `mod clustering;` **Scope:** Qualify the graph crate and create a tiny adapter boundary before any analyze integration. This task does not write database rows. @@ -298,9 +298,9 @@ any analyze integration. This task does not write database rows. - [x] Run qualification gates: ```bash -cargo test -p clarion-cli clustering -- --nocapture +cargo test -p loomweave-cli clustering -- --nocapture cargo deny check -cargo tree -p clarion-cli +cargo tree -p loomweave-cli ``` **Exit:** The tests prove fixed-seed determinism, directed weighted behavior, @@ -314,13 +314,13 @@ without broadening `deny.toml`. If not, stop and amend this plan. ## Task 2: Analyze Config Plumbing **Files:** -- Modify: `crates/clarion-cli/src/cli.rs` -- Modify: `crates/clarion-cli/src/main.rs` -- Add: `crates/clarion-cli/src/config.rs` -- Modify: `crates/clarion-cli/src/analyze.rs` -- Modify: `crates/clarion-cli/tests/analyze.rs` +- Modify: `crates/loomweave-cli/src/cli.rs` +- Modify: `crates/loomweave-cli/src/main.rs` +- Add: `crates/loomweave-cli/src/config.rs` +- Modify: `crates/loomweave-cli/src/analyze.rs` +- Modify: `crates/loomweave-cli/tests/analyze.rs` -**Scope:** Give `clarion analyze` the `analysis.clustering` configuration +**Scope:** Give `loomweave analyze` the `analysis.clustering` configuration surface and persist the resolved config in `runs.config`. No subsystem rows yet. - [x] Write failing tests: @@ -342,8 +342,8 @@ analysis: weight_by: reference_count ``` -- [x] Add `clarion analyze --config `, defaulting to - `/clarion.yaml` if present, otherwise defaults. +- [x] Add `loomweave analyze --config `, defaulting to + `/loomweave.yaml` if present, otherwise defaults. - [x] Change `analyze::run(path)` to accept an options/config value while preserving tests that call the default path. - [x] Replace the current `BeginRun.config_json = "{}"` with the resolved @@ -353,10 +353,10 @@ analysis: existing no-plugin/skipped tests still pass. ```bash -cargo test -p clarion-cli analyze_default_config_records_clustering_defaults -- --nocapture -cargo test -p clarion-cli analyze_config_file_overrides_clustering_seed_and_algorithm -- --nocapture -cargo test -p clarion-cli analyze_rejects_invalid_clustering_algorithm -- --nocapture -cargo test -p clarion-cli analyze_without_plugins_writes_skipped_run_row -- --nocapture +cargo test -p loomweave-cli analyze_default_config_records_clustering_defaults -- --nocapture +cargo test -p loomweave-cli analyze_config_file_overrides_clustering_seed_and_algorithm -- --nocapture +cargo test -p loomweave-cli analyze_rejects_invalid_clustering_algorithm -- --nocapture +cargo test -p loomweave-cli analyze_without_plugins_writes_skipped_run_row -- --nocapture ``` **Commit:** `feat(wp4): add analyze clustering config (phase3 task 2)` @@ -366,16 +366,16 @@ cargo test -p clarion-cli analyze_without_plugins_writes_skipped_run_row -- --no ## Task 3: Python imports Edge Emission **Files:** -- Modify: `crates/clarion-cli/src/analyze.rs` -- Modify: `crates/clarion-cli/tests/analyze.rs` +- Modify: `crates/loomweave-cli/src/analyze.rs` +- Modify: `crates/loomweave-cli/tests/analyze.rs` - Modify: `plugins/python/plugin.toml` -- Modify: `plugins/python/src/clarion_plugin_python/__init__.py` -- Modify: `plugins/python/src/clarion_plugin_python/server.py` -- Modify: `plugins/python/src/clarion_plugin_python/extractor.py` +- Modify: `plugins/python/src/loomweave_plugin_python/__init__.py` +- Modify: `plugins/python/src/loomweave_plugin_python/server.py` +- Modify: `plugins/python/src/loomweave_plugin_python/extractor.py` - Modify: `plugins/python/tests/test_extractor.py` - Modify: `plugins/python/tests/test_round_trip.py` - Modify: `fixtures/entity_id.json` if shared parity is extended -- Modify: `crates/clarion-core/src/entity_id.rs` if shared parity is extended +- Modify: `crates/loomweave-core/src/entity_id.rs` if shared parity is extended **Scope:** Emit anchored `imports` candidate edges from module entities. The host filters candidate imports whose `to_id` module is not present in the same @@ -419,7 +419,7 @@ plugins/python/.venv/bin/pytest plugins/python/tests/test_round_trip.py -q plugins/python/.venv/bin/mypy --strict plugins/python plugins/python/.venv/bin/ruff check plugins/python plugins/python/.venv/bin/ruff format --check plugins/python -cargo test -p clarion-cli analyze_filters_external_import_edges_before_writer_insert -- --nocapture +cargo test -p loomweave-cli analyze_filters_external_import_edges_before_writer_insert -- --nocapture ``` **Commit:** `feat(wp3): emit python imports edges (phase3 task 3)` @@ -429,9 +429,9 @@ cargo test -p clarion-cli analyze_filters_external_import_edges_before_writer_in ## Task 4: Storage Query Helpers for the Module Graph **Files:** -- Modify: `crates/clarion-storage/src/query.rs` -- Modify: `crates/clarion-storage/src/lib.rs` -- Modify: `crates/clarion-storage/tests/schema_apply.rs` +- Modify: `crates/loomweave-storage/src/query.rs` +- Modify: `crates/loomweave-storage/src/lib.rs` +- Modify: `crates/loomweave-storage/tests/schema_apply.rs` - Add or modify storage query tests in the existing storage test suite **Scope:** Read the persisted graph into a module-level dependency graph, and @@ -470,9 +470,9 @@ pub struct SubsystemMember { **Exit:** Query tests pass and existing writer/schema tests still pass. ```bash -cargo test -p clarion-storage module_dependency_edges -- --nocapture -cargo test -p clarion-storage subsystem_members -- --nocapture -cargo test -p clarion-storage schema_accepts_open_entity_and_edge_kinds -- --nocapture +cargo test -p loomweave-storage module_dependency_edges -- --nocapture +cargo test -p loomweave-storage subsystem_members -- --nocapture +cargo test -p loomweave-storage schema_accepts_open_entity_and_edge_kinds -- --nocapture ``` **Commit:** `feat(storage): add subsystem graph queries (phase3 task 4)` @@ -482,15 +482,15 @@ cargo test -p clarion-storage schema_accepts_open_entity_and_edge_kinds -- --noc ## Task 5: Analyze Phase 3 Writes, Stats, and Weak-Modularity Finding **Files:** -- Modify: `crates/clarion-cli/src/analyze.rs` -- Modify: `crates/clarion-cli/src/clustering.rs` -- Modify: `crates/clarion-cli/tests/analyze.rs` -- Modify: `crates/clarion-storage/src/commands.rs` -- Modify: `crates/clarion-storage/src/writer.rs` -- Modify: `crates/clarion-storage/tests/writer_actor.rs` +- Modify: `crates/loomweave-cli/src/analyze.rs` +- Modify: `crates/loomweave-cli/src/clustering.rs` +- Modify: `crates/loomweave-cli/tests/analyze.rs` +- Modify: `crates/loomweave-storage/src/commands.rs` +- Modify: `crates/loomweave-storage/src/writer.rs` +- Modify: `crates/loomweave-storage/tests/writer_actor.rs` **Scope:** Insert subsystem entities and `in_subsystem` edges during -`clarion analyze`, then persist run-level clustering stats and the weak +`loomweave analyze`, then persist run-level clustering stats and the weak modularity fact. - [x] Write failing Rust tests: @@ -536,10 +536,10 @@ and skipped/no-plugin stats tests still pass, proving stats additions are additive. ```bash -cargo test -p clarion-cli analyze_phase3 -- --nocapture -cargo test -p clarion-storage writer_inserts_fact_findings -- --nocapture -cargo test -p clarion-cli analyze_stats_reports_ambiguous_edges_total -- --nocapture -cargo test -p clarion-cli analyze_without_plugins_writes_skipped_run_row -- --nocapture +cargo test -p loomweave-cli analyze_phase3 -- --nocapture +cargo test -p loomweave-storage writer_inserts_fact_findings -- --nocapture +cargo test -p loomweave-cli analyze_stats_reports_ambiguous_edges_total -- --nocapture +cargo test -p loomweave-cli analyze_without_plugins_writes_skipped_run_row -- --nocapture ``` **Commit:** `feat(wp4): write subsystem clusters in analyze (phase3 task 5)` @@ -549,9 +549,9 @@ cargo test -p clarion-cli analyze_without_plugins_writes_skipped_run_row -- --no ## Task 6: MCP subsystem_members and Subsystem Summary Policy **Files:** -- Modify: `crates/clarion-mcp/src/lib.rs` -- Modify: `crates/clarion-mcp/tests/storage_tools.rs` -- Modify: `crates/clarion-storage/src/query.rs` only if Task 4 helpers need +- Modify: `crates/loomweave-mcp/src/lib.rs` +- Modify: `crates/loomweave-mcp/tests/storage_tools.rs` +- Modify: `crates/loomweave-storage/src/query.rs` only if Task 4 helpers need response-shape refinements **Scope:** Surface persisted subsystems through MCP without invoking the LLM. @@ -598,9 +598,9 @@ cargo test -p clarion-cli analyze_without_plugins_writes_skipped_run_row -- --no the additive eighth tool. ```bash -cargo test -p clarion-mcp subsystem_members -- --nocapture -cargo test -p clarion-mcp summary_on_subsystem_returns_policy_envelope_without_llm_call -- --nocapture -cargo test -p clarion-mcp tools_list -- --nocapture +cargo test -p loomweave-mcp subsystem_members -- --nocapture +cargo test -p loomweave-mcp summary_on_subsystem_returns_policy_envelope_without_llm_call -- --nocapture +cargo test -p loomweave-mcp tools_list -- --nocapture ``` **Commit:** `feat(mcp): expose subsystem membership tool (phase3 task 6)` @@ -623,12 +623,12 @@ gate before declaring Phase 3 ready. dependency groups and sparse cross-group edges. - [x] Add E2E script: - install project - - run `clarion analyze` + - run `loomweave analyze` - assert at least two `subsystem` rows - assert each subsystem has at least `min_cluster_size` module members - assert `runs.stats.clustering.status = "completed"` - run a second clean analysis and assert subsystem IDs and modularity match - - run `clarion serve` fixture interaction or a direct MCP harness call for + - run `loomweave serve` fixture interaction or a direct MCP harness call for `subsystem_members` - [x] Add `docs/operator/clustering.md` with: - config keys and defaults @@ -679,7 +679,7 @@ plugins/python/.venv/bin/pytest plugins/python/tests -q - `in_subsystem` edges link every member module to its subsystem. - `runs.stats.clustering` records status, config, counts, modularity, duration, and skip/finding state. -- `CLA-FACT-CLUSTERING-WEAK-MODULARITY` persists when modularity is below 0.3 +- `LMWV-FACT-CLUSTERING-WEAK-MODULARITY` persists when modularity is below 0.3 and there is an emitted subsystem anchor. - `subsystem_members(id)` is available through MCP. - `summary(id)` on a subsystem returns the no-subsystem-summary policy envelope diff --git a/docs/implementation/arch-analysis-2026-05-20-2124/00-coordination.md b/docs/implementation/arch-analysis-2026-05-20-2124/00-coordination.md index 3be24ee1..9d93540c 100644 --- a/docs/implementation/arch-analysis-2026-05-20-2124/00-coordination.md +++ b/docs/implementation/arch-analysis-2026-05-20-2124/00-coordination.md @@ -2,7 +2,7 @@ ## Configuration -- **Scope**: Full RC1 branch at `/home/john/clarion`, including Rust workspace crates, Python plugin, tests/perf/e2e harnesses, federation fixtures, release/CI scripts, and governing docs. +- **Scope**: Full RC1 branch at `/home/john/loomweave`, including Rust workspace crates, Python plugin, tests/perf/e2e harnesses, federation fixtures, release/CI scripts, and governing docs. - **Branch / commit**: `RC1` at `286d92d` (`RC1...origin/RC1 [ahead 1]`). - **Deliverables selected**: **Option G — Comprehensive**. - Rationale: user requested a fresh "root and branch" analysis after deleting the old analysis. @@ -15,12 +15,12 @@ ## Subsystem Candidates -1. `clarion-core` — domain primitives, plugin protocol/host, entity IDs, LLM provider contracts. -2. `clarion-storage` — SQLite schema, writer actor, reader pool, query/cache helpers. -3. `clarion-cli` — `clarion` binary, install/analyze/serve, HTTP read API, secret-scan glue, clustering. -4. `clarion-mcp` — consult-mode MCP read surface, summary/inferred dispatch, Filigree enrichment. -5. `clarion-scanner` — pre-ingest secret scanning engine and baseline policy. -6. `clarion-plugin-fixture` — protocol-compatible fixture plugin and host integration support. +1. `loomweave-core` — domain primitives, plugin protocol/host, entity IDs, LLM provider contracts. +2. `loomweave-storage` — SQLite schema, writer actor, reader pool, query/cache helpers. +3. `loomweave-cli` — `loomweave` binary, install/analyze/serve, HTTP read API, secret-scan glue, clustering. +4. `loomweave-mcp` — consult-mode MCP read surface, summary/inferred dispatch, Filigree enrichment. +5. `loomweave-scanner` — pre-ingest secret scanning engine and baseline policy. +6. `loomweave-plugin-fixture` — protocol-compatible fixture plugin and host integration support. 7. `plugins/python` — Python AST/pyright language plugin and Wardline probe. 8. `tests`, `scripts`, `.github`, and docs/fixtures — release, federation, quality, and validation infrastructure. @@ -45,7 +45,7 @@ - 2026-05-20 21:24 — Ran `git status --short --branch`; branch is `RC1...origin/RC1 [ahead 1]`. - 2026-05-20 21:24 — Removed old analysis directory `docs/implementation/arch-analysis-2026-05-18-1244/` at user request. - 2026-05-20 21:24 — Created fresh workspace `docs/implementation/arch-analysis-2026-05-20-2124/temp`. -- 2026-05-20 21:24 — Confirmed Rust workspace metadata has six crates: `clarion-core`, `clarion-storage`, `clarion-cli`, `clarion-mcp`, `clarion-scanner`, and `clarion-plugin-fixture`. +- 2026-05-20 21:24 — Confirmed Rust workspace metadata has six crates: `loomweave-core`, `loomweave-storage`, `loomweave-cli`, `loomweave-mcp`, `loomweave-scanner`, and `loomweave-plugin-fixture`. - 2026-05-20 21:25 — Started six focused subsystem exploration agents: core/fixture, storage, CLI/scanner, MCP, Python plugin, and release/federation/docs. - 2026-05-20 21:31 — Integrated all six exploration reports into the root-and-branch synthesis. - 2026-05-20 21:36 — Created Option G deliverables: discovery, catalog, diagrams, final report, quality, security, release readiness, test infrastructure, dependency analysis, and architect handover. @@ -55,9 +55,9 @@ | Agent | Scope | Result | |---|---|---| -| Aristotle | `clarion-core` and `clarion-plugin-fixture` | Complete; high confidence. | -| Poincare | `clarion-storage` | Complete; high confidence. | -| Halley | `clarion-cli` and `clarion-scanner` | Complete; high confidence. | -| James | `clarion-mcp` | Complete; high confidence. | +| Aristotle | `loomweave-core` and `loomweave-plugin-fixture` | Complete; high confidence. | +| Poincare | `loomweave-storage` | Complete; high confidence. | +| Halley | `loomweave-cli` and `loomweave-scanner` | Complete; high confidence. | +| James | `loomweave-mcp` | Complete; high confidence. | | Pascal | `plugins/python` | Complete; high confidence for source shape, medium for live runtime health because tests were not executed in the exploration pass. | | Mill | Release, federation, governance, docs, workflows | Complete; high confidence for doc/workflow shape, with live GitHub policy still unverified in this pass. | diff --git a/docs/implementation/arch-analysis-2026-05-20-2124/01-discovery-findings.md b/docs/implementation/arch-analysis-2026-05-20-2124/01-discovery-findings.md index 689c1b1d..23f26330 100644 --- a/docs/implementation/arch-analysis-2026-05-20-2124/01-discovery-findings.md +++ b/docs/implementation/arch-analysis-2026-05-20-2124/01-discovery-findings.md @@ -8,7 +8,7 @@ ## Executive Discovery -Clarion is a local-first code archaeology system. It ingests a repository, +Loomweave is a local-first code archaeology system. It ingests a repository, extracts code entities and relationships, persists a SQLite graph, and serves consult-mode agents through MCP and a federation HTTP read API. The v1.0 implementation is a Rust 2024 workspace plus a Python language plugin. @@ -24,12 +24,12 @@ high-blast-radius files that need restraint before tag. | Area | Role | |---|---| -| `crates/clarion-core` | Shared contracts, entity IDs, plugin host, manifest/protocol/transport validation, LLM provider adapters. | -| `crates/clarion-storage` | SQLite schema, writer actor, reader pool, typed graph queries, summary/inferred-edge caches. | -| `crates/clarion-cli` | `clarion` binary: install, analyze, serve, HTTP read API, release-facing operator paths. | -| `crates/clarion-mcp` | MCP consult-mode JSON-RPC server and tool handlers. | -| `crates/clarion-scanner` | Pre-ingest secret detector and detect-secrets-style baseline handling. | -| `crates/clarion-plugin-fixture` | Subprocess fixture plugin for host/integration tests. | +| `crates/loomweave-core` | Shared contracts, entity IDs, plugin host, manifest/protocol/transport validation, LLM provider adapters. | +| `crates/loomweave-storage` | SQLite schema, writer actor, reader pool, typed graph queries, summary/inferred-edge caches. | +| `crates/loomweave-cli` | `loomweave` binary: install, analyze, serve, HTTP read API, release-facing operator paths. | +| `crates/loomweave-mcp` | MCP consult-mode JSON-RPC server and tool handlers. | +| `crates/loomweave-scanner` | Pre-ingest secret detector and detect-secrets-style baseline handling. | +| `crates/loomweave-plugin-fixture` | Subprocess fixture plugin for host/integration tests. | | `plugins/python` | Python language extractor, JSON-RPC stdio server, Pyright-backed resolution, Wardline probe. | Approximate source inventory from this pass: @@ -44,21 +44,21 @@ Largest/highest-blast-radius files: | File | Approx LOC | Why It Matters | |---|---:|---| -| `crates/clarion-mcp/src/lib.rs` | 3127 | Tool catalog, MCP envelope, LLM summary/inferred-edge paths, Filigree enrichment. | -| `crates/clarion-core/src/plugin/host.rs` | 2935 | Plugin subprocess supervision, boundary validation, path/resource breakers. | -| `crates/clarion-cli/src/analyze.rs` | 2427 | Main ingestion orchestration and subsystem clustering path. | -| `crates/clarion-core/src/llm_provider.rs` | 2467 | Live-provider adapters, CLI/HTTP calls, usage accounting. | -| `crates/clarion-cli/src/http_read.rs` | 1532 | Federation HTTP contract, auth, envelopes, limits. | -| `plugins/python/src/clarion_plugin_python/pyright_session.py` | 1406 | LSP lifecycle, timeouts, target mapping. | +| `crates/loomweave-mcp/src/lib.rs` | 3127 | Tool catalog, MCP envelope, LLM summary/inferred-edge paths, Filigree enrichment. | +| `crates/loomweave-core/src/plugin/host.rs` | 2935 | Plugin subprocess supervision, boundary validation, path/resource breakers. | +| `crates/loomweave-cli/src/analyze.rs` | 2427 | Main ingestion orchestration and subsystem clustering path. | +| `crates/loomweave-core/src/llm_provider.rs` | 2467 | Live-provider adapters, CLI/HTTP calls, usage accounting. | +| `crates/loomweave-cli/src/http_read.rs` | 1532 | Federation HTTP contract, auth, envelopes, limits. | +| `plugins/python/src/loomweave_plugin_python/pyright_session.py` | 1406 | LSP lifecycle, timeouts, target mapping. | ## Source Of Truth The governing ladder is explicit and healthy: -1. Accepted ADRs under `docs/clarion/adr/`. -2. `docs/clarion/1.0/requirements.md`. -3. `docs/clarion/1.0/system-design.md`. -4. `docs/clarion/1.0/detailed-design.md`. +1. Accepted ADRs under `docs/loomweave/adr/`. +2. `docs/loomweave/1.0/requirements.md`. +3. `docs/loomweave/1.0/system-design.md`. +4. `docs/loomweave/1.0/detailed-design.md`. 5. Implementation history under `docs/implementation/`. Implementation-history documents are evidence, not governing design. Accepted @@ -82,22 +82,22 @@ operator documentation. ## Architecture Pattern Summary -1. `clarion install` creates `.clarion/clarion.db`, config files, ignore rules, +1. `loomweave install` creates `.loomweave/loomweave.db`, config files, ignore rules, and applies migrations. -2. `clarion analyze` discovers plugins, scans source before ingest, runs plugin +2. `loomweave analyze` discovers plugins, scans source before ingest, runs plugin analysis, writes core file/plugin entities and edges, runs graph completion and subsystem clustering, records run state, and persists findings. -3. `clarion serve` opens storage, starts MCP stdio serving, and optionally +3. `loomweave serve` opens storage, starts MCP stdio serving, and optionally starts the federation HTTP read API. 4. MCP clients query the graph for entity lookup, paths, neighborhoods, summaries, inferred calls, Filigree issue associations, and subsystem membership. 5. Federation consumers use the HTTP read API for file resolution and - briefing-safe content reads without making Clarion depend on sibling + briefing-safe content reads without making Loomweave depend on sibling products. -The design preserves the Loom doctrine: sibling integrations enrich Clarion, -but Clarion remains useful alone. +The design preserves the Weft doctrine: sibling integrations enrich Loomweave, +but Loomweave remains useful alone. ## High-Confidence Strengths @@ -122,10 +122,10 @@ but Clarion remains useful alone. not every end-to-end gate named in `AGENTS.md`. - `CHANGELOG.md` contains a federation auth error-code drift: `UNAUTHORIZED` versus canonical `UNAUTHENTICATED`. -- `crates/clarion-core/src/plugin/limits.rs` describes `EntityCountCap` as +- `crates/loomweave-core/src/plugin/limits.rs` describes `EntityCountCap` as covering entities, edges, and findings, while host edge processing says edges do not participate in the entity cap. -- HTTP HMAC is hand-rolled in `crates/clarion-cli/src/http_read.rs`; future +- HTTP HMAC is hand-rolled in `crates/loomweave-cli/src/http_read.rs`; future edits need crypto-specific review. - Python plugin pins and Wardline bounds are duplicated across manifest/config code without a direct drift test. diff --git a/docs/implementation/arch-analysis-2026-05-20-2124/02-subsystem-catalog.md b/docs/implementation/arch-analysis-2026-05-20-2124/02-subsystem-catalog.md index 66f86375..b78a736b 100644 --- a/docs/implementation/arch-analysis-2026-05-20-2124/02-subsystem-catalog.md +++ b/docs/implementation/arch-analysis-2026-05-20-2124/02-subsystem-catalog.md @@ -4,9 +4,9 @@ Each subsystem uses the archaeologist catalog contract: Location, Responsibility, Key Components, Dependencies, Patterns Observed, Concerns, and Confidence. -## 1. Clarion Core +## 1. Loomweave Core -**Location:** `crates/clarion-core` +**Location:** `crates/loomweave-core` **Responsibility:** Shared runtime contracts and enforcement: entity ID assembly, plugin manifest/protocol/transport/discovery/supervision, resource @@ -32,18 +32,18 @@ radius. **Confidence:** High. -## 2. Clarion Plugin Fixture +## 2. Loomweave Plugin Fixture -**Location:** `crates/clarion-plugin-fixture` +**Location:** `crates/loomweave-plugin-fixture` -**Responsibility:** Minimal binary test plugin that speaks Clarion JSON-RPC +**Responsibility:** Minimal binary test plugin that speaks Loomweave JSON-RPC framing over stdin/stdout and gives `PluginHost::spawn` a real subprocess target. **Key Components:** `src/main.rs`, `src/lib.rs`, `Cargo.toml`, and -`crates/clarion-core/tests/fixtures/plugin.toml`. +`crates/loomweave-core/tests/fixtures/plugin.toml`. -**Dependencies:** consumed by core subprocess tests; depends on `clarion-core`, +**Dependencies:** consumed by core subprocess tests; depends on `loomweave-core`, `serde_json`, and Unix-only `nix` features for memory-limit testing. **Patterns Observed:** reuses production framing/protocol types; fails closed @@ -55,11 +55,11 @@ stress coverage is platform-shaped. **Confidence:** High. -## 3. Clarion Storage +## 3. Loomweave Storage -**Location:** `crates/clarion-storage` +**Location:** `crates/loomweave-storage` -**Responsibility:** SQLite persistence for Clarion's graph, runs, findings, +**Responsibility:** SQLite persistence for Loomweave's graph, runs, findings, search indexes, LLM/query caches, writer serialization, and pooled reads. **Key Components:** `migrations/0001_initial_schema.sql`, `src/schema.rs`, @@ -67,7 +67,7 @@ search indexes, LLM/query caches, writer serialization, and pooled reads. `src/cache.rs`, `src/unresolved.rs`. **Dependencies:** consumed by CLI, MCP, and HTTP read paths; depends on -`clarion-core`, `rusqlite`, `deadpool-sqlite`, `tokio`, `serde`, +`loomweave-core`, `rusqlite`, `deadpool-sqlite`, `tokio`, `serde`, `serde_json`, `blake3`, `tracing`, and `thiserror`. **Patterns Observed:** single writer actor serializes all durable mutation; @@ -81,11 +81,11 @@ transactions to commit. **Confidence:** High. -## 4. Clarion CLI, Analyze, Serve, And HTTP Read API +## 4. Loomweave CLI, Analyze, Serve, And HTTP Read API -**Location:** `crates/clarion-cli` +**Location:** `crates/loomweave-cli` -**Responsibility:** Owns the `clarion` binary: project install, analyze +**Responsibility:** Owns the `loomweave` binary: project install, analyze orchestration, pre-ingest secret scanning, MCP stdio serving, and federation HTTP read API. @@ -93,8 +93,8 @@ HTTP read API. `src/secret_scan/*`, `src/serve.rs`, `src/http_read.rs`. **Dependencies:** consumed by operators, CI/E2E scripts, federation consumers, -and MCP clients; depends on `clarion-core`, `clarion-storage`, `clarion-mcp`, -`clarion-scanner`, `clap`, `axum`, `tokio`, `tower`, `rusqlite`, `ignore`, +and MCP clients; depends on `loomweave-core`, `loomweave-storage`, `loomweave-mcp`, +`loomweave-scanner`, `clap`, `axum`, `tokio`, `tower`, `rusqlite`, `ignore`, `serde`, and `xgraph`. **Patterns Observed:** explicit run terminal states; plugin execution isolated @@ -109,9 +109,9 @@ future concerns. **Confidence:** High. -## 5. Clarion Scanner +## 5. Loomweave Scanner -**Location:** `crates/clarion-scanner` +**Location:** `crates/loomweave-scanner` **Responsibility:** Core-owned pre-ingest scanner: detects secret-like byte ranges, returns redacted metadata plus SHA-1 hashes, and applies @@ -133,11 +133,11 @@ only handles `#` comments. **Confidence:** High. -## 6. Clarion MCP Consult Surface +## 6. Loomweave MCP Consult Surface -**Location:** `crates/clarion-mcp` +**Location:** `crates/loomweave-mcp` -**Responsibility:** Exposes Clarion's MCP JSON-RPC/tool surface for code-graph +**Responsibility:** Exposes Loomweave's MCP JSON-RPC/tool surface for code-graph lookup, graph traversal, summaries, inferred calls, subsystem membership, and optional Filigree issue enrichment. @@ -145,7 +145,7 @@ optional Filigree issue enrichment. path, `config.rs`, `filigree.rs`. **Dependencies:** consumed by MCP clients and CLI `serve`; depends on -`clarion-storage`, `clarion-core`, optional Filigree HTTP, `blake3`, `reqwest`, +`loomweave-storage`, `loomweave-core`, optional Filigree HTTP, `blake3`, `reqwest`, `rusqlite`, `serde`, `serde_json`, `serde_norway`, `time`, `tokio`, and `tracing`. @@ -163,14 +163,14 @@ Filigree HTTP error bodies can appear in MCP envelope error strings. **Location:** `plugins/python` -**Responsibility:** Clarion's v1.0 Python language plugin: JSON-RPC stdio +**Responsibility:** Loomweave's v1.0 Python language plugin: JSON-RPC stdio server, AST entity/edge extraction, Pyright-backed call/reference resolution, and fail-soft Wardline compatibility reporting. **Key Components:** `plugin.toml`, `pyproject.toml`, `server.py`, `extractor.py`, `pyright_session.py`, `wardline_probe.py`, `stdout_guard.py`. -**Dependencies:** consumed by Clarion plugin host and discovery; depends on +**Dependencies:** consumed by Loomweave plugin host and discovery; depends on Python stdlib AST/JSON/subprocess/select/pathlib, `packaging`, pinned `pyright==1.1.409`, optional Wardline import, and `pyright-langserver`. @@ -189,7 +189,7 @@ runtime health because plugin tests were not executed in this pass. ## 8. Release, Governance, And Federation Evidence -**Location:** `docs/clarion/1.0`, `docs/clarion/adr`, `docs/federation`, +**Location:** `docs/loomweave/1.0`, `docs/loomweave/adr`, `docs/federation`, `docs/operator`, `.github/workflows`, `scripts`, `tests/e2e`, `tests/perf`. **Responsibility:** Defines the v1.0 release contract, federation HTTP read @@ -200,7 +200,7 @@ publish checks, and scale/perf evidence. ADR-034, federation contracts and fixtures, release workflows, governance guard scripts, E2E/perf artifacts. -**Dependencies:** used by release maintainers, Filigree's `ClarionRegistry`, +**Dependencies:** used by release maintainers, Filigree's `LoomweaveRegistry`, MCP clients, consult-mode agents, and external operators; depends on GitHub Releases/Actions/rulesets, Dependabot, cosign, SLSA generator, cargo/nextest, Python tooling, sqlite3, pyright, and optional Filigree/Wardline. diff --git a/docs/implementation/arch-analysis-2026-05-20-2124/03-diagrams.md b/docs/implementation/arch-analysis-2026-05-20-2124/03-diagrams.md index e464665b..9173b710 100644 --- a/docs/implementation/arch-analysis-2026-05-20-2124/03-diagrams.md +++ b/docs/implementation/arch-analysis-2026-05-20-2124/03-diagrams.md @@ -9,29 +9,29 @@ archive. flowchart LR Operator[Local operator] Agent[Consult-mode agent / MCP client] - Sibling[Sibling Loom products] + Sibling[Sibling Weft products] Provider[Optional LLM providers] Repo[Target repository] - Clarion[Clarion local-first code archaeology] + Loomweave[Loomweave local-first code archaeology] DB[(SQLite graph)] - Operator -->|install / analyze / serve| Clarion - Clarion -->|walks and analyzes| Repo - Clarion -->|stores graph/runs/findings| DB - Agent -->|MCP JSON-RPC| Clarion - Sibling -->|HTTP read API| Clarion - Clarion -->|optional summaries / inferred calls| Provider + Operator -->|install / analyze / serve| Loomweave + Loomweave -->|walks and analyzes| Repo + Loomweave -->|stores graph/runs/findings| DB + Agent -->|MCP JSON-RPC| Loomweave + Sibling -->|HTTP read API| Loomweave + Loomweave -->|optional summaries / inferred calls| Provider ``` ## Container View ```mermaid flowchart TB - CLI[clarion-cli] - Core[clarion-core] - Storage[clarion-storage] - MCP[clarion-mcp] - Scanner[clarion-scanner] + CLI[loomweave-cli] + Core[loomweave-core] + Storage[loomweave-storage] + MCP[loomweave-mcp] + Scanner[loomweave-scanner] Py[Python language plugin] Fixture[Fixture plugin] DB[(SQLite)] @@ -58,14 +58,14 @@ flowchart TB ```mermaid sequenceDiagram participant Op as Operator - participant CLI as clarion analyze + participant CLI as loomweave analyze participant Scan as Secret scanner participant Core as Plugin host participant Py as Python plugin participant Writer as Storage writer actor participant DB as SQLite - Op->>CLI: clarion analyze + Op->>CLI: loomweave analyze CLI->>Scan: pre-ingest source scan alt secret blocks briefing Scan-->>CLI: findings / block summary paths @@ -89,7 +89,7 @@ sequenceDiagram flowchart LR DB[(SQLite graph)] Reader[ReaderPool] - MCP[clarion-mcp tools] + MCP[loomweave-mcp tools] HTTP[HTTP read API] Agent[MCP client] Sibling[Filigree/Wardline consumer] @@ -113,7 +113,7 @@ flowchart TB Source[Target source files] Scanner[Pre-ingest scanner] Plugin[External plugin subprocess] - Host[Clarion plugin host] + Host[Loomweave plugin host] DB[(SQLite)] MCP[MCP stdio] HTTP[HTTP read API] @@ -153,7 +153,7 @@ flowchart LR ## Notes -- Clarion has one durable local graph store. Sibling products enrich Clarion +- Loomweave has one durable local graph store. Sibling products enrich Loomweave through APIs, not shared runtime. - The strongest architectural boundary is the storage writer actor: durable mutation is serialized while reads are pooled. diff --git a/docs/implementation/arch-analysis-2026-05-20-2124/04-final-report.md b/docs/implementation/arch-analysis-2026-05-20-2124/04-final-report.md index d7211ef2..886c09b9 100644 --- a/docs/implementation/arch-analysis-2026-05-20-2124/04-final-report.md +++ b/docs/implementation/arch-analysis-2026-05-20-2124/04-final-report.md @@ -30,23 +30,23 @@ Filigree state. ## Architecture Assessment -Clarion remains aligned with its local-first mission. It can install, analyze, +Loomweave remains aligned with its local-first mission. It can install, analyze, store, and serve without mandatory sibling runtime. Federation integrations are read/enrichment paths, not semantic dependencies. The crate/plugin split is sensible: -- Core contracts and plugin host concerns live in `clarion-core`. -- Durable graph storage lives in `clarion-storage`. -- Operator commands and federation HTTP live in `clarion-cli`. -- Consult-mode MCP lives in `clarion-mcp`. -- Pre-ingest secret detection lives in `clarion-scanner`. +- Core contracts and plugin host concerns live in `loomweave-core`. +- Durable graph storage lives in `loomweave-storage`. +- Operator commands and federation HTTP live in `loomweave-cli`. +- Consult-mode MCP lives in `loomweave-mcp`. +- Pre-ingest secret detection lives in `loomweave-scanner`. - Python language semantics live in `plugins/python`. The main maintainability pressure comes from large files that carry many responsibilities inside otherwise sound crate boundaries. `analyze.rs`, `http_read.rs`, `plugin/host.rs`, `llm_provider.rs`, and -`clarion-mcp/src/lib.rs` should be treated as "touch with tests and local +`loomweave-mcp/src/lib.rs` should be treated as "touch with tests and local factoring" files. ## Strengths @@ -124,7 +124,7 @@ release or explicitly remove it from the release-ready checklist. ## Architectural Decisions To Preserve - Keep federation enrich-only. Do not add a shared runtime, shared registry, or - cross-product mediator to simplify Clarion. + cross-product mediator to simplify Loomweave. - Keep plugin subprocesses untrusted. - Keep scanner-before-LLM as a hard ordering constraint. - Keep storage mutation serialized through the writer actor. diff --git a/docs/implementation/arch-analysis-2026-05-20-2124/05-quality-assessment.md b/docs/implementation/arch-analysis-2026-05-20-2124/05-quality-assessment.md index 580d669a..67ac1a15 100644 --- a/docs/implementation/arch-analysis-2026-05-20-2124/05-quality-assessment.md +++ b/docs/implementation/arch-analysis-2026-05-20-2124/05-quality-assessment.md @@ -2,7 +2,7 @@ ## Overall Quality Posture -Clarion's quality posture is strong for a release candidate: important +Loomweave's quality posture is strong for a release candidate: important boundaries are tested, the source-of-truth ladder is explicit, and most high-risk runtime behavior has focused coverage. Remaining concerns are concentrated in drift-prone duplicated facts, large files, release-gate @@ -20,9 +20,9 @@ mismatches, and a few intentionally permissive or platform-shaped behaviors. ### Concerns -- `crates/clarion-mcp/src/lib.rs`, `crates/clarion-core/src/plugin/host.rs`, - `crates/clarion-cli/src/analyze.rs`, `crates/clarion-core/src/llm_provider.rs`, - and `crates/clarion-cli/src/http_read.rs` are large enough that changes need +- `crates/loomweave-mcp/src/lib.rs`, `crates/loomweave-core/src/plugin/host.rs`, + `crates/loomweave-cli/src/analyze.rs`, `crates/loomweave-core/src/llm_provider.rs`, + and `crates/loomweave-cli/src/http_read.rs` are large enough that changes need local regression tests and careful review. - Release/publish history still contains old arch-analysis concepts. Current work should use this RC1 report and live source, not old H/L labels. diff --git a/docs/implementation/arch-analysis-2026-05-20-2124/06-architect-handover.md b/docs/implementation/arch-analysis-2026-05-20-2124/06-architect-handover.md index 88ec3845..5437d6ff 100644 --- a/docs/implementation/arch-analysis-2026-05-20-2124/06-architect-handover.md +++ b/docs/implementation/arch-analysis-2026-05-20-2124/06-architect-handover.md @@ -5,7 +5,7 @@ This handover is for a system architect or release owner taking over RC1 review. It supersedes the removed `arch-analysis-2026-05-18-1244` snapshot. -Clarion is a local-first code archaeology tool. It analyzes a target repo, +Loomweave is a local-first code archaeology tool. It analyzes a target repo, stores a graph in SQLite, and serves consult-mode agents through MCP and the federation HTTP read API. The RC1 branch is coherent and near release hardening, but not yet release-ready by policy. @@ -41,9 +41,9 @@ from the release checklist. ## Architecture Guardrails -- No shared runtime, shared registry, or mediator across Loom products. -- Clarion must remain useful alone. -- Federation enriches Clarion; it does not define Clarion semantics. +- No shared runtime, shared registry, or mediator across Weft products. +- Loomweave must remain useful alone. +- Federation enriches Loomweave; it does not define Loomweave semantics. - Plugin subprocesses are untrusted. - Source-to-LLM flow stays behind pre-ingest scanning, live-provider opt-in, source-hash verification, and token budgeting. @@ -54,12 +54,12 @@ from the release checklist. | File | Review Rule | |---|---| -| `crates/clarion-cli/src/analyze.rs` | Require focused tests for pipeline/run-state/subsystem changes. | -| `crates/clarion-cli/src/http_read.rs` | Require federation contract tests and security review for auth/path/limits. | -| `crates/clarion-core/src/plugin/host.rs` | Require plugin boundary tests for protocol/path/resource changes. | -| `crates/clarion-core/src/llm_provider.rs` | Require provider/accounting tests for usage, JSONL parsing, live calls. | -| `crates/clarion-mcp/src/lib.rs` | Require MCP envelope/tool tests for response shape or LLM behavior changes. | -| `plugins/python/src/clarion_plugin_python/pyright_session.py` | Require Pyright timeout/cap/target-mapping tests. | +| `crates/loomweave-cli/src/analyze.rs` | Require focused tests for pipeline/run-state/subsystem changes. | +| `crates/loomweave-cli/src/http_read.rs` | Require federation contract tests and security review for auth/path/limits. | +| `crates/loomweave-core/src/plugin/host.rs` | Require plugin boundary tests for protocol/path/resource changes. | +| `crates/loomweave-core/src/llm_provider.rs` | Require provider/accounting tests for usage, JSONL parsing, live calls. | +| `crates/loomweave-mcp/src/lib.rs` | Require MCP envelope/tool tests for response shape or LLM behavior changes. | +| `plugins/python/src/loomweave_plugin_python/pyright_session.py` | Require Pyright timeout/cap/target-mapping tests. | ## Immediate Work Queue diff --git a/docs/implementation/arch-analysis-2026-05-20-2124/07-security-surface.md b/docs/implementation/arch-analysis-2026-05-20-2124/07-security-surface.md index 6efc3fde..647c9cd6 100644 --- a/docs/implementation/arch-analysis-2026-05-20-2124/07-security-surface.md +++ b/docs/implementation/arch-analysis-2026-05-20-2124/07-security-surface.md @@ -2,7 +2,7 @@ ## Security Posture Summary -Clarion has a credible local-first security posture: it treats plugins as +Loomweave has a credible local-first security posture: it treats plugins as untrusted subprocesses, scans source before LLM exposure, constrains HTTP read access, and keeps sibling products optional. The main security risks are local implementation choices that need discipline: hand-rolled HMAC, unauthenticated @@ -13,8 +13,8 @@ inherent in canonicalization-based path jails. | Boundary | Control | |---|---| -| Target repository to Clarion | Secret scanning before ingest; path normalization; ignore handling; briefing-blocked file semantics. | -| Clarion host to plugin subprocess | Content-Length JSON-RPC; manifest validation; executable basename validation; frame caps; path jail; field caps; Linux process limits. | +| Target repository to Loomweave | Secret scanning before ingest; path normalization; ignore handling; briefing-blocked file semantics. | +| Loomweave host to plugin subprocess | Content-Length JSON-RPC; manifest validation; executable basename validation; frame caps; path jail; field caps; Linux process limits. | | Plugin output to storage | Entity/edge validation; reserved core ontology enforcement; malformed outputs become findings or breaker conditions. | | Storage to MCP/HTTP | Typed query helpers; closed envelopes; traversal/path protections; briefing-blocked non-disclosure. | | MCP to external LLM provider | Live-provider opt-in; source-hash checks; excerpt/range limiting; token budget; cache accounting. | @@ -36,7 +36,7 @@ inherent in canonicalization-based path jails. ### Hand-Rolled HTTP HMAC -`crates/clarion-cli/src/http_read.rs` implements HMAC behavior locally with +`crates/loomweave-cli/src/http_read.rs` implements HMAC behavior locally with `sha2`. This increases review burden. The length-mismatch fast return in constant-time comparison is probably not catastrophic if request parsing and exact-length signatures are enforced, but the code should be treated as @@ -67,7 +67,7 @@ The path jail relies on canonicalization. The source explicitly acknowledges a race between proof and later open. **Recommendation:** acceptable for local-first RC1 if documented, but revisit -if Clarion is ever exposed to hostile multi-user repositories or remote read +if Loomweave is ever exposed to hostile multi-user repositories or remote read surfaces. ### LLM Empty Excerpt Behavior diff --git a/docs/implementation/arch-analysis-2026-05-20-2124/10-dependency-analysis.md b/docs/implementation/arch-analysis-2026-05-20-2124/10-dependency-analysis.md index e5d0c9b9..bcb2c250 100644 --- a/docs/implementation/arch-analysis-2026-05-20-2124/10-dependency-analysis.md +++ b/docs/implementation/arch-analysis-2026-05-20-2124/10-dependency-analysis.md @@ -4,12 +4,12 @@ ```mermaid flowchart LR - Core[clarion-core] - Storage[clarion-storage] - CLI[clarion-cli] - MCP[clarion-mcp] - Scanner[clarion-scanner] - Fixture[clarion-plugin-fixture] + Core[loomweave-core] + Storage[loomweave-storage] + CLI[loomweave-cli] + MCP[loomweave-mcp] + Scanner[loomweave-scanner] + Fixture[loomweave-plugin-fixture] Py[plugins/python] Storage --> Core @@ -25,16 +25,16 @@ flowchart LR ## Internal Coupling Notes -- `clarion-core` is the contract root. Changes to plugin protocol, entity IDs, +- `loomweave-core` is the contract root. Changes to plugin protocol, entity IDs, resource limits, or LLM provider abstractions can affect most of the workspace. -- `clarion-storage` depends on core ontology and is consumed by CLI, MCP, and +- `loomweave-storage` depends on core ontology and is consumed by CLI, MCP, and HTTP read paths. -- `clarion-cli` is the orchestration hub and therefore depends on almost every +- `loomweave-cli` is the orchestration hub and therefore depends on almost every Rust crate. -- `clarion-mcp` depends on storage/core and has optional outward coupling to +- `loomweave-mcp` depends on storage/core and has optional outward coupling to Filigree HTTP. -- `clarion-scanner` is intentionally small and mostly leaf-like. +- `loomweave-scanner` is intentionally small and mostly leaf-like. - `plugins/python` is coupled through process protocol and manifest metadata rather than Rust linking. diff --git a/docs/implementation/comprehensive-readonly-audit-2026-06-04-5-agent-current-main.md b/docs/implementation/comprehensive-readonly-audit-2026-06-04-5-agent-current-main.md index 9a215f1a..4a0017d3 100644 --- a/docs/implementation/comprehensive-readonly-audit-2026-06-04-5-agent-current-main.md +++ b/docs/implementation/comprehensive-readonly-audit-2026-06-04-5-agent-current-main.md @@ -1,6 +1,6 @@ -# Clarion Comprehensive Read-Only Audit - 2026-06-04 - 5 Agent Current Main +# Loomweave Comprehensive Read-Only Audit - 2026-06-04 - 5 Agent Current Main -Repository: `/home/john/clarion` +Repository: `/home/john/loomweave` Branch observed at session start: `main...origin/main` @@ -12,7 +12,7 @@ Verification: source inspection only. No build, test, formatter, scanner, server ## Reviewer Coverage -- Architecture Critic: crate boundaries, Loom doctrine, federation coupling, SARIF and Filigree integration paths. +- Architecture Critic: crate boundaries, Weft doctrine, federation coupling, SARIF and Filigree integration paths. - Systems Thinker: analyze lifecycle, failure propagation, stale graph risks, MCP analyze supervision. - Rust and Python Engineer: plugin host, Python extractor/Pyright integration, JSON-RPC/LSP framing, implementation idioms. - Quality Engineer: release gates, CI coverage, migration guard, perf harness coverage. @@ -24,9 +24,9 @@ Verification: source inspection only. No build, test, formatter, scanner, server Locations: -- [/home/john/clarion/scripts/check-migration-retirement.py:70](/home/john/clarion/scripts/check-migration-retirement.py:70), lines 70-74 -- [/home/john/clarion/.github/workflows/release.yml:65](/home/john/clarion/.github/workflows/release.yml:65), lines 65-68 -- `/home/john/clarion/crates/clarion-storage/migrations/published_build.txt`, absent on the audited checkout +- [/home/john/loomweave/scripts/check-migration-retirement.py:70](/home/john/loomweave/scripts/check-migration-retirement.py:70), lines 70-74 +- [/home/john/loomweave/.github/workflows/release.yml:65](/home/john/loomweave/.github/workflows/release.yml:65), lines 65-68 +- `/home/john/loomweave/crates/loomweave-storage/migrations/published_build.txt`, absent on the audited checkout Evidence: the release workflow invokes `scripts/check-migration-retirement.py`, but the guard returns success when `published_build.txt` is absent, printing that the policy is still pre-trigger. The marker file is absent in the current checkout. @@ -38,7 +38,7 @@ Remediation: - Invoke strict mode from the release workflow on tag pushes. - Keep the current permissive behavior only for pre-release/local development if needed. - Add self-tests for absent marker, empty marker, missing ref, matching migration, and changed migration. -- Commit `crates/clarion-storage/migrations/published_build.txt` at the intended published tag-cut baseline. +- Commit `crates/loomweave-storage/migrations/published_build.txt` at the intended published tag-cut baseline. Acceptance test: run the release verify guard in tag/release mode without `published_build.txt`; it must fail before build or publish jobs can run. @@ -48,11 +48,11 @@ Acceptance test: run the release verify guard in tag/release mode without `publi Locations: -- [/home/john/clarion/crates/clarion-storage/src/writer.rs:1](/home/john/clarion/crates/clarion-storage/src/writer.rs:1), lines 1-8 -- [/home/john/clarion/crates/clarion-storage/migrations/0001_initial_schema.sql:31](/home/john/clarion/crates/clarion-storage/migrations/0001_initial_schema.sql:31), lines 31-55 -- [/home/john/clarion/crates/clarion-storage/migrations/0001_initial_schema.sql:81](/home/john/clarion/crates/clarion-storage/migrations/0001_initial_schema.sql:81), lines 81-97 -- [/home/john/clarion/crates/clarion-cli/src/analyze.rs:1440](/home/john/clarion/crates/clarion-cli/src/analyze.rs:1440), lines 1440-1486 -- [/home/john/clarion/crates/clarion-storage/src/writer.rs:1276](/home/john/clarion/crates/clarion-storage/src/writer.rs:1276), lines 1276-1303 +- [/home/john/loomweave/crates/loomweave-storage/src/writer.rs:1](/home/john/loomweave/crates/loomweave-storage/src/writer.rs:1), lines 1-8 +- [/home/john/loomweave/crates/loomweave-storage/migrations/0001_initial_schema.sql:31](/home/john/loomweave/crates/loomweave-storage/migrations/0001_initial_schema.sql:31), lines 31-55 +- [/home/john/loomweave/crates/loomweave-storage/migrations/0001_initial_schema.sql:81](/home/john/loomweave/crates/loomweave-storage/migrations/0001_initial_schema.sql:81), lines 81-97 +- [/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:1440](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:1440), lines 1440-1486 +- [/home/john/loomweave/crates/loomweave-storage/src/writer.rs:1276](/home/john/loomweave/crates/loomweave-storage/src/writer.rs:1276), lines 1276-1303 Evidence: the writer commits batches during a run, and `entities`/`edges` are cumulative tables without a visible generation scope. Soft-failed runs intentionally commit healthy-plugin entities and edges while marking the run failed. Hard-fail rollback only covers the currently open transaction, not previously committed batches. @@ -71,10 +71,10 @@ Acceptance test: analyze a fixture, run a second analyze that commits one batch Locations: -- [/home/john/clarion/crates/clarion-federation/src/config.rs:346](/home/john/clarion/crates/clarion-federation/src/config.rs:346), lines 346-374 -- [/home/john/clarion/crates/clarion-cli/src/http_read/auth.rs:97](/home/john/clarion/crates/clarion-cli/src/http_read/auth.rs:97), lines 97-103 -- [/home/john/clarion/crates/clarion-cli/src/http_read.rs:141](/home/john/clarion/crates/clarion-cli/src/http_read.rs:141), lines 141-153 -- [/home/john/clarion/crates/clarion-cli/src/http_read.rs:302](/home/john/clarion/crates/clarion-cli/src/http_read.rs:302), lines 302-309 +- [/home/john/loomweave/crates/loomweave-federation/src/config.rs:346](/home/john/loomweave/crates/loomweave-federation/src/config.rs:346), lines 346-374 +- [/home/john/loomweave/crates/loomweave-cli/src/http_read/auth.rs:97](/home/john/loomweave/crates/loomweave-cli/src/http_read/auth.rs:97), lines 97-103 +- [/home/john/loomweave/crates/loomweave-cli/src/http_read.rs:141](/home/john/loomweave/crates/loomweave-cli/src/http_read.rs:141), lines 141-153 +- [/home/john/loomweave/crates/loomweave-cli/src/http_read.rs:302](/home/john/loomweave/crates/loomweave-cli/src/http_read.rs:302), lines 302-309 Evidence: `validate_auth_trust` explicitly returns success for loopback binds even when no bearer token or HMAC secret is configured. The auth middleware falls through to the route when both secrets are absent. Startup warns that any local process can read the catalogue. The route group can also include Wardline taint writes when `wardline_taint_write` is enabled. @@ -93,10 +93,10 @@ Acceptance test: start the HTTP read API on `127.0.0.1` with no token or identit Locations: -- [/home/john/clarion/crates/clarion-cli/src/sarif.rs:91](/home/john/clarion/crates/clarion-cli/src/sarif.rs:91), lines 91-93 -- [/home/john/clarion/crates/clarion-cli/src/sarif.rs:124](/home/john/clarion/crates/clarion-cli/src/sarif.rs:124), lines 124-137 -- [/home/john/clarion/crates/clarion-cli/src/sarif.rs:188](/home/john/clarion/crates/clarion-cli/src/sarif.rs:188), lines 188-205 -- [/home/john/clarion/crates/clarion-cli/src/sarif.rs:221](/home/john/clarion/crates/clarion-cli/src/sarif.rs:221), lines 221-226 +- [/home/john/loomweave/crates/loomweave-cli/src/sarif.rs:91](/home/john/loomweave/crates/loomweave-cli/src/sarif.rs:91), lines 91-93 +- [/home/john/loomweave/crates/loomweave-cli/src/sarif.rs:124](/home/john/loomweave/crates/loomweave-cli/src/sarif.rs:124), lines 124-137 +- [/home/john/loomweave/crates/loomweave-cli/src/sarif.rs:188](/home/john/loomweave/crates/loomweave-cli/src/sarif.rs:188), lines 188-205 +- [/home/john/loomweave/crates/loomweave-cli/src/sarif.rs:221](/home/john/loomweave/crates/loomweave-cli/src/sarif.rs:221), lines 221-226 Evidence: `normalize_sarif_uri` relativizes absolute `file://` URIs under `project_root`, but preserves absolute paths outside `project_root`. The test suite currently asserts preservation of `/tmp/other/src/a.py`. The preserved path is sent as the finding `path` in the Filigree scan-results request. @@ -116,21 +116,21 @@ Acceptance test: importing SARIF with `file:///tmp/other/src/a.py` against `/hom Locations: -- [/home/john/clarion/crates/clarion-mcp/src/analyze_runs.rs:1](/home/john/clarion/crates/clarion-mcp/src/analyze_runs.rs:1), lines 1-16 -- [/home/john/clarion/crates/clarion-mcp/src/tools/analyze.rs:169](/home/john/clarion/crates/clarion-mcp/src/tools/analyze.rs:169), lines 169-187 -- [/home/john/clarion/crates/clarion-mcp/src/tools/analyze.rs:232](/home/john/clarion/crates/clarion-mcp/src/tools/analyze.rs:232), lines 232-248 -- [/home/john/clarion/crates/clarion-mcp/src/lib.rs:3541](/home/john/clarion/crates/clarion-mcp/src/lib.rs:3541), lines 3541-3550 -- [/home/john/clarion/crates/clarion-storage/src/runs.rs:7](/home/john/clarion/crates/clarion-storage/src/runs.rs:7), lines 7-19 +- [/home/john/loomweave/crates/loomweave-mcp/src/analyze_runs.rs:1](/home/john/loomweave/crates/loomweave-mcp/src/analyze_runs.rs:1), lines 1-16 +- [/home/john/loomweave/crates/loomweave-mcp/src/tools/analyze.rs:169](/home/john/loomweave/crates/loomweave-mcp/src/tools/analyze.rs:169), lines 169-187 +- [/home/john/loomweave/crates/loomweave-mcp/src/tools/analyze.rs:232](/home/john/loomweave/crates/loomweave-mcp/src/tools/analyze.rs:232), lines 232-248 +- [/home/john/loomweave/crates/loomweave-mcp/src/lib.rs:3541](/home/john/loomweave/crates/loomweave-mcp/src/lib.rs:3541), lines 3541-3550 +- [/home/john/loomweave/crates/loomweave-storage/src/runs.rs:7](/home/john/loomweave/crates/loomweave-storage/src/runs.rs:7), lines 7-19 Evidence: MCP supervision lives in an in-memory registry. The module explicitly says supervising-process crash reconciliation is out of scope. If a run is absent from the registry, status/cancel fall back to DB terminal status handling; `map_run_status` maps any DB status other than completed/skipped/cancelled to failed. Storage can mark stale rows abandoned only after a 24-hour heartbeat window. -Impact: if `clarion serve` dies while a child `clarion analyze` continues running, a restarted MCP server cannot adopt, cancel, or accurately report the live child. Operators lose observability and control even though the analyze process may still own the lock and continue writing. +Impact: if `loomweave serve` dies while a child `loomweave analyze` continues running, a restarted MCP server cannot adopt, cancel, or accurately report the live child. Operators lose observability and control even though the analyze process may still own the lock and continue writing. Remediation: - Persist supervision metadata: process group id, owner pid, progress path, command identity, project root, and heartbeat. - On `serve` startup and `analyze_status`, distinguish `running_owned`, `running_orphaned`, `stale_running`, and terminal failed states. -- Allow safe cancellation of orphaned runs only after validating the persisted process group still belongs to the recorded Clarion analyze command/project. +- Allow safe cancellation of orphaned runs only after validating the persisted process group still belongs to the recorded Loomweave analyze command/project. - Lower or make configurable the stale heartbeat threshold for interactive MCP status, while preserving conservative repair for portability. Acceptance test: start an MCP analyze, terminate `serve` while the child continues, restart `serve`, and assert `analyze_status` reports an orphaned live run and `analyze_cancel` can terminate it safely. @@ -139,11 +139,11 @@ Acceptance test: start an MCP analyze, terminate `serve` while the child continu Locations: -- [/home/john/clarion/.github/workflows/release.yml:11](/home/john/clarion/.github/workflows/release.yml:11), lines 11-15 -- [/home/john/clarion/.github/workflows/release.yml:309](/home/john/clarion/.github/workflows/release.yml:309), lines 309-315 -- [/home/john/clarion/.github/workflows/release.yml:329](/home/john/clarion/.github/workflows/release.yml:329), lines 329-361 -- [/home/john/clarion/.github/workflows/release.yml:376](/home/john/clarion/.github/workflows/release.yml:376), lines 376-423 -- [/home/john/clarion/docs/operator/v1.0-release-governance.md:186](/home/john/clarion/docs/operator/v1.0-release-governance.md:186), lines 186-196 +- [/home/john/loomweave/.github/workflows/release.yml:11](/home/john/loomweave/.github/workflows/release.yml:11), lines 11-15 +- [/home/john/loomweave/.github/workflows/release.yml:309](/home/john/loomweave/.github/workflows/release.yml:309), lines 309-315 +- [/home/john/loomweave/.github/workflows/release.yml:329](/home/john/loomweave/.github/workflows/release.yml:329), lines 329-361 +- [/home/john/loomweave/.github/workflows/release.yml:376](/home/john/loomweave/.github/workflows/release.yml:376), lines 376-423 +- [/home/john/loomweave/docs/operator/v1.0-release-governance.md:186](/home/john/loomweave/docs/operator/v1.0-release-governance.md:186), lines 186-196 Evidence: the operator governance doc requires a manual `release.yml` run before tag creation. The workflow's manual dispatch path validates verify/build/artifact plumbing, but the actual `release` job is gated to tag pushes. Signing, signature verification, release notes generation, and GitHub Release creation therefore remain unexercised by the documented dry run. @@ -164,9 +164,9 @@ Acceptance test: run `workflow_dispatch` with a candidate tag/ref and assert the Locations: -- [/home/john/clarion/crates/clarion-mcp/src/lib.rs:2188](/home/john/clarion/crates/clarion-mcp/src/lib.rs:2188), lines 2188-2195 -- [/home/john/clarion/crates/clarion-mcp/src/lib.rs:2222](/home/john/clarion/crates/clarion-mcp/src/lib.rs:2222), lines 2222-2239 -- [/home/john/clarion/crates/clarion-core/src/plugin/limits.rs:70](/home/john/clarion/crates/clarion-core/src/plugin/limits.rs:70), lines 70-71 +- [/home/john/loomweave/crates/loomweave-mcp/src/lib.rs:2188](/home/john/loomweave/crates/loomweave-mcp/src/lib.rs:2188), lines 2188-2195 +- [/home/john/loomweave/crates/loomweave-mcp/src/lib.rs:2222](/home/john/loomweave/crates/loomweave-mcp/src/lib.rs:2222), lines 2222-2239 +- [/home/john/loomweave/crates/loomweave-core/src/plugin/limits.rs:70](/home/john/loomweave/crates/loomweave-core/src/plugin/limits.rs:70), lines 70-71 Evidence: stdio frames beginning with `{`, `[`, or whitespace are routed to JSON-line compatibility mode. `read_json_line_frame` calls `read_until` into a `Vec` with no byte ceiling. Content-Length framing has an 8 MiB ceiling, but JSON-line framing does not. @@ -185,8 +185,8 @@ Acceptance test: feed a JSON-line request larger than the configured cap; `read_ Locations: -- [/home/john/clarion/plugins/python/src/clarion_plugin_python/pyright_session.py:879](/home/john/clarion/plugins/python/src/clarion_plugin_python/pyright_session.py:879), lines 879-902 -- [/home/john/clarion/crates/clarion-core/src/plugin/transport.rs:166](/home/john/clarion/crates/clarion-core/src/plugin/transport.rs:166), lines 166-175 +- [/home/john/loomweave/plugins/python/src/loomweave_plugin_python/pyright_session.py:879](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/pyright_session.py:879), lines 879-902 +- [/home/john/loomweave/crates/loomweave-core/src/plugin/transport.rs:166](/home/john/loomweave/crates/loomweave-core/src/plugin/transport.rs:166), lines 166-175 Evidence: `_read_message` reads Pyright headers, parses `Content-Length` with `int`, reads that many bytes, and passes the body to `json.loads`. It has no maximum content length and does not turn malformed/negative/oversized length or invalid JSON into a controlled poisoned-transport path. The Rust host/plugin transport has explicit size validation. @@ -205,10 +205,10 @@ Acceptance test: a fake Pyright server returning `Content-Length: 999999999` mus Locations: -- [/home/john/clarion/plugins/python/src/clarion_plugin_python/pyright_session.py:606](/home/john/clarion/plugins/python/src/clarion_plugin_python/pyright_session.py:606), lines 606-628 -- [/home/john/clarion/plugins/python/src/clarion_plugin_python/pyright_session.py:917](/home/john/clarion/plugins/python/src/clarion_plugin_python/pyright_session.py:917), lines 917-923 -- [/home/john/clarion/plugins/python/src/clarion_plugin_python/pyright_session.py:931](/home/john/clarion/plugins/python/src/clarion_plugin_python/pyright_session.py:931), lines 931-940 -- [/home/john/clarion/plugins/python/src/clarion_plugin_python/server.py:197](/home/john/clarion/plugins/python/src/clarion_plugin_python/server.py:197), lines 197-201 +- [/home/john/loomweave/plugins/python/src/loomweave_plugin_python/pyright_session.py:606](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/pyright_session.py:606), lines 606-628 +- [/home/john/loomweave/plugins/python/src/loomweave_plugin_python/pyright_session.py:917](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/pyright_session.py:917), lines 917-923 +- [/home/john/loomweave/plugins/python/src/loomweave_plugin_python/pyright_session.py:931](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/pyright_session.py:931), lines 931-940 +- [/home/john/loomweave/plugins/python/src/loomweave_plugin_python/server.py:197](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/server.py:197), lines 197-201 Evidence: `server.py` catches `OSError` and `UnicodeDecodeError` when reading the directly analyzed file. Pyright target resolution calls `_function_index_for_path` for referenced internal files, and that helper performs `read_text(encoding="utf-8")` without the same containment. @@ -227,14 +227,14 @@ Acceptance test: analyzing `a.py` that references `b.py`, where `b.py` is invali Locations: -- [/home/john/clarion/crates/clarion-federation/src/filigree_url.rs:53](/home/john/clarion/crates/clarion-federation/src/filigree_url.rs:53), lines 53-87 -- [/home/john/clarion/crates/clarion-federation/src/filigree_url.rs:89](/home/john/clarion/crates/clarion-federation/src/filigree_url.rs:89), lines 89-97 -- [/home/john/clarion/crates/clarion-cli/src/serve.rs:48](/home/john/clarion/crates/clarion-cli/src/serve.rs:48), lines 48-63 -- [/home/john/clarion/crates/clarion-cli/src/analyze.rs:3477](/home/john/clarion/crates/clarion-cli/src/analyze.rs:3477), lines 3477-3484 +- [/home/john/loomweave/crates/loomweave-federation/src/filigree_url.rs:53](/home/john/loomweave/crates/loomweave-federation/src/filigree_url.rs:53), lines 53-87 +- [/home/john/loomweave/crates/loomweave-federation/src/filigree_url.rs:89](/home/john/loomweave/crates/loomweave-federation/src/filigree_url.rs:89), lines 89-97 +- [/home/john/loomweave/crates/loomweave-cli/src/serve.rs:48](/home/john/loomweave/crates/loomweave-cli/src/serve.rs:48), lines 48-63 +- [/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:3477](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:3477), lines 3477-3484 Evidence: Filigree URL resolution prefers `/.filigree/ephemeral.port` over the configured port. The port file is read from the target project tree and used by both `serve` and `analyze` when building Filigree clients. -Impact: when Clarion analyzes an untrusted project with Filigree enabled and a token configured, project-local content can redirect bearer-authenticated calls to an attacker-chosen local port. The connection stays loopback, but the token and request body can still be exposed to a local listener. +Impact: when Loomweave analyzes an untrusted project with Filigree enabled and a token configured, project-local content can redirect bearer-authenticated calls to an attacker-chosen local port. The connection stays loopback, but the token and request body can still be exposed to a local listener. Remediation: @@ -243,44 +243,44 @@ Remediation: - Consider ignoring repo-contained `.filigree/ephemeral.port` when analyzing untrusted projects unless an explicit flag enables ethereal discovery. - Record the resolved URL source in emission stats for every outbound call. -Acceptance test: place `.filigree/ephemeral.port` in a project with unsafe permissions or mismatched ownership; Clarion must ignore it or fail closed before sending authenticated requests. +Acceptance test: place `.filigree/ephemeral.port` in a project with unsafe permissions or mismatched ownership; Loomweave must ignore it or fail closed before sending authenticated requests. ### M-05: SARIF import bypasses the live Filigree endpoint resolver Locations: -- [/home/john/clarion/crates/clarion-cli/src/sarif.rs:17](/home/john/clarion/crates/clarion-cli/src/sarif.rs:17), lines 17-25 -- [/home/john/clarion/crates/clarion-cli/src/serve.rs:48](/home/john/clarion/crates/clarion-cli/src/serve.rs:48), lines 48-63 -- [/home/john/clarion/crates/clarion-cli/src/analyze.rs:3477](/home/john/clarion/crates/clarion-cli/src/analyze.rs:3477), lines 3477-3484 +- [/home/john/loomweave/crates/loomweave-cli/src/sarif.rs:17](/home/john/loomweave/crates/loomweave-cli/src/sarif.rs:17), lines 17-25 +- [/home/john/loomweave/crates/loomweave-cli/src/serve.rs:48](/home/john/loomweave/crates/loomweave-cli/src/serve.rs:48), lines 48-63 +- [/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:3477](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:3477), lines 3477-3484 Evidence: `serve` and `analyze` call `resolve_filigree_url` and prefer the live ethereal port. `sarif.rs` builds `FiligreeHttpClient` directly from the static config. -Impact: `clarion sarif import` is the least reliable federation path in dogfood/ethereal mode. It can post to a stale configured port even when the live dashboard port is available. +Impact: `loomweave sarif import` is the least reliable federation path in dogfood/ethereal mode. It can post to a stale configured port even when the live dashboard port is available. Remediation: -- Use `clarion_federation::filigree_url::resolve_filigree_url` in `sarif.rs` before constructing the client. +- Use `loomweave_federation::filigree_url::resolve_filigree_url` in `sarif.rs` before constructing the client. - Surface the configured URL, resolved URL, and source in logs/errors. - Share outbound Filigree client construction between analyze and SARIF import to avoid drift. -Acceptance test: with config base URL on a stale port and `.filigree/ephemeral.port` pointing to a test Filigree server, `clarion sarif import` should post to the resolved live endpoint. +Acceptance test: with config base URL on a stale port and `.filigree/ephemeral.port` pointing to a test Filigree server, `loomweave sarif import` should post to the resolved live endpoint. ### M-06: Root-level added source files can produce a false fresh index verdict Locations: -- [/home/john/clarion/crates/clarion-mcp/src/snapshot.rs:24](/home/john/clarion/crates/clarion-mcp/src/snapshot.rs:24), lines 24-35 -- [/home/john/clarion/crates/clarion-mcp/src/snapshot.rs:383](/home/john/clarion/crates/clarion-mcp/src/snapshot.rs:383), lines 383-398 -- [/home/john/clarion/crates/clarion-mcp/src/snapshot.rs:412](/home/john/clarion/crates/clarion-mcp/src/snapshot.rs:412), lines 412-430 +- [/home/john/loomweave/crates/loomweave-mcp/src/snapshot.rs:24](/home/john/loomweave/crates/loomweave-mcp/src/snapshot.rs:24), lines 24-35 +- [/home/john/loomweave/crates/loomweave-mcp/src/snapshot.rs:383](/home/john/loomweave/crates/loomweave-mcp/src/snapshot.rs:383), lines 383-398 +- [/home/john/loomweave/crates/loomweave-mcp/src/snapshot.rs:412](/home/john/loomweave/crates/loomweave-mcp/src/snapshot.rs:412), lines 412-430 Evidence: snapshot freshness watches direct parent directories of already ingested files but deliberately excludes the project root. It also checks only files already present in `entities.source_file_path`. A new top-level source file is neither an existing entity nor in a watched parent directory. -Impact: `clarion://context` and session-start freshness can report fresh while the graph is missing a new top-level source file. +Impact: `loomweave://context` and session-start freshness can report fresh while the graph is missing a new top-level source file. Remediation: - Store analyzed source roots or an analyzed file inventory. -- Watch root-level source additions while explicitly excluding `.clarion/` and other generated directories. +- Watch root-level source additions while explicitly excluding `.loomweave/` and other generated directories. - Or compute freshness from the same ignore/plugin-extension walk used by analyze, bounded and reported as truncated when necessary. Acceptance test: after analyzing a project with nested Python files, add `new_top_level.py` at the project root; freshness should report stale. @@ -289,9 +289,9 @@ Acceptance test: after analyzing a project with nested Python files, add `new_to Locations: -- [/home/john/clarion/crates/clarion-storage/src/query.rs:884](/home/john/clarion/crates/clarion-storage/src/query.rs:884), lines 884-925 -- [/home/john/clarion/crates/clarion-cli/src/http_read/linkages.rs:132](/home/john/clarion/crates/clarion-cli/src/http_read/linkages.rs:132), lines 132-150 -- [/home/john/clarion/crates/clarion-mcp/src/tools/graph.rs:340](/home/john/clarion/crates/clarion-mcp/src/tools/graph.rs:340), lines 340-357 +- [/home/john/loomweave/crates/loomweave-storage/src/query.rs:884](/home/john/loomweave/crates/loomweave-storage/src/query.rs:884), lines 884-925 +- [/home/john/loomweave/crates/loomweave-cli/src/http_read/linkages.rs:132](/home/john/loomweave/crates/loomweave-cli/src/http_read/linkages.rs:132), lines 132-150 +- [/home/john/loomweave/crates/loomweave-mcp/src/tools/graph.rs:340](/home/john/loomweave/crates/loomweave-mcp/src/tools/graph.rs:340), lines 340-357 Evidence: for `max_confidence >= ambiguous`, `call_edges_targeting` selects every ambiguous `calls` edge with properties and filters `candidate_ids` in Rust. HTTP and MCP callers materialize full vectors before aggregating/filtering/paginating. @@ -309,14 +309,14 @@ Acceptance test: seed many ambiguous call edges with only one candidate pointing Locations: -- [/home/john/clarion/crates/clarion-cli/src/analyze.rs:1120](/home/john/clarion/crates/clarion-cli/src/analyze.rs:1120), lines 1120-1146 -- [/home/john/clarion/crates/clarion-cli/src/analyze.rs:1220](/home/john/clarion/crates/clarion-cli/src/analyze.rs:1220), lines 1220-1229 -- [/home/john/clarion/crates/clarion-cli/src/analyze.rs:1477](/home/john/clarion/crates/clarion-cli/src/analyze.rs:1477), lines 1477-1486 -- [/home/john/clarion/crates/clarion-storage/src/writer.rs:1168](/home/john/clarion/crates/clarion-storage/src/writer.rs:1168), lines 1168-1183 +- [/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:1120](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:1120), lines 1120-1146 +- [/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:1220](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:1220), lines 1220-1229 +- [/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:1477](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:1477), lines 1477-1486 +- [/home/john/loomweave/crates/loomweave-storage/src/writer.rs:1168](/home/john/loomweave/crates/loomweave-storage/src/writer.rs:1168), lines 1168-1183 Evidence: Phase 8 emits findings to Filigree before `CommitRun` so the emission outcome can be stored in `stats.json`. The local terminal update and final commit can still fail after an external POST succeeds. -Impact: Filigree can hold findings for a Clarion run that did not durably reach the matching terminal state. This does not make Filigree load-bearing, but it propagates stale/inconsistent state outward. +Impact: Filigree can hold findings for a Loomweave run that did not durably reach the matching terminal state. This does not make Filigree load-bearing, but it propagates stale/inconsistent state outward. Remediation: @@ -330,9 +330,9 @@ Acceptance test: force `CommitRun` failure after a successful Filigree POST in a Locations: -- [/home/john/clarion/tests/perf/b8_scale_test/test_driver.py:5](/home/john/clarion/tests/perf/b8_scale_test/test_driver.py:5), lines 5-17 -- [/home/john/clarion/.github/workflows/ci.yml:180](/home/john/clarion/.github/workflows/ci.yml:180), lines 180-181 -- [/home/john/clarion/.github/workflows/ci.yml:192](/home/john/clarion/.github/workflows/ci.yml:192), lines 192-193 +- [/home/john/loomweave/tests/perf/b8_scale_test/test_driver.py:5](/home/john/loomweave/tests/perf/b8_scale_test/test_driver.py:5), lines 5-17 +- [/home/john/loomweave/.github/workflows/ci.yml:180](/home/john/loomweave/.github/workflows/ci.yml:180), lines 180-181 +- [/home/john/loomweave/.github/workflows/ci.yml:192](/home/john/loomweave/.github/workflows/ci.yml:192), lines 192-193 Evidence: `tests/perf/b8_scale_test/test_driver.py` is a pytest suite for the B8 driver. CI runs the B4/B5 performance gate script and `pytest plugins/python`, but does not visibly run `pytest tests/perf/b8_scale_test`. @@ -352,12 +352,12 @@ Acceptance test: intentionally break `driver.parse_tool_response`; CI should fai Locations: -- [/home/john/clarion/plugins/python/src/clarion_plugin_python/server.py:66](/home/john/clarion/plugins/python/src/clarion_plugin_python/server.py:66), lines 66-97 -- [/home/john/clarion/crates/clarion-core/src/plugin/transport.rs:150](/home/john/clarion/crates/clarion-core/src/plugin/transport.rs:150), lines 150-175 +- [/home/john/loomweave/plugins/python/src/loomweave_plugin_python/server.py:66](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/server.py:66), lines 66-97 +- [/home/john/loomweave/crates/loomweave-core/src/plugin/transport.rs:150](/home/john/loomweave/crates/loomweave-core/src/plugin/transport.rs:150), lines 150-175 Evidence: the Python plugin caps body size but reads header lines until a blank line without a header count, header line length, or total header byte cap. The Rust host transport has stricter frame validation for the normal host-to-plugin boundary. -Impact: direct invocation of the Python plugin can consume memory on oversized headers. Normal Clarion host traffic is bounded, so this is a hardening issue rather than a primary product exploit. +Impact: direct invocation of the Python plugin can consume memory on oversized headers. Normal Loomweave host traffic is bounded, so this is a hardening issue rather than a primary product exploit. Remediation: @@ -369,26 +369,26 @@ Remediation: Locations: -- [/home/john/clarion/crates/clarion-cli/src/analyze.rs:2929](/home/john/clarion/crates/clarion-cli/src/analyze.rs:2929), lines 2929-2932 -- [/home/john/clarion/crates/clarion-cli/src/analyze.rs:4178](/home/john/clarion/crates/clarion-cli/src/analyze.rs:4178), lines 4178-4185 -- [/home/john/clarion/crates/clarion-cli/src/analyze.rs:4400](/home/john/clarion/crates/clarion-cli/src/analyze.rs:4400), lines 4400-4408 +- [/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:2929](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:2929), lines 2929-2932 +- [/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:4178](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:4178), lines 4178-4185 +- [/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:4400](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:4400), lines 4400-4408 -Evidence: `run_plugin_blocking` is generic over plugin execution, but per-file timeout findings use `CLA-PY-TIMEOUT`. +Evidence: `run_plugin_blocking` is generic over plugin execution, but per-file timeout findings use `LMWV-PY-TIMEOUT`. Impact: acceptable for the current Python-only v1 release line, but future non-Python plugins would be misclassified under a Python rule ID. Remediation: -- Rename the host-side timeout to `CLA-INFRA-PLUGIN-TIMEOUT`, or derive a plugin/language-specific rule from the manifest with a generic fallback. +- Rename the host-side timeout to `LMWV-INFRA-PLUGIN-TIMEOUT`, or derive a plugin/language-specific rule from the manifest with a generic fallback. - Keep the Python-specific rule only for Python-plugin-owned timeout facts. ### L-03: Filigree federation client is blocking and forces async callers into thread wrappers Locations: -- [/home/john/clarion/crates/clarion-federation/src/filigree.rs:270](/home/john/clarion/crates/clarion-federation/src/filigree.rs:270), lines 270-275 -- [/home/john/clarion/crates/clarion-cli/src/analyze.rs:3486](/home/john/clarion/crates/clarion-cli/src/analyze.rs:3486), lines 3486-3501 -- [/home/john/clarion/crates/clarion-cli/src/sarif.rs:160](/home/john/clarion/crates/clarion-cli/src/sarif.rs:160), lines 160-166 +- [/home/john/loomweave/crates/loomweave-federation/src/filigree.rs:270](/home/john/loomweave/crates/loomweave-federation/src/filigree.rs:270), lines 270-275 +- [/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:3486](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:3486), lines 3486-3501 +- [/home/john/loomweave/crates/loomweave-cli/src/sarif.rs:160](/home/john/loomweave/crates/loomweave-cli/src/sarif.rs:160), lines 160-166 Evidence: `FiligreeHttpClient` owns a `reqwest::blocking::Client`. Async callers work around nested-runtime panics by moving request lifecycle into extra OS threads. @@ -404,7 +404,7 @@ Remediation: - Plugin host boundary: manifest size checks, executable basename validation, path jail, content-length ceiling, stderr ring buffer, entity/edge/finding cap accounting, path-escape breaker, timeout watchdog, and host-finding persistence are present. - Rust storage basics: writer actor, WAL-oriented reader pool, schema versioning tests, generated columns, summary cache, unresolved call sites, inferred edge cache, and migration tests have broad coverage. -- Python extractor basics: the requested `scanner/ast_primitives.py` path does not exist; the equivalent AST implementation is `plugins/python/src/clarion_plugin_python/extractor.py`. The plugin currently has structured entity/tag extraction, duplicate-definition suppression, `@overload` skipping, syntax-error degradation, and stdout guarding. +- Python extractor basics: the requested `scanner/ast_primitives.py` path does not exist; the equivalent AST implementation is `plugins/python/src/loomweave_plugin_python/extractor.py`. The plugin currently has structured entity/tag extraction, duplicate-definition suppression, `@overload` skipping, syntax-error degradation, and stdout guarding. - Wardline posture: the Python plugin no longer imports `wardline.core.registry`, and `plugins/python/plugin.toml` declares `wardline_aware = false`. Wardline-derived guidance and taint storage are explicit integration paths rather than hidden plugin startup coupling. - HTTP route input handling: route body limits, HMAC replay protection, authorization log scrubbing, project-root path canonicalization, and SQL parameter binding were reviewed and did not produce additional findings beyond the auth-default and SARIF issues above. - LLM provider surface: no separate LLM judge surface was found. Provider paths cover OpenRouter and CLI-provider parsing, malformed structured output, retryability, MCP summary budget/cache behavior, and live-provider opt-in. diff --git a/docs/implementation/comprehensive-readonly-audit-2026-06-04-clarion-7-agent.md b/docs/implementation/comprehensive-readonly-audit-2026-06-04-loomweave-7-agent.md similarity index 57% rename from docs/implementation/comprehensive-readonly-audit-2026-06-04-clarion-7-agent.md rename to docs/implementation/comprehensive-readonly-audit-2026-06-04-loomweave-7-agent.md index 19f9cc6f..91c65441 100644 --- a/docs/implementation/comprehensive-readonly-audit-2026-06-04-clarion-7-agent.md +++ b/docs/implementation/comprehensive-readonly-audit-2026-06-04-loomweave-7-agent.md @@ -1,16 +1,16 @@ -# Clarion Comprehensive Read-Only Audit - 2026-06-04 +# Loomweave Comprehensive Read-Only Audit - 2026-06-04 -Repository: `/home/john/clarion` +Repository: `/home/john/loomweave` Branch observed at session start: `ws6-guidance-maturity...origin/ws6-guidance-maturity`, ahead by 1. Initial tracked tree was clean. A later `git status` showed unrelated documentation changes; this report did not modify those files. Mode: read-only audit plus this requested markdown artifact. Seven specialist subagents were dispatched as read-only explorers and instructed to avoid MCP tools, write tools, file edits, generated files, formatting, installs, staging, commits, and mutating commands. The subagent tool schema did not expose literal `enable_write_tools=false` or `enable_mcp_tools=false` fields, so those constraints were enforced in each subagent prompt. -Verification: source inspection only. No build, test, clippy, mypy, pytest, ruff, cargo metadata, or audit commands were run because they can write caches or artifacts. One parent `project_status_get` call against the Clarion dogfood MCP timed out and was not used as evidence. +Verification: source inspection only. No build, test, clippy, mypy, pytest, ruff, cargo metadata, or audit commands were run because they can write caches or artifacts. One parent `project_status_get` call against the Loomweave dogfood MCP timed out and was not used as evidence. ## Reviewer Coverage -- Architecture Critic: crate boundaries, Loom doctrine, plugin ontology, federation/storage coupling. +- Architecture Critic: crate boundaries, Weft doctrine, plugin ontology, federation/storage coupling. - Systems Thinker: graph lifecycle, feedback loops, freshness, read/write boundaries. - Python Engineer: Python plugin extractor, Pyright integration, typing and Python idioms. - Quality Engineer: CI/release gates, test structure, perf evidence, coverage posture. @@ -28,9 +28,9 @@ None found. Locations: -- [/home/john/clarion/crates/clarion-storage/migrations/0001_initial_schema.sql:81](/home/john/clarion/crates/clarion-storage/migrations/0001_initial_schema.sql:81), lines 81-97 -- [/home/john/clarion/docs/clarion/adr/ADR-026-containment-wire-and-edge-identity.md:85](/home/john/clarion/docs/clarion/adr/ADR-026-containment-wire-and-edge-identity.md:85), lines 85-91 -- [/home/john/clarion/crates/clarion-storage/src/writer.rs:738](/home/john/clarion/crates/clarion-storage/src/writer.rs:738), lines 738-789 +- [/home/john/loomweave/crates/loomweave-storage/migrations/0001_initial_schema.sql:81](/home/john/loomweave/crates/loomweave-storage/migrations/0001_initial_schema.sql:81), lines 81-97 +- [/home/john/loomweave/docs/loomweave/adr/ADR-026-containment-wire-and-edge-identity.md:85](/home/john/loomweave/docs/loomweave/adr/ADR-026-containment-wire-and-edge-identity.md:85), lines 85-91 +- [/home/john/loomweave/crates/loomweave-storage/src/writer.rs:738](/home/john/loomweave/crates/loomweave-storage/src/writer.rs:738), lines 738-789 The `edges` table primary key is `(kind, from_id, to_id)`, and `insert_edge` uses `INSERT OR IGNORE`. This makes re-analysis idempotent, but it does not retract edges that disappeared from source, and it does not update metadata when the same triple gets a new source range, confidence, or properties. @@ -47,11 +47,11 @@ Remediation: Locations: -- [/home/john/clarion/crates/clarion-storage/migrations/0007_run_analyzed_commit.sql:1](/home/john/clarion/crates/clarion-storage/migrations/0007_run_analyzed_commit.sql:1), lines 1-13 -- [/home/john/clarion/crates/clarion-mcp/src/index_diff.rs:203](/home/john/clarion/crates/clarion-mcp/src/index_diff.rs:203), lines 203-250 -- [/home/john/clarion/crates/clarion-mcp/src/tools/status.rs:282](/home/john/clarion/crates/clarion-mcp/src/tools/status.rs:282), lines 282-287 +- [/home/john/loomweave/crates/loomweave-storage/migrations/0007_run_analyzed_commit.sql:1](/home/john/loomweave/crates/loomweave-storage/migrations/0007_run_analyzed_commit.sql:1), lines 1-13 +- [/home/john/loomweave/crates/loomweave-mcp/src/index_diff.rs:203](/home/john/loomweave/crates/loomweave-mcp/src/index_diff.rs:203), lines 203-250 +- [/home/john/loomweave/crates/loomweave-mcp/src/tools/status.rs:282](/home/john/loomweave/crates/loomweave-mcp/src/tools/status.rs:282), lines 282-287 -Clarion persists `runs.analyzed_at_commit`, but MCP freshness and status surfaces still describe `analyzed_commit`/`git_sha` as null and rely on weaker time-based heuristics. +Loomweave persists `runs.analyzed_at_commit`, but MCP freshness and status surfaces still describe `analyzed_commit`/`git_sha` as null and rely on weaker time-based heuristics. Impact: branch switches or checkouts to older commits can leave the index stale without being reported as stale. Agents may trust an index built against a different commit. @@ -66,9 +66,9 @@ Remediation: Locations: -- [/home/john/clarion/crates/clarion-cli/src/http_read/linkages.rs:78](/home/john/clarion/crates/clarion-cli/src/http_read/linkages.rs:78), lines 78-88 -- [/home/john/clarion/docs/clarion/adr/ADR-028-edge-confidence-tiers.md:90](/home/john/clarion/docs/clarion/adr/ADR-028-edge-confidence-tiers.md:90), lines 90-96 -- [/home/john/clarion/docs/federation/contracts.md:343](/home/john/clarion/docs/federation/contracts.md:343), lines 343-345 +- [/home/john/loomweave/crates/loomweave-cli/src/http_read/linkages.rs:78](/home/john/loomweave/crates/loomweave-cli/src/http_read/linkages.rs:78), lines 78-88 +- [/home/john/loomweave/docs/loomweave/adr/ADR-028-edge-confidence-tiers.md:90](/home/john/loomweave/docs/loomweave/adr/ADR-028-edge-confidence-tiers.md:90), lines 90-96 +- [/home/john/loomweave/docs/federation/contracts.md:343](/home/john/loomweave/docs/federation/contracts.md:343), lines 343-345 `parse_max_confidence(None)` defaults to `all`, which admits inferred edges. ADR-028 makes resolved-only the safe default for graph traversal, with weaker tiers opt-in. The HTTP contract currently documents the lower-precedence default as `all`. @@ -85,11 +85,11 @@ Remediation: Locations: -- [/home/john/clarion/crates/clarion-mcp/src/lib.rs:297](/home/john/clarion/crates/clarion-mcp/src/lib.rs:297), lines 297-327 -- [/home/john/clarion/crates/clarion-mcp/src/lib.rs:355](/home/john/clarion/crates/clarion-mcp/src/lib.rs:355), lines 355-382 -- [/home/john/clarion/crates/clarion-mcp/src/tools/analyze.rs:40](/home/john/clarion/crates/clarion-mcp/src/tools/analyze.rs:40), lines 40-75 +- [/home/john/loomweave/crates/loomweave-mcp/src/lib.rs:297](/home/john/loomweave/crates/loomweave-mcp/src/lib.rs:297), lines 297-327 +- [/home/john/loomweave/crates/loomweave-mcp/src/lib.rs:355](/home/john/loomweave/crates/loomweave-mcp/src/lib.rs:355), lines 355-382 +- [/home/john/loomweave/crates/loomweave-mcp/src/tools/analyze.rs:40](/home/john/loomweave/crates/loomweave-mcp/src/tools/analyze.rs:40), lines 40-75 -The MCP server advertises mutating or process-spawning tools such as `analyze_start`, `analyze_cancel`, `propose_guidance`, and `promote_guidance`. There is no Clarion config gate that lets an operator expose only read-only MCP tools. +The MCP server advertises mutating or process-spawning tools such as `analyze_start`, `analyze_cancel`, `propose_guidance`, and `promote_guidance`. There is no Loomweave config gate that lets an operator expose only read-only MCP tools. Impact: a deployment that intends read-only consult mode cannot enforce that boundary at the server. Clients also cannot reliably distinguish pure read tools from tools that spawn processes, write SQLite, create observations, or invalidate summaries. @@ -104,8 +104,8 @@ Remediation: Locations: -- [/home/john/clarion/plugins/python/src/clarion_plugin_python/extractor.py:863](/home/john/clarion/plugins/python/src/clarion_plugin_python/extractor.py:863), lines 863-940 -- [/home/john/clarion/plugins/python/src/clarion_plugin_python/pyright_session.py:1000](/home/john/clarion/plugins/python/src/clarion_plugin_python/pyright_session.py:1000), lines 1000-1058 +- [/home/john/loomweave/plugins/python/src/loomweave_plugin_python/extractor.py:863](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/extractor.py:863), lines 863-940 +- [/home/john/loomweave/plugins/python/src/loomweave_plugin_python/pyright_session.py:1000](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/pyright_session.py:1000), lines 1000-1058 The extractor skips `@overload` stubs before emitting the implementation entity, but `_collect_entities` in `pyright_session.py` does not apply the same policy. It records the first duplicate entity ID, which can be the overload stub. @@ -121,11 +121,11 @@ Remediation: Locations: -- [/home/john/clarion/plugins/python/src/clarion_plugin_python/pyright_session.py:247](/home/john/clarion/plugins/python/src/clarion_plugin_python/pyright_session.py:247), lines 247-252 -- [/home/john/clarion/plugins/python/src/clarion_plugin_python/pyright_session.py:308](/home/john/clarion/plugins/python/src/clarion_plugin_python/pyright_session.py:308), lines 308-320 -- [/home/john/clarion/plugins/python/src/clarion_plugin_python/pyright_session.py:938](/home/john/clarion/plugins/python/src/clarion_plugin_python/pyright_session.py:938), lines 938-951 -- [/home/john/clarion/plugins/python/src/clarion_plugin_python/server.py:218](/home/john/clarion/plugins/python/src/clarion_plugin_python/server.py:218), lines 218-230 -- [/home/john/clarion/crates/clarion-core/src/plugin/protocol.rs:364](/home/john/clarion/crates/clarion-core/src/plugin/protocol.rs:364), lines 364-397 +- [/home/john/loomweave/plugins/python/src/loomweave_plugin_python/pyright_session.py:247](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/pyright_session.py:247), lines 247-252 +- [/home/john/loomweave/plugins/python/src/loomweave_plugin_python/pyright_session.py:308](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/pyright_session.py:308), lines 308-320 +- [/home/john/loomweave/plugins/python/src/loomweave_plugin_python/pyright_session.py:938](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/pyright_session.py:938), lines 938-951 +- [/home/john/loomweave/plugins/python/src/loomweave_plugin_python/server.py:218](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/server.py:218), lines 218-230 +- [/home/john/loomweave/crates/loomweave-core/src/plugin/protocol.rs:364](/home/john/loomweave/crates/loomweave-core/src/plugin/protocol.rs:364), lines 364-397 Pyright records findings for timeout, cap, and process degradation paths, but `AnalyzeFileResult`/server response does not carry those findings to the host. @@ -135,21 +135,21 @@ Remediation: - Extend `AnalyzeFileResult` or `AnalyzeFileStats` with bounded plugin findings. - Validate size and schema in the host. -- Persist them as `CLA-PY-*` findings with file/entity anchors. +- Persist them as `LMWV-PY-*` findings with file/entity anchors. - Add tests for Pyright unavailable, timeout, cap-exceeded, and poisoned-process paths. ### H-07: Source files are scanned and dispatched before a jail-safe open Locations: -- [/home/john/clarion/crates/clarion-cli/src/analyze.rs:4946](/home/john/clarion/crates/clarion-cli/src/analyze.rs:4946), lines 4946-4972 -- [/home/john/clarion/crates/clarion-cli/src/secret_scan.rs:393](/home/john/clarion/crates/clarion-cli/src/secret_scan.rs:393), lines 393-399 -- [/home/john/clarion/crates/clarion-core/src/plugin/host.rs:859](/home/john/clarion/crates/clarion-core/src/plugin/host.rs:859), lines 859-874 -- [/home/john/clarion/crates/clarion-core/src/plugin/jail.rs:86](/home/john/clarion/crates/clarion-core/src/plugin/jail.rs:86), lines 86-129 +- [/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:4946](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:4946), lines 4946-4972 +- [/home/john/loomweave/crates/loomweave-cli/src/secret_scan.rs:393](/home/john/loomweave/crates/loomweave-cli/src/secret_scan.rs:393), lines 393-399 +- [/home/john/loomweave/crates/loomweave-core/src/plugin/host.rs:859](/home/john/loomweave/crates/loomweave-core/src/plugin/host.rs:859), lines 859-874 +- [/home/john/loomweave/crates/loomweave-core/src/plugin/jail.rs:86](/home/john/loomweave/crates/loomweave-core/src/plugin/jail.rs:86), lines 86-129 The walker disables symlink following, but later secret scanning reads `fs::read(file)`, and plugin dispatch sends the path to the plugin before returned source paths are jail-checked. A safer `safe_open` helper exists but is not used before scanner reads or plugin dispatch. -Impact: in a writable workspace, a symlink swap after walking can make Clarion read or hand a plugin an out-of-tree file before later output validation rejects paths. +Impact: in a writable workspace, a symlink swap after walking can make Loomweave read or hand a plugin an out-of-tree file before later output validation rejects paths. Remediation: @@ -164,9 +164,9 @@ Remediation: Locations: -- [/home/john/clarion/crates/clarion-cli/src/http_read/linkages.rs:133](/home/john/clarion/crates/clarion-cli/src/http_read/linkages.rs:133), lines 133-151 -- [/home/john/clarion/crates/clarion-cli/src/http_read/linkages.rs:204](/home/john/clarion/crates/clarion-cli/src/http_read/linkages.rs:204), lines 204-214 -- [/home/john/clarion/docs/federation/contracts.md:390](/home/john/clarion/docs/federation/contracts.md:390), lines 390-398 +- [/home/john/loomweave/crates/loomweave-cli/src/http_read/linkages.rs:133](/home/john/loomweave/crates/loomweave-cli/src/http_read/linkages.rs:133), lines 133-151 +- [/home/john/loomweave/crates/loomweave-cli/src/http_read/linkages.rs:204](/home/john/loomweave/crates/loomweave-cli/src/http_read/linkages.rs:204), lines 204-214 +- [/home/john/loomweave/docs/federation/contracts.md:390](/home/john/loomweave/docs/federation/contracts.md:390), lines 390-398 The handlers check the queried entity for `briefing_blocked` but do not filter returned neighbor IDs. The contract explicitly says neighbors are not filtered. @@ -183,8 +183,8 @@ Remediation: Locations: -- [/home/john/clarion/plugins/python/src/clarion_plugin_python/pyright_session.py:1018](/home/john/clarion/plugins/python/src/clarion_plugin_python/pyright_session.py:1018), lines 1018-1026 -- [/home/john/clarion/plugins/python/src/clarion_plugin_python/pyright_session.py:1066](/home/john/clarion/plugins/python/src/clarion_plugin_python/pyright_session.py:1066), lines 1066-1074 +- [/home/john/loomweave/plugins/python/src/loomweave_plugin_python/pyright_session.py:1018](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/pyright_session.py:1018), lines 1018-1026 +- [/home/john/loomweave/plugins/python/src/loomweave_plugin_python/pyright_session.py:1066](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/pyright_session.py:1066), lines 1066-1074 `line_text.find(child.name)` can match text before the declaration name, such as the `f` in `def f():`. @@ -199,8 +199,8 @@ Remediation: Locations: -- [/home/john/clarion/plugins/python/src/clarion_plugin_python/pyright_session.py:500](/home/john/clarion/plugins/python/src/clarion_plugin_python/pyright_session.py:500), lines 500-531 -- [/home/john/clarion/plugins/python/src/clarion_plugin_python/pyright_session.py:1130](/home/john/clarion/plugins/python/src/clarion_plugin_python/pyright_session.py:1130), lines 1130-1135 +- [/home/john/loomweave/plugins/python/src/loomweave_plugin_python/pyright_session.py:500](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/pyright_session.py:500), lines 500-531 +- [/home/john/loomweave/plugins/python/src/loomweave_plugin_python/pyright_session.py:1130](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/pyright_session.py:1130), lines 1130-1135 The cache key is `(from_id, kind, lexeme)`, omitting line/character or binding identity. @@ -216,7 +216,7 @@ Remediation: Location: -- [/home/john/clarion/plugins/python/src/clarion_plugin_python/pyright_session.py:1539](/home/john/clarion/plugins/python/src/clarion_plugin_python/pyright_session.py:1539), lines 1539-1549 +- [/home/john/loomweave/plugins/python/src/loomweave_plugin_python/pyright_session.py:1539](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/pyright_session.py:1539), lines 1539-1549 `_containing_function_id` scans `index.functions` in insertion order. Since `_collect_entities` inserts outer functions before nested functions, fallback can return the outer function for a range inside a nested callable. @@ -232,7 +232,7 @@ Remediation: Location: -- [/home/john/clarion/crates/clarion-cli/src/http_read/wardline.rs:233](/home/john/clarion/crates/clarion-cli/src/http_read/wardline.rs:233), lines 233-267 +- [/home/john/loomweave/crates/loomweave-cli/src/http_read/wardline.rs:233](/home/john/loomweave/crates/loomweave-cli/src/http_read/wardline.rs:233), lines 233-267 The write path resolves locator SEIs, then lets `fact.sei` win if supplied. @@ -248,7 +248,7 @@ Remediation: Location: -- [/home/john/clarion/crates/clarion-cli/src/sarif.rs:91](/home/john/clarion/crates/clarion-cli/src/sarif.rs:91), lines 91-103 +- [/home/john/loomweave/crates/loomweave-cli/src/sarif.rs:91](/home/john/loomweave/crates/loomweave-cli/src/sarif.rs:91), lines 91-103 URI stripping checks `file://` before `file:///` and then trims all leading slashes. @@ -265,10 +265,10 @@ Remediation: Locations: -- [/home/john/clarion/crates/clarion-storage/src/reader.rs:1](/home/john/clarion/crates/clarion-storage/src/reader.rs:1), lines 1-7 -- [/home/john/clarion/crates/clarion-storage/src/reader.rs:134](/home/john/clarion/crates/clarion-storage/src/reader.rs:134), lines 134-145 -- [/home/john/clarion/crates/clarion-mcp/src/tools/status.rs:173](/home/john/clarion/crates/clarion-mcp/src/tools/status.rs:173), lines 173-199 -- [/home/john/clarion/crates/clarion-mcp/src/tools/analyze.rs:253](/home/john/clarion/crates/clarion-mcp/src/tools/analyze.rs:253), lines 253-272 +- [/home/john/loomweave/crates/loomweave-storage/src/reader.rs:1](/home/john/loomweave/crates/loomweave-storage/src/reader.rs:1), lines 1-7 +- [/home/john/loomweave/crates/loomweave-storage/src/reader.rs:134](/home/john/loomweave/crates/loomweave-storage/src/reader.rs:134), lines 134-145 +- [/home/john/loomweave/crates/loomweave-mcp/src/tools/status.rs:173](/home/john/loomweave/crates/loomweave-mcp/src/tools/status.rs:173), lines 173-199 +- [/home/john/loomweave/crates/loomweave-mcp/src/tools/analyze.rs:253](/home/john/loomweave/crates/loomweave-mcp/src/tools/analyze.rs:253), lines 253-272 Status/read paths can mark stale running analyses failed through a pool documented as read-only. @@ -280,15 +280,15 @@ Remediation: - Or rename/split the pool to document and isolate write-capable maintenance reads. - Add tests around the intended mutation boundary. -### M-08: ReaderPool serve validation does not enforce Clarion database identity +### M-08: ReaderPool serve validation does not enforce Loomweave database identity Locations: -- [/home/john/clarion/crates/clarion-storage/src/pragma.rs:26](/home/john/clarion/crates/clarion-storage/src/pragma.rs:26), lines 26-42 -- [/home/john/clarion/crates/clarion-storage/src/reader.rs:81](/home/john/clarion/crates/clarion-storage/src/reader.rs:81), lines 81-91 -- [/home/john/clarion/crates/clarion-cli/src/serve.rs:70](/home/john/clarion/crates/clarion-cli/src/serve.rs:70), lines 70-74 +- [/home/john/loomweave/crates/loomweave-storage/src/pragma.rs:26](/home/john/loomweave/crates/loomweave-storage/src/pragma.rs:26), lines 26-42 +- [/home/john/loomweave/crates/loomweave-storage/src/reader.rs:81](/home/john/loomweave/crates/loomweave-storage/src/reader.rs:81), lines 81-91 +- [/home/john/loomweave/crates/loomweave-cli/src/serve.rs:70](/home/john/loomweave/crates/loomweave-cli/src/serve.rs:70), lines 70-74 -Write-side startup enforces SQLite `application_id`, but `clarion serve` reader validation checks only schema readability/version, not database identity. +Write-side startup enforces SQLite `application_id`, but `loomweave serve` reader validation checks only schema readability/version, not database identity. Impact: a mispointed or foreign SQLite file can pass serve startup and later fail as confusing empty or broken queries. @@ -302,28 +302,28 @@ Remediation: Locations: -- [/home/john/clarion/crates/clarion-cli/src/main.rs:1](/home/john/clarion/crates/clarion-cli/src/main.rs:1), lines 1-22 -- [/home/john/clarion/crates/clarion-cli/src/analyze.rs:24](/home/john/clarion/crates/clarion-cli/src/analyze.rs:24), lines 24-48 -- [/home/john/clarion/crates/clarion-cli/src/clustering.rs:8](/home/john/clarion/crates/clarion-cli/src/clustering.rs:8), lines 8-58 +- [/home/john/loomweave/crates/loomweave-cli/src/main.rs:1](/home/john/loomweave/crates/loomweave-cli/src/main.rs:1), lines 1-22 +- [/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:24](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:24), lines 24-48 +- [/home/john/loomweave/crates/loomweave-cli/src/clustering.rs:8](/home/john/loomweave/crates/loomweave-cli/src/clustering.rs:8), lines 8-58 -`clarion-cli` privately owns analysis orchestration, clustering, config mapping, and HTTP serving glue. +`loomweave-cli` privately owns analysis orchestration, clustering, config mapping, and HTTP serving glue. -Impact: other surfaces must shell out to `clarion analyze` instead of linking reusable analysis logic. Boundaries become harder to defend as Clarion adds languages and surfaces. +Impact: other surfaces must shell out to `loomweave analyze` instead of linking reusable analysis logic. Boundaries become harder to defend as Loomweave adds languages and surfaces. Remediation: -- Extract analysis orchestration and clustering into a library crate such as `clarion-analyze` or a clearly bounded `clarion-core::analysis`. -- Leave `clarion-cli` as a thin command wrapper. +- Extract analysis orchestration and clustering into a library crate such as `loomweave-analyze` or a clearly bounded `loomweave-core::analysis`. +- Leave `loomweave-cli` as a thin command wrapper. - Move HTTP read serving to a crate whose ownership matches the served API surface. ### M-10: Plugin ontology boundary is weakened by hardcoded kind semantics Locations: -- [/home/john/clarion/crates/clarion-cli/src/analyze.rs:119](/home/john/clarion/crates/clarion-cli/src/analyze.rs:119), lines 119-127 -- [/home/john/clarion/crates/clarion-cli/src/analyze.rs:4730](/home/john/clarion/crates/clarion-cli/src/analyze.rs:4730), lines 4730-4755 -- [/home/john/clarion/crates/clarion-cli/src/analyze.rs:4799](/home/john/clarion/crates/clarion-cli/src/analyze.rs:4799), lines 4799-4828 -- [/home/john/clarion/crates/clarion-mcp/src/lib.rs:2854](/home/john/clarion/crates/clarion-mcp/src/lib.rs:2854), lines 2854-2863 +- [/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:119](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:119), lines 119-127 +- [/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:4730](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:4730), lines 4730-4755 +- [/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:4799](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:4799), lines 4799-4828 +- [/home/john/loomweave/crates/loomweave-mcp/src/lib.rs:2854](/home/john/loomweave/crates/loomweave-mcp/src/lib.rs:2854), lines 2854-2863 Rust-side paths still treat names such as `module` and `function` as special semantic buckets, even though requirements put ontology ownership at the plugin boundary. @@ -339,26 +339,26 @@ Remediation: Locations: -- [/home/john/clarion/crates/clarion-federation/Cargo.toml:12](/home/john/clarion/crates/clarion-federation/Cargo.toml:12), lines 12-16 -- [/home/john/clarion/crates/clarion-federation/src/scan_results.rs:13](/home/john/clarion/crates/clarion-federation/src/scan_results.rs:13), lines 13-17 -- [/home/john/clarion/crates/clarion-federation/src/scan_results.rs:92](/home/john/clarion/crates/clarion-federation/src/scan_results.rs:92), lines 92-101 +- [/home/john/loomweave/crates/loomweave-federation/Cargo.toml:12](/home/john/loomweave/crates/loomweave-federation/Cargo.toml:12), lines 12-16 +- [/home/john/loomweave/crates/loomweave-federation/src/scan_results.rs:13](/home/john/loomweave/crates/loomweave-federation/src/scan_results.rs:13), lines 13-17 +- [/home/john/loomweave/crates/loomweave-federation/src/scan_results.rs:92](/home/john/loomweave/crates/loomweave-federation/src/scan_results.rs:92), lines 92-101 -`clarion-federation` consumes `clarion_storage::FindingForEmitRow` directly. +`loomweave-federation` consumes `loomweave_storage::FindingForEmitRow` directly. Impact: storage projection refactors cross a federation boundary unnecessarily, making the external contract layer depend on persistence internals. Remediation: -- Define federation DTOs in `clarion-federation`. -- Map storage rows to DTOs in `clarion-cli` or `clarion-storage`. +- Define federation DTOs in `loomweave-federation`. +- Map storage rows to DTOs in `loomweave-cli` or `loomweave-storage`. - Keep storage schema evolution behind the storage crate. ### M-12: Release required-check guard does not require the macOS CI job Locations: -- [/home/john/clarion/.github/workflows/ci.yml:103](/home/john/clarion/.github/workflows/ci.yml:103), lines 103-141 -- [/home/john/clarion/scripts/check-github-release-governance.py:44](/home/john/clarion/scripts/check-github-release-governance.py:44), lines 44-50 +- [/home/john/loomweave/.github/workflows/ci.yml:103](/home/john/loomweave/.github/workflows/ci.yml:103), lines 103-141 +- [/home/john/loomweave/scripts/check-github-release-governance.py:44](/home/john/loomweave/scripts/check-github-release-governance.py:44), lines 44-50 CI defines a macOS Rust job to catch platform-specific `-D warnings`, but the release governance guard only requires `Rust`, `Python plugin`, and `Sprint 1 walking skeleton (end-to-end)`. @@ -374,9 +374,9 @@ Remediation: Locations: -- [/home/john/clarion/.github/workflows/ci.yml:161](/home/john/clarion/.github/workflows/ci.yml:161), lines 161-162 -- [/home/john/clarion/scripts/check-b4-gate-result.py:41](/home/john/clarion/scripts/check-b4-gate-result.py:41), lines 41-59 -- [/home/john/clarion/tests/perf/b5_reference_scale_smoke.py:153](/home/john/clarion/tests/perf/b5_reference_scale_smoke.py:153), lines 153-258 +- [/home/john/loomweave/.github/workflows/ci.yml:161](/home/john/loomweave/.github/workflows/ci.yml:161), lines 161-162 +- [/home/john/loomweave/scripts/check-b4-gate-result.py:41](/home/john/loomweave/scripts/check-b4-gate-result.py:41), lines 41-59 +- [/home/john/loomweave/tests/perf/b5_reference_scale_smoke.py:153](/home/john/loomweave/tests/perf/b5_reference_scale_smoke.py:153), lines 153-258 CI checks freshness of prior B.4 evidence but does not run even a synthetic perf corpus. @@ -392,10 +392,10 @@ Remediation: Locations: -- [/home/john/clarion/.github/workflows/ci.yml:149](/home/john/clarion/.github/workflows/ci.yml:149), lines 149-182 -- [/home/john/clarion/.github/workflows/release.yml:124](/home/john/clarion/.github/workflows/release.yml:124), lines 124-137 -- [/home/john/clarion/.github/workflows/release.yml:248](/home/john/clarion/.github/workflows/release.yml:248), lines 248-260 -- [/home/john/clarion/deny.toml:3](/home/john/clarion/deny.toml:3), lines 3-7 +- [/home/john/loomweave/.github/workflows/ci.yml:149](/home/john/loomweave/.github/workflows/ci.yml:149), lines 149-182 +- [/home/john/loomweave/.github/workflows/release.yml:124](/home/john/loomweave/.github/workflows/release.yml:124), lines 124-137 +- [/home/john/loomweave/.github/workflows/release.yml:248](/home/john/loomweave/.github/workflows/release.yml:248), lines 248-260 +- [/home/john/loomweave/deny.toml:3](/home/john/loomweave/deny.toml:3), lines 3-7 Rust dependencies are covered by `cargo deny`, but Python CI/release installs from PyPI via pip without lock enforcement, hash checking, or Python vulnerability audit. @@ -411,10 +411,10 @@ Remediation: Locations: -- [/home/john/clarion/crates/clarion-mcp/src/lib.rs:811](/home/john/clarion/crates/clarion-mcp/src/lib.rs:811), lines 811-814 -- [/home/john/clarion/crates/clarion-mcp/src/lib.rs:2035](/home/john/clarion/crates/clarion-mcp/src/lib.rs:2035), line 2035 -- [/home/john/clarion/crates/clarion-mcp/src/tools/summary.rs:380](/home/john/clarion/crates/clarion-mcp/src/tools/summary.rs:380), line 380 -- [/home/john/clarion/crates/clarion-mcp/src/tools/summary.rs:597](/home/john/clarion/crates/clarion-mcp/src/tools/summary.rs:597), line 597 +- [/home/john/loomweave/crates/loomweave-mcp/src/lib.rs:811](/home/john/loomweave/crates/loomweave-mcp/src/lib.rs:811), lines 811-814 +- [/home/john/loomweave/crates/loomweave-mcp/src/lib.rs:2035](/home/john/loomweave/crates/loomweave-mcp/src/lib.rs:2035), line 2035 +- [/home/john/loomweave/crates/loomweave-mcp/src/tools/summary.rs:380](/home/john/loomweave/crates/loomweave-mcp/src/tools/summary.rs:380), line 380 +- [/home/john/loomweave/crates/loomweave-mcp/src/tools/summary.rs:597](/home/john/loomweave/crates/loomweave-mcp/src/tools/summary.rs:597), line 597 The server drops JSON-RPC notifications before method handling and processes stdio frames sequentially, while summary/inferred paths can await long LLM calls. @@ -431,7 +431,7 @@ Remediation: Location: -- [/home/john/clarion/crates/clarion-mcp/src/lib.rs:2235](/home/john/clarion/crates/clarion-mcp/src/lib.rs:2235), lines 2235-2244 +- [/home/john/loomweave/crates/loomweave-mcp/src/lib.rs:2235](/home/john/loomweave/crates/loomweave-mcp/src/lib.rs:2235), lines 2235-2244 `arguments.get("confidence").and_then(Value::as_str)` treats `null`, numbers, arrays, and objects like an omitted field and defaults to resolved. @@ -448,8 +448,8 @@ Remediation: Locations: -- [/home/john/clarion/plugins/python/src/clarion_plugin_python/extractor.py:501](/home/john/clarion/plugins/python/src/clarion_plugin_python/extractor.py:501), lines 501-511 -- [/home/john/clarion/plugins/python/src/clarion_plugin_python/extractor.py:579](/home/john/clarion/plugins/python/src/clarion_plugin_python/extractor.py:579), lines 579-586 +- [/home/john/loomweave/plugins/python/src/loomweave_plugin_python/extractor.py:501](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/extractor.py:501), lines 501-511 +- [/home/john/loomweave/plugins/python/src/loomweave_plugin_python/extractor.py:579](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/extractor.py:579), lines 579-586 The predicate treats any boolean expression containing `TYPE_CHECKING` as type-only. @@ -463,14 +463,14 @@ Remediation: ## Low -### L-01: HTTP tracing records raw `X-Loom-Component` authentication material +### L-01: HTTP tracing records raw `X-Weft-Component` authentication material Locations: -- [/home/john/clarion/crates/clarion-cli/src/http_read.rs:609](/home/john/clarion/crates/clarion-cli/src/http_read.rs:609), lines 609-632 -- [/home/john/clarion/crates/clarion-cli/src/http_read/auth.rs:135](/home/john/clarion/crates/clarion-cli/src/http_read/auth.rs:135), lines 135-141 +- [/home/john/loomweave/crates/loomweave-cli/src/http_read.rs:609](/home/john/loomweave/crates/loomweave-cli/src/http_read.rs:609), lines 609-632 +- [/home/john/loomweave/crates/loomweave-cli/src/http_read/auth.rs:135](/home/john/loomweave/crates/loomweave-cli/src/http_read/auth.rs:135), lines 135-141 -The request span records the raw `x-loom-component` header. For HMAC auth, that header includes the signature. +The request span records the raw `x-weft-component` header. For HMAC auth, that header includes the signature. Impact: logs can contain signed request material. Timestamp and nonce are separate, but combined logs may increase replay risk inside the freshness window. @@ -478,14 +478,14 @@ Remediation: - Do not log raw auth headers. - Record only derived booleans or non-secret component kind. -- Add a scrub list for `authorization`, `x-loom-component`, `x-loom-timestamp`, and `x-loom-nonce`. +- Add a scrub list for `authorization`, `x-weft-component`, `x-weft-timestamp`, and `x-weft-nonce`. ### L-02: Python coverage is reported but not gated Locations: -- [/home/john/clarion/plugins/python/pyproject.toml:97](/home/john/clarion/plugins/python/pyproject.toml:97), lines 97-104 -- [/home/john/clarion/.github/workflows/ci.yml:181](/home/john/clarion/.github/workflows/ci.yml:181), lines 181-182 +- [/home/john/loomweave/plugins/python/pyproject.toml:97](/home/john/loomweave/plugins/python/pyproject.toml:97), lines 97-104 +- [/home/john/loomweave/.github/workflows/ci.yml:181](/home/john/loomweave/.github/workflows/ci.yml:181), lines 181-182 `pytest-cov` is configured for reporting, but no `--cov-fail-under` threshold gates CI. @@ -500,8 +500,8 @@ Remediation: Locations: -- [/home/john/clarion/scripts/check-github-release-governance.py:363](/home/john/clarion/scripts/check-github-release-governance.py:363), lines 363-386 -- [/home/john/clarion/docs/operator/v1.0-release-governance.md:12](/home/john/clarion/docs/operator/v1.0-release-governance.md:12), lines 12-28 +- [/home/john/loomweave/scripts/check-github-release-governance.py:363](/home/john/loomweave/scripts/check-github-release-governance.py:363), lines 363-386 +- [/home/john/loomweave/docs/operator/v1.0-release-governance.md:12](/home/john/loomweave/docs/operator/v1.0-release-governance.md:12), lines 12-28 The script enforces an active ruleset for `refs/tags/v*`, but the operator checklist omits it. @@ -516,13 +516,13 @@ Remediation: Locations: -- [/home/john/clarion/plugins/python/src/clarion_plugin_python/wardline_probe.py:1](/home/john/clarion/plugins/python/src/clarion_plugin_python/wardline_probe.py:1), lines 1-22 -- [/home/john/clarion/plugins/python/src/clarion_plugin_python/server.py:140](/home/john/clarion/plugins/python/src/clarion_plugin_python/server.py:140), lines 140-150 -- [/home/john/clarion/plugins/python/plugin.toml:22](/home/john/clarion/plugins/python/plugin.toml:22), lines 22-27 +- [/home/john/loomweave/plugins/python/src/loomweave_plugin_python/wardline_probe.py:1](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/wardline_probe.py:1), lines 1-22 +- [/home/john/loomweave/plugins/python/src/loomweave_plugin_python/server.py:140](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/server.py:140), lines 140-150 +- [/home/john/loomweave/plugins/python/plugin.toml:22](/home/john/loomweave/plugins/python/plugin.toml:22), lines 22-27 The probe claims startup Wardline discovery, but the server does not call it and the plugin handshake returns empty capabilities. -Impact: the plugin remains Loom-safe and not Wardline-dependent, but dead probe code and stale wording make federation state harder to audit. +Impact: the plugin remains Weft-safe and not Wardline-dependent, but dead probe code and stale wording make federation state harder to audit. Remediation: @@ -533,9 +533,9 @@ Remediation: Locations: -- [/home/john/clarion/crates/clarion-mcp/src/lib.rs:160](/home/john/clarion/crates/clarion-mcp/src/lib.rs:160), lines 160-171 -- [/home/john/clarion/crates/clarion-mcp/src/lib.rs:835](/home/john/clarion/crates/clarion-mcp/src/lib.rs:835), lines 835-845 -- [/home/john/clarion/crates/clarion-mcp/src/tools/graph.rs:83](/home/john/clarion/crates/clarion-mcp/src/tools/graph.rs:83), line 83 +- [/home/john/loomweave/crates/loomweave-mcp/src/lib.rs:160](/home/john/loomweave/crates/loomweave-mcp/src/lib.rs:160), lines 160-171 +- [/home/john/loomweave/crates/loomweave-mcp/src/lib.rs:835](/home/john/loomweave/crates/loomweave-mcp/src/lib.rs:835), lines 835-845 +- [/home/john/loomweave/crates/loomweave-mcp/src/tools/graph.rs:83](/home/john/loomweave/crates/loomweave-mcp/src/tools/graph.rs:83), line 83 Tool schemas advertise `additionalProperties: false`, but runtime dispatch generally validates only object shape and lets handlers ignore unknown keys. @@ -548,12 +548,12 @@ Remediation: ## Notable Non-Findings -- SCC/Tarjan cycle logic looked sound in the inspected scope. The implementation handles target-only nodes, self-edges, deterministic ordering, and truncation tests around `/home/john/clarion/crates/clarion-mcp/src/catalogue/shortcuts.rs:855`. +- SCC/Tarjan cycle logic looked sound in the inspected scope. The implementation handles target-only nodes, self-edges, deterministic ordering, and truncation tests around `/home/john/loomweave/crates/loomweave-mcp/src/catalogue/shortcuts.rs:855`. - Subsystem clustering normalizes community/member order before hashing and output in the inspected paths. -- No evidence was found that Clarion introduces mandatory Loom sibling dependencies; the federation concerns above are enrichment/policy-boundary issues, not mandatory-centralization issues. +- No evidence was found that Loomweave introduces mandatory Weft sibling dependencies; the federation concerns above are enrichment/policy-boundary issues, not mandatory-centralization issues. ## Verification Gaps - No test/build/lint gates were run due the requested read-only audit mode. -- The parent Clarion dogfood MCP status call timed out after 120 seconds and was not used as evidence. +- The parent Loomweave dogfood MCP status call timed out after 120 seconds and was not used as evidence. - Findings should be converted to Filigree issues or implementation tasks before remediation work starts. diff --git a/docs/implementation/comprehensive-readonly-audit-2026-06-04.md b/docs/implementation/comprehensive-readonly-audit-2026-06-04.md index c79f0fcf..00d05330 100644 --- a/docs/implementation/comprehensive-readonly-audit-2026-06-04.md +++ b/docs/implementation/comprehensive-readonly-audit-2026-06-04.md @@ -1,9 +1,9 @@ -# Clarion Comprehensive Read-Only Audit +# Loomweave Comprehensive Read-Only Audit Date: 2026-06-04 -Repository: `/home/john/clarion` +Repository: `/home/john/loomweave` Branch observed: `ws6-guidance-maturity` ahead of `origin/ws6-guidance-maturity` by 2 -Dirty state observed: untracked `.clarion/clarion.lock` +Dirty state observed: untracked `.loomweave/loomweave.lock` ## Method @@ -43,9 +43,9 @@ None found in the completed read-only audit. Severity: High Locations: -- [host.rs](/home/john/clarion/crates/clarion-core/src/plugin/host.rs:978), lines 978-1087 -- [limits.rs](/home/john/clarion/crates/clarion-core/src/plugin/limits.rs:109), lines 109-130 -- [analyze.rs](/home/john/clarion/crates/clarion-cli/src/analyze.rs:3478), lines 3478-3569 +- [host.rs](/home/john/loomweave/crates/loomweave-core/src/plugin/host.rs:978), lines 978-1087 +- [limits.rs](/home/john/loomweave/crates/loomweave-core/src/plugin/limits.rs:109), lines 109-130 +- [analyze.rs](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:3478), lines 3478-3569 Evidence: `EntityCountCap` documents ADR-021 as a combined cap for entities, edges, and findings. The host calls `try_admit(1)` only in the entity loop, then @@ -69,16 +69,16 @@ persisting the excess items. Severity: High Locations: -- [host.rs](/home/john/clarion/crates/clarion-core/src/plugin/host.rs:48), lines 48-59 -- [host.rs](/home/john/clarion/crates/clarion-core/src/plugin/host.rs:594), lines 594-610 -- [limits.rs](/home/john/clarion/crates/clarion-core/src/plugin/limits.rs:301), lines 301-326 +- [host.rs](/home/john/loomweave/crates/loomweave-core/src/plugin/host.rs:48), lines 48-59 +- [host.rs](/home/john/loomweave/crates/loomweave-core/src/plugin/host.rs:594), lines 594-610 +- [limits.rs](/home/john/loomweave/crates/loomweave-core/src/plugin/limits.rs:301), lines 301-326 Evidence: The `pre_exec` block is compiled for Linux or macOS, but `host.rs` imports several limit symbols only on Linux and `effective_max_nproc` is compiled only for Linux/tests. The macOS path therefore appears to reference symbols that are not imported or defined. -Impact: Clarion can fail to compile on macOS targets despite macOS being named +Impact: Loomweave can fail to compile on macOS targets despite macOS being named in the resource-limit path and release governance history. Remediation: Align cfgs. Either make the `pre_exec` block Linux-only or import @@ -86,16 +86,16 @@ and define macOS-safe helpers, splitting Linux-only `nproc` behavior from the portable address-space/file-descriptor limits. Acceptance test: Run `cargo check --workspace --all-targets --target x86_64-apple-darwin` -or restore an equivalent macOS CI leg and prove `clarion-core` builds. +or restore an equivalent macOS CI leg and prove `loomweave-core` builds. ### H3. File entities are not yet the canonical graph/source anchor Severity: High Locations: -- [analyze.rs](/home/john/clarion/crates/clarion-cli/src/analyze.rs:3514), lines 3514-3540 -- [analyze.rs](/home/john/clarion/crates/clarion-cli/src/analyze.rs:3853), lines 3853-3933 -- [writer.rs](/home/john/clarion/crates/clarion-storage/src/writer.rs:571), lines 571-573 +- [analyze.rs](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:3514), lines 3514-3540 +- [analyze.rs](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:3853), lines 3853-3933 +- [writer.rs](/home/john/loomweave/crates/loomweave-storage/src/writer.rs:571), lines 571-573 Evidence: Analyze now creates `core:file:*` records, but plugin entities still derive `source_file_id` from the module entity. Storage still permits both @@ -117,9 +117,9 @@ the module parent is the file, the function parent chain resolves to the file, Severity: High Locations: -- [analyze.rs](/home/john/clarion/crates/clarion-cli/src/analyze.rs:321), lines 321-328 -- [commands.rs](/home/john/clarion/crates/clarion-storage/src/commands.rs:157), lines 157-164 -- [writer.rs](/home/john/clarion/crates/clarion-storage/src/writer.rs:439), lines 439-447 +- [analyze.rs](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:321), lines 321-328 +- [commands.rs](/home/john/loomweave/crates/loomweave-storage/src/commands.rs:157), lines 157-164 +- [writer.rs](/home/john/loomweave/crates/loomweave-storage/src/writer.rs:439), lines 439-447 Evidence: Storage comments state `--resume` is a re-emit-without-flip path, not incremental checkpoint recovery. No durable phase/file checkpoint path was found. @@ -139,10 +139,10 @@ completed phases/files and provider calls are not repeated. Severity: High Locations: -- [requirements.md](/home/john/clarion/docs/clarion/1.0/requirements.md:548), lines 548-554 -- [analyze.rs](/home/john/clarion/crates/clarion-cli/src/analyze.rs:668), lines 668-700 -- [analyze.rs](/home/john/clarion/crates/clarion-cli/src/analyze.rs:785), lines 785-884 -- [writer.rs](/home/john/clarion/crates/clarion-storage/src/writer.rs:974), lines 974-989 +- [requirements.md](/home/john/loomweave/docs/loomweave/1.0/requirements.md:548), lines 548-554 +- [analyze.rs](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:668), lines 668-700 +- [analyze.rs](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:785), lines 785-884 +- [writer.rs](/home/john/loomweave/crates/loomweave-storage/src/writer.rs:974), lines 974-989 Evidence: Requirements describe streaming so the core can commit entities incrementally. Implementation runs blocking plugin work, collects all entities, @@ -164,11 +164,11 @@ earlier batches are durable, the run records failure, and memory stays bounded. Severity: High Locations: -- [plugin.toml](/home/john/clarion/plugins/python/plugin.toml:22), lines 22-40 -- [wardline_probe.py](/home/john/clarion/plugins/python/src/clarion_plugin_python/wardline_probe.py:38), lines 38-84 -- [server.py](/home/john/clarion/plugins/python/src/clarion_plugin_python/server.py:144), lines 144-155 -- [extractor.py](/home/john/clarion/plugins/python/src/clarion_plugin_python/extractor.py:179), lines 179-194 -- [extractor.py](/home/john/clarion/plugins/python/src/clarion_plugin_python/extractor.py:903), lines 903-930 +- [plugin.toml](/home/john/loomweave/plugins/python/plugin.toml:22), lines 22-40 +- [wardline_probe.py](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/wardline_probe.py:38), lines 38-84 +- [server.py](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/server.py:144), lines 144-155 +- [extractor.py](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/extractor.py:179), lines 179-194 +- [extractor.py](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/extractor.py:903), lines 903-930 Evidence: The manifest sets `wardline_aware = true`, but the ontology exposes only `function`, `class`, `module` and `contains`, `calls`, `references`, @@ -192,10 +192,10 @@ vocabulary is unavailable. Severity: High Locations: -- [analyze_runs.rs](/home/john/clarion/crates/clarion-mcp/src/analyze_runs.rs:11), lines 11-16 -- [analyze_runs.rs](/home/john/clarion/crates/clarion-mcp/src/analyze_runs.rs:166), lines 166-202 -- [lib.rs](/home/john/clarion/crates/clarion-mcp/src/lib.rs:2005), lines 2005-2028 -- [writer.rs](/home/john/clarion/crates/clarion-storage/src/writer.rs:344), lines 344-363 +- [analyze_runs.rs](/home/john/loomweave/crates/loomweave-mcp/src/analyze_runs.rs:11), lines 11-16 +- [analyze_runs.rs](/home/john/loomweave/crates/loomweave-mcp/src/analyze_runs.rs:166), lines 166-202 +- [lib.rs](/home/john/loomweave/crates/loomweave-mcp/src/lib.rs:2005), lines 2005-2028 +- [writer.rs](/home/john/loomweave/crates/loomweave-storage/src/writer.rs:344), lines 344-363 Evidence: MCP cancel explicitly defers supervising-process crash reconciliation to future `owner_pid`/`heartbeat_at` work. `project_status` reports the latest @@ -218,12 +218,12 @@ terminal state and update the row. Severity: High Locations: -- [ADR-007-summary-cache-key.md](/home/john/clarion/docs/clarion/adr/ADR-007-summary-cache-key.md:28), lines 28-40 -- [ADR-030-on-demand-summary-scope.md](/home/john/clarion/docs/clarion/adr/ADR-030-on-demand-summary-scope.md:58), lines 58-63 -- [summary.rs](/home/john/clarion/crates/clarion-mcp/src/tools/summary.rs:425), lines 425-454 -- [summary.rs](/home/john/clarion/crates/clarion-mcp/src/tools/summary.rs:510), lines 510-532 -- [guidance.rs](/home/john/clarion/crates/clarion-storage/src/guidance.rs:443), lines 443-452 -- [guidance.rs](/home/john/clarion/crates/clarion-cli/src/guidance.rs:272), lines 272-282 +- [ADR-007-summary-cache-key.md](/home/john/loomweave/docs/loomweave/adr/ADR-007-summary-cache-key.md:28), lines 28-40 +- [ADR-030-on-demand-summary-scope.md](/home/john/loomweave/docs/loomweave/adr/ADR-030-on-demand-summary-scope.md:58), lines 58-63 +- [summary.rs](/home/john/loomweave/crates/loomweave-mcp/src/tools/summary.rs:425), lines 425-454 +- [summary.rs](/home/john/loomweave/crates/loomweave-mcp/src/tools/summary.rs:510), lines 510-532 +- [guidance.rs](/home/john/loomweave/crates/loomweave-storage/src/guidance.rs:443), lines 443-452 +- [guidance.rs](/home/john/loomweave/crates/loomweave-cli/src/guidance.rs:272), lines 272-282 Evidence: ADRs require `guidance_fingerprint` because summaries are guidance-conditioned. The summary read path hard-codes `guidance-empty`, and the @@ -246,9 +246,9 @@ miss with a changed fingerprint and guidance content in the prompt. Severity: High Locations: -- [extractor.py](/home/john/clarion/plugins/python/src/clarion_plugin_python/extractor.py:454), lines 454-526 -- [shortcuts.rs](/home/john/clarion/crates/clarion-mcp/src/catalogue/shortcuts.rs:104), lines 104-132 -- [requirements.md](/home/john/clarion/docs/clarion/1.0/requirements.md:564), lines 564-569 +- [extractor.py](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/extractor.py:454), lines 454-526 +- [shortcuts.rs](/home/john/loomweave/crates/loomweave-mcp/src/catalogue/shortcuts.rs:104), lines 104-132 +- [requirements.md](/home/john/loomweave/docs/loomweave/1.0/requirements.md:564), lines 564-569 Evidence: `_ImportEdgeCollector` visits the whole AST and emits every import as a module-level resolved `imports` edge. It does not track `if TYPE_CHECKING`, @@ -270,9 +270,9 @@ Acceptance test: A fixture where `b.py` imports `a.py` only under Severity: High Locations: -- [pyright_session.py](/home/john/clarion/plugins/python/src/clarion_plugin_python/pyright_session.py:432), lines 432-448 -- [shortcuts.rs](/home/john/clarion/crates/clarion-mcp/src/catalogue/shortcuts.rs:324), lines 324-331 -- [shortcuts.rs](/home/john/clarion/crates/clarion-mcp/src/catalogue/shortcuts.rs:738), lines 738-757 +- [pyright_session.py](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/pyright_session.py:432), lines 432-448 +- [shortcuts.rs](/home/john/loomweave/crates/loomweave-mcp/src/catalogue/shortcuts.rs:324), lines 324-331 +- [shortcuts.rs](/home/john/loomweave/crates/loomweave-mcp/src/catalogue/shortcuts.rs:738), lines 738-757 Evidence: Ambiguous calls store only `candidate_ids[0]` in `to_id`; the full set is stored in `properties.candidates`. Dead-code reachability selects only @@ -292,10 +292,10 @@ both must be excluded from `entity_dead_list`. Severity: High Locations: -- [extractor.py](/home/john/clarion/plugins/python/src/clarion_plugin_python/extractor.py:356), lines 356-365 -- [extractor.py](/home/john/clarion/plugins/python/src/clarion_plugin_python/extractor.py:861), lines 861-889 -- [pyright_session.py](/home/john/clarion/plugins/python/src/clarion_plugin_python/pyright_session.py:970), lines 970-982 -- [pyright_session.py](/home/john/clarion/plugins/python/src/clarion_plugin_python/pyright_session.py:1008), lines 1008-1038 +- [extractor.py](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/extractor.py:356), lines 356-365 +- [extractor.py](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/extractor.py:861), lines 861-889 +- [pyright_session.py](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/pyright_session.py:970), lines 970-982 +- [pyright_session.py](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/pyright_session.py:1008), lines 1008-1038 Evidence: Entity extraction first-wins duplicate definitions and suppresses dropped duplicate bodies. Pyright indexing separately collects all definitions @@ -317,11 +317,11 @@ Acceptance test: Two same-name functions where only the dropped duplicate calls Severity: High Locations: -- [pyright_session.py](/home/john/clarion/plugins/python/src/clarion_plugin_python/pyright_session.py:247), lines 247-276 -- [analyze.rs](/home/john/clarion/crates/clarion-cli/src/analyze.rs:4141), lines 4141-4164 -- [unresolved.rs](/home/john/clarion/crates/clarion-storage/src/unresolved.rs:20), lines 20-29 -- [query.rs](/home/john/clarion/crates/clarion-storage/src/query.rs:961), lines 961-975 -- [summary.rs](/home/john/clarion/crates/clarion-mcp/src/tools/summary.rs:188), lines 188-199 +- [pyright_session.py](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/pyright_session.py:247), lines 247-276 +- [analyze.rs](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:4141), lines 4141-4164 +- [unresolved.rs](/home/john/loomweave/crates/loomweave-storage/src/unresolved.rs:20), lines 20-29 +- [query.rs](/home/john/loomweave/crates/loomweave-storage/src/query.rs:961), lines 961-975 +- [summary.rs](/home/john/loomweave/crates/loomweave-mcp/src/tools/summary.rs:188), lines 188-199 Evidence: On Pyright unavailable/timeout/crash, `resolve_calls` reports unresolved totals but returns an empty unresolved-site list. The analyzer only @@ -346,9 +346,9 @@ on or materialize the stale site. Severity: High Locations: -- [ci.yml](/home/john/clarion/.github/workflows/ci.yml:48), lines 48-74 -- [release.yml](/home/john/clarion/.github/workflows/release.yml:26), lines 26-29 -- [release.yml](/home/john/clarion/.github/workflows/release.yml:62), lines 62-117 +- [ci.yml](/home/john/loomweave/.github/workflows/ci.yml:48), lines 48-74 +- [release.yml](/home/john/loomweave/.github/workflows/release.yml:26), lines 26-29 +- [release.yml](/home/john/loomweave/.github/workflows/release.yml:62), lines 62-117 Evidence: `release.yml` says the verify job must mirror CI. CI includes release governance static guard, pyright pin lockstep, Wardline version bounds, and @@ -370,9 +370,9 @@ lockstep; both CI and release verify should fail before artifact build/publish. Severity: Medium Locations: -- [pyright_session.py](/home/john/clarion/plugins/python/src/clarion_plugin_python/pyright_session.py:370), lines 370-463 -- [pyright_session.py](/home/john/clarion/plugins/python/src/clarion_plugin_python/pyright_session.py:1202), lines 1202-1218 -- [pyright_session.py](/home/john/clarion/plugins/python/src/clarion_plugin_python/pyright_session.py:1384), lines 1384-1389 +- [pyright_session.py](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/pyright_session.py:370), lines 370-463 +- [pyright_session.py](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/pyright_session.py:1202), lines 1202-1218 +- [pyright_session.py](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/pyright_session.py:1384), lines 1384-1389 Evidence: AST `col_offset`/`end_col_offset` are byte offsets. LSP ranges are UTF-16 character offsets. The code compares and converts these as if they were @@ -393,8 +393,8 @@ call resolves and the byte span slices exactly to the callee expression. Severity: Medium Locations: -- [extractor.py](/home/john/clarion/plugins/python/src/clarion_plugin_python/extractor.py:632), lines 632-668 -- [pyright_session.py](/home/john/clarion/plugins/python/src/clarion_plugin_python/pyright_session.py:602), lines 602-624 +- [extractor.py](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/extractor.py:632), lines 632-668 +- [pyright_session.py](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/pyright_session.py:602), lines 602-624 Evidence: The reference collector suppresses only names bound in the current scope. Names bound in an enclosing function can be collected in an inner @@ -416,9 +416,9 @@ local, assert no `references` edge targets the module for `token`. Severity: Medium Locations: -- [lib.rs](/home/john/clarion/crates/clarion-mcp/src/lib.rs:345), lines 345-347 -- [inspection.rs](/home/john/clarion/crates/clarion-mcp/src/catalogue/inspection.rs:30), lines 30-31 -- [inspection.rs](/home/john/clarion/crates/clarion-mcp/src/catalogue/inspection.rs:179), lines 179-235 +- [lib.rs](/home/john/loomweave/crates/loomweave-mcp/src/lib.rs:345), lines 345-347 +- [inspection.rs](/home/john/loomweave/crates/loomweave-mcp/src/catalogue/inspection.rs:30), lines 30-31 +- [inspection.rs](/home/john/loomweave/crates/loomweave-mcp/src/catalogue/inspection.rs:179), lines 179-235 Evidence: `entity_finding_list` fetches `LIMIT 5000`, then checks whether the already-capped vector reached 5,000. The 5,001st row is never fetched, so @@ -439,10 +439,10 @@ truncation. Severity: Medium Locations: -- [analyze.rs](/home/john/clarion/crates/clarion-cli/src/analyze.rs:3484), lines 3484-3494 -- [lib.rs](/home/john/clarion/crates/clarion-mcp/src/lib.rs:2796), lines 2796-2798 -- [lib.rs](/home/john/clarion/crates/clarion-mcp/src/lib.rs:2846), lines 2846-2850 -- [analyze.rs](/home/john/clarion/crates/clarion-mcp/src/tools/analyze.rs:136), lines 136-160 +- [analyze.rs](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:3484), lines 3484-3494 +- [lib.rs](/home/john/loomweave/crates/loomweave-mcp/src/lib.rs:2796), lines 2796-2798 +- [lib.rs](/home/john/loomweave/crates/loomweave-mcp/src/lib.rs:2846), lines 2846-2850 +- [analyze.rs](/home/john/loomweave/crates/loomweave-mcp/src/tools/analyze.rs:136), lines 136-160 Evidence: Progress heartbeat is written at phase/file boundaries, but not while `host.analyze_file(file)` is in flight. MCP treats heartbeat age over 30 seconds @@ -464,8 +464,8 @@ work. Severity: Medium Locations: -- [contracts.md](/home/john/clarion/docs/federation/contracts.md:236), lines 236-310 -- [serve.rs](/home/john/clarion/crates/clarion-cli/tests/serve.rs:195), lines 195-234 +- [contracts.md](/home/john/loomweave/docs/federation/contracts.md:236), lines 236-310 +- [serve.rs](/home/john/loomweave/crates/loomweave-cli/tests/serve.rs:195), lines 195-234 Evidence: The contract marks `fixtures/post-api-v1-files-batch.json` as normative. The conformance test loads other fixtures but not that batch fixture. @@ -483,8 +483,8 @@ fixture-conformance test should fail. Severity: Medium Locations: -- [contracts.md](/home/john/clarion/docs/federation/contracts.md:791), lines 791-805 -- [wardline_taint.rs](/home/john/clarion/crates/clarion-storage/src/wardline_taint.rs:346), lines 346-377 +- [contracts.md](/home/john/loomweave/docs/federation/contracts.md:791), lines 791-805 +- [wardline_taint.rs](/home/john/loomweave/crates/loomweave-storage/src/wardline_taint.rs:346), lines 346-377 Evidence: The test states expected values were copied from `wardline-qualname-normalization.json`, then hard-codes them. New fixture @@ -503,8 +503,8 @@ implementation supports it. Severity: Medium Locations: -- [analyze.rs](/home/john/clarion/crates/clarion-cli/src/analyze.rs:3908), lines 3908-3933 -- [writer.rs](/home/john/clarion/crates/clarion-storage/src/writer.rs:487), lines 487-528 +- [analyze.rs](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:3908), lines 3908-3933 +- [writer.rs](/home/john/loomweave/crates/loomweave-storage/src/writer.rs:487), lines 487-528 Evidence: Core file records set `first_seen_commit` and `last_seen_commit` to `None`, and the writer preserves incoming values rather than repairing them. @@ -523,7 +523,7 @@ entities have correct first/last commit values. Severity: Medium Location: -- [analyze.rs](/home/john/clarion/crates/clarion-cli/src/analyze.rs:4270), lines 4270-4321 +- [analyze.rs](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:4270), lines 4270-4321 Evidence: `collect_source_files` warns and increments a local skipped counter for walk errors, but the return value is only a file list. The skipped count and @@ -542,8 +542,8 @@ durable stats/findings report the skipped source-walk failure. Severity: Medium Locations: -- [auth.rs](/home/john/clarion/crates/clarion-cli/src/http_read/auth.rs:71), lines 71-110 -- [auth.rs](/home/john/clarion/crates/clarion-cli/src/http_read/auth.rs:122), lines 122-185 +- [auth.rs](/home/john/loomweave/crates/loomweave-cli/src/http_read/auth.rs:71), lines 71-110 +- [auth.rs](/home/john/loomweave/crates/loomweave-cli/src/http_read/auth.rs:122), lines 122-185 Evidence: HMAC-SHA256 and constant-time comparison are implemented locally. The canonical message signs method, path/query, and body hash only; it has no @@ -565,8 +565,8 @@ class. Replaying the same signed request/nonce should fail. Severity: Medium Locations: -- [plugin.toml](/home/john/clarion/plugins/python/plugin.toml:31), lines 31-47 -- [requirements.md](/home/john/clarion/docs/clarion/1.0/requirements.md:556), lines 556-577 +- [plugin.toml](/home/john/loomweave/plugins/python/plugin.toml:31), lines 31-47 +- [requirements.md](/home/john/loomweave/docs/loomweave/1.0/requirements.md:556), lines 556-577 Evidence: Requirements name protocols, globals, modules, packages, and edges such as `inherits_from`, `decorated_by`, `uses_type`, and `alias_of`. The live @@ -590,8 +590,8 @@ documented shapes or return explicit missing-signal notes. Severity: Low Locations: -- [host.rs](/home/john/clarion/crates/clarion-core/src/plugin/host.rs:656), lines 656-666 -- [host_subprocess.rs](/home/john/clarion/crates/clarion-core/tests/host_subprocess.rs:185), lines 185-233 +- [host.rs](/home/john/loomweave/crates/loomweave-core/src/plugin/host.rs:656), lines 656-666 +- [host_subprocess.rs](/home/john/loomweave/crates/loomweave-core/tests/host_subprocess.rs:185), lines 185-233 Evidence: Production code kills/waits on handshake failure, but the test comment states it verifies only prompt error return and non-hanging behavior, not zombie @@ -608,8 +608,8 @@ fail the new test. Severity: Low Locations: -- [server.py](/home/john/clarion/plugins/python/src/clarion_plugin_python/server.py:74), lines 74-117 -- [server.py](/home/john/clarion/plugins/python/src/clarion_plugin_python/server.py:294), lines 294-300 +- [server.py](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/server.py:74), lines 74-117 +- [server.py](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/server.py:294), lines 294-300 Evidence: `read_frame` decodes headers with `line.decode("ascii")`; a non-ASCII byte raises `UnicodeDecodeError`, while `main` catches only @@ -626,8 +626,8 @@ assert it returns the protocol-error exit path without corrupting stdout. Severity: Low Locations: -- [guidance.rs](/home/john/clarion/crates/clarion-cli/src/guidance.rs:228), lines 228-234 -- [guidance.rs](/home/john/clarion/crates/clarion-storage/src/guidance.rs:156), lines 156-204 +- [guidance.rs](/home/john/loomweave/crates/loomweave-cli/src/guidance.rs:228), lines 228-234 +- [guidance.rs](/home/john/loomweave/crates/loomweave-storage/src/guidance.rs:156), lines 156-204 Evidence: CLI create performs a non-atomic existence check before a low-level upsert. The source comment acknowledges a concurrent create can overwrite the @@ -644,8 +644,8 @@ the other fails without modifying the first row. Severity: Low Locations: -- [shortcuts.rs](/home/john/clarion/crates/clarion-mcp/src/catalogue/shortcuts.rs:104), lines 104-119 -- [shortcuts.rs](/home/john/clarion/crates/clarion-mcp/src/catalogue/shortcuts.rs:741), lines 741-744 +- [shortcuts.rs](/home/john/loomweave/crates/loomweave-mcp/src/catalogue/shortcuts.rs:104), lines 104-119 +- [shortcuts.rs](/home/john/loomweave/crates/loomweave-mcp/src/catalogue/shortcuts.rs:741), lines 741-744 Evidence: Circular-import and dead-code adjacency scans use `LIMIT ?1` without `ORDER BY`. @@ -662,19 +662,19 @@ assert repeated runs return identical truncated output. Severity: Low Locations: -- [lib.rs](/home/john/clarion/crates/clarion-mcp/src/lib.rs:149), lines 149-490 -- [lib.rs](/home/john/clarion/crates/clarion-mcp/src/lib.rs:648), lines 648-970 -- [analyze.rs](/home/john/clarion/crates/clarion-cli/src/analyze.rs:38), lines 38-44 +- [lib.rs](/home/john/loomweave/crates/loomweave-mcp/src/lib.rs:149), lines 149-490 +- [lib.rs](/home/john/loomweave/crates/loomweave-mcp/src/lib.rs:648), lines 648-970 +- [analyze.rs](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:38), lines 38-44 -Evidence: `clarion-mcp` carries tool registry, server state, dispatch, resources, +Evidence: `loomweave-mcp` carries tool registry, server state, dispatch, resources, diagnostics, analyze registry, and utility clients; CLI analyze imports helpers -from `clarion_mcp`. +from `loomweave_mcp`. Remediation: Move shared federation/config/scan-result helpers to a narrower crate or CLI-owned module and split MCP registry/state/dispatch/resource code into focused modules. -Acceptance test: CLI analyze no longer depends on `clarion_mcp` for non-MCP +Acceptance test: CLI analyze no longer depends on `loomweave_mcp` for non-MCP helpers, and MCP dispatch delegates to focused modules with behavior preserved. ## Recommended Remediation Order @@ -723,55 +723,55 @@ regressions and broad gates run afterward. | Finding | Status | Primary implementation points | Regression evidence | | --- | --- | --- | --- | -| H1 | Resolved | Combined entity, edge, and finding admission is enforced in [host.rs](/home/john/clarion/crates/clarion-core/src/plugin/host.rs:982) and [host.rs](/home/john/clarion/crates/clarion-core/src/plugin/host.rs:1174); cap semantics remain documented in [limits.rs](/home/john/clarion/crates/clarion-core/src/plugin/limits.rs:128). | Host cap tests in [host.rs](/home/john/clarion/crates/clarion-core/src/plugin/host.rs:2440) and [host.rs](/home/john/clarion/crates/clarion-core/src/plugin/host.rs:2524). | -| H2 | Resolved | macOS/Linux resource-limit cfgs now align in [host.rs](/home/john/clarion/crates/clarion-core/src/plugin/host.rs:52), [host.rs](/home/john/clarion/crates/clarion-core/src/plugin/host.rs:107), [limits.rs](/home/john/clarion/crates/clarion-core/src/plugin/limits.rs:301), and [limits.rs](/home/john/clarion/crates/clarion-core/src/plugin/limits.rs:320). | Workspace check/build/clippy gates below compile the local targets; macOS target CI remains the stronger remote proof. | -| H3 | Resolved | Analyze maps plugin output to canonical `core:file:*` anchors in [analyze.rs](/home/john/clarion/crates/clarion-cli/src/analyze.rs:4137) and [analyze.rs](/home/john/clarion/crates/clarion-cli/src/analyze.rs:4395); storage rejects module/function anchors in [writer.rs](/home/john/clarion/crates/clarion-storage/src/writer.rs:590). | Anchor tests in [writer_actor.rs](/home/john/clarion/crates/clarion-storage/tests/writer_actor.rs:1324) and [writer_actor.rs](/home/john/clarion/crates/clarion-storage/tests/writer_actor.rs:1374). | -| H4 | Resolved by contract reconciliation | ADR-041 makes v1.x resume an idempotent re-emit rather than checkpoint recovery in [ADR-041-resume-is-idempotent-reemit.md](/home/john/clarion/docs/clarion/adr/ADR-041-resume-is-idempotent-reemit.md:12), and amends ADR-005/ADR-011 status lines. | Resume behavior test in [analyze.rs](/home/john/clarion/crates/clarion-cli/tests/analyze.rs:2261). | -| H5 | Resolved | Plugin file output streams through a bounded channel in [analyze.rs](/home/john/clarion/crates/clarion-cli/src/analyze.rs:765), [analyze.rs](/home/john/clarion/crates/clarion-cli/src/analyze.rs:776), and [analyze.rs](/home/john/clarion/crates/clarion-cli/src/analyze.rs:3673); cross-file edges are queued until both endpoints are inserted in [analyze.rs](/home/john/clarion/crates/clarion-cli/src/analyze.rs:797) and [analyze.rs](/home/john/clarion/crates/clarion-cli/src/analyze.rs:3593). | Failure-mode coverage in [analyze_failure_modes.rs](/home/john/clarion/crates/clarion-cli/tests/analyze_failure_modes.rs:439), plus full workspace and Phase 3 e2e tests. | -| H6 | Resolved by honest capability contract | Python Wardline capability claims and v1 ontology docs were reconciled in [plugin.toml](/home/john/clarion/plugins/python/plugin.toml:1), [requirements.md](/home/john/clarion/docs/clarion/1.0/requirements.md:1), [system-design.md](/home/john/clarion/docs/clarion/1.0/system-design.md:1), and [detailed-design.md](/home/john/clarion/docs/clarion/1.0/detailed-design.md:1). | Ontology test in [test_package.py](/home/john/clarion/plugins/python/tests/test_package.py:1). | -| H7 | Resolved | Runs now persist `owner_pid` and `heartbeat_at`; stale running rows are repaired by [runs.rs](/home/john/clarion/crates/clarion-storage/src/runs.rs:15), analyze startup in [analyze.rs](/home/john/clarion/crates/clarion-cli/src/analyze.rs:371), MCP status in [status.rs](/home/john/clarion/crates/clarion-mcp/src/tools/status.rs:184), and analyze status in [analyze.rs](/home/john/clarion/crates/clarion-mcp/src/tools/analyze.rs:261). | Tests in [storage_tools.rs](/home/john/clarion/crates/clarion-mcp/tests/storage_tools.rs:4292) and [analyze_lifecycle.rs](/home/john/clarion/crates/clarion-mcp/tests/analyze_lifecycle.rs:265). | -| H8 | Resolved | Summary inputs compose applicable guidance and hash it into the cache key in [summary.rs](/home/john/clarion/crates/clarion-mcp/src/tools/summary.rs:39), [summary.rs](/home/john/clarion/crates/clarion-mcp/src/tools/summary.rs:77), and [summary.rs](/home/john/clarion/crates/clarion-mcp/src/tools/summary.rs:500). | Prompt/cache regression in [storage_tools.rs](/home/john/clarion/crates/clarion-mcp/tests/storage_tools.rs:1421). | -| H9 | Resolved | Python import extraction now records `type_only` and `scope`, and MCP runtime import algorithms filter them in [extractor.py](/home/john/clarion/plugins/python/src/clarion_plugin_python/extractor.py:456), [extractor.py](/home/john/clarion/plugins/python/src/clarion_plugin_python/extractor.py:550), and [shortcuts.rs](/home/john/clarion/crates/clarion-mcp/src/catalogue/shortcuts.rs:60). | Python package tests plus MCP shortcut tests. | -| H10 | Resolved | Dead-code reachability expands ambiguous call candidates from edge properties in [shortcuts.rs](/home/john/clarion/crates/clarion-mcp/src/catalogue/shortcuts.rs:823) and [shortcuts.rs](/home/john/clarion/crates/clarion-mcp/src/catalogue/shortcuts.rs:829). | Ambiguous candidate tests in [catalogue_tools.rs](/home/john/clarion/crates/clarion-mcp/tests/catalogue_tools.rs:1205). | -| H11 | Resolved | Duplicate-definition disposition now suppresses dropped duplicate bodies before call/reference collection in [extractor.py](/home/john/clarion/plugins/python/src/clarion_plugin_python/extractor.py:896). | Regression in [test_extractor.py](/home/john/clarion/plugins/python/tests/test_extractor.py:565). | -| H12 | Resolved | Unresolved-call reads require current caller content hashes in [query.rs](/home/john/clarion/crates/clarion-storage/src/query.rs:961) and [query.rs](/home/john/clarion/crates/clarion-storage/src/query.rs:984); analyze writes hash-scoped replacements in [analyze.rs](/home/john/clarion/crates/clarion-cli/src/analyze.rs:4419). | Tests in [query_helpers.rs](/home/john/clarion/crates/clarion-storage/tests/query_helpers.rs:438) and [storage_tools.rs](/home/john/clarion/crates/clarion-mcp/tests/storage_tools.rs:3132). | -| H13 | Resolved | Release verify now mirrors CI static guards in [release.yml](/home/john/clarion/.github/workflows/release.yml:70), [release.yml](/home/john/clarion/.github/workflows/release.yml:78), [release.yml](/home/john/clarion/.github/workflows/release.yml:85), and [release.yml](/home/john/clarion/.github/workflows/release.yml:88). | Guard scripts are covered by self-tests in the release verify job and cargo deny/build gates below. | -| M1 | Resolved | Pyright/LSP matching now converts AST byte columns and LSP UTF-16 positions explicitly in [pyright_session.py](/home/john/clarion/plugins/python/src/clarion_plugin_python/pyright_session.py:1451), [pyright_session.py](/home/john/clarion/plugins/python/src/clarion_plugin_python/pyright_session.py:1461), and [pyright_session.py](/home/john/clarion/plugins/python/src/clarion_plugin_python/pyright_session.py:1471). | Regression in [test_pyright_session.py](/home/john/clarion/plugins/python/tests/test_pyright_session.py:180). | -| M2 | Resolved | Reference collection tracks enclosing local bindings in [extractor.py](/home/john/clarion/plugins/python/src/clarion_plugin_python/extractor.py:650) and [extractor.py](/home/john/clarion/plugins/python/src/clarion_plugin_python/extractor.py:717). | Python extractor tests. | -| M3 | Resolved | Finding filters and pagination are pushed into SQL before the cap in [inspection.rs](/home/john/clarion/crates/clarion-mcp/src/catalogue/inspection.rs:200) and [inspection.rs](/home/john/clarion/crates/clarion-mcp/src/catalogue/inspection.rs:216). | Regression in [catalogue_tools.rs](/home/john/clarion/crates/clarion-mcp/tests/catalogue_tools.rs:315). | -| M4 | Resolved | Analyze progress refreshes `heartbeat_at` through live progress snapshots and writer heartbeats in [analyze.rs](/home/john/clarion/crates/clarion-cli/src/analyze.rs:126), [writer.rs](/home/john/clarion/crates/clarion-storage/src/writer.rs:1013), and [analyze.rs](/home/john/clarion/crates/clarion-mcp/src/tools/analyze.rs:147). | Analyze status and stale-run tests listed under H7. | -| M5 | Resolved | The normative batch fixture is exercised by [serve.rs](/home/john/clarion/crates/clarion-cli/tests/serve.rs:1). | `cargo test -p clarion-cli --test serve serve_http_responses_match_federation_fixture_contracts -- --nocapture`. | -| M6 | Resolved | Wardline qualname fixture vectors are loaded directly by storage/Python tests in [wardline_taint.rs](/home/john/clarion/crates/clarion-storage/src/wardline_taint.rs:1). | Full storage and Python gates. | -| M7 | Resolved | Analyze stamps entity git provenance in [analyze.rs](/home/john/clarion/crates/clarion-cli/src/analyze.rs:3540), with writer preserving first-seen and refreshing last-seen in [writer.rs](/home/john/clarion/crates/clarion-storage/src/writer.rs:501). | Regression in [analyze.rs](/home/john/clarion/crates/clarion-cli/tests/analyze.rs:3100). | -| M8 | Resolved | Source-walk failures now persist stats/findings in [analyze.rs](/home/john/clarion/crates/clarion-cli/src/analyze.rs:4270). | `cargo test -p clarion-cli analyze::tests::source_walk -- --nocapture`. | -| M9 | Resolved | Federation auth uses `hmac`/`subtle` plus timestamp and nonce replay checks in [auth.rs](/home/john/clarion/crates/clarion-cli/src/http_read/auth.rs:1), with ADR-042 documenting the contract. | `cargo test -p clarion-cli http_read::auth::tests::hmac -- --nocapture` and `cargo test -p clarion-cli --test serve hmac_identity -- --nocapture`. | -| M10 | Resolved by contract reconciliation | v1.0 Python ontology now explicitly limits emitted kinds/edges and defers the absent ontology in [requirements.md](/home/john/clarion/docs/clarion/1.0/requirements.md:1), [system-design.md](/home/john/clarion/docs/clarion/1.0/system-design.md:1), and [detailed-design.md](/home/john/clarion/docs/clarion/1.0/detailed-design.md:1). | [test_package.py](/home/john/clarion/plugins/python/tests/test_package.py:1). | -| L1 | Resolved | Handshake-failure zombie reaping is asserted on Linux in [host_subprocess.rs](/home/john/clarion/crates/clarion-core/tests/host_subprocess.rs:1). | `cargo test -p clarion-core --test host_subprocess t9 -- --nocapture`. | -| L2 | Resolved | Python protocol header decoding converts non-ASCII malformed headers to `ProtocolError` in [server.py](/home/john/clarion/plugins/python/src/clarion_plugin_python/server.py:1). | [test_server.py](/home/john/clarion/plugins/python/tests/test_server.py:1). | -| L3 | Resolved | Guidance create is now insert-only and atomic in [guidance.rs](/home/john/clarion/crates/clarion-storage/src/guidance.rs:1) and [guidance.rs](/home/john/clarion/crates/clarion-cli/src/guidance.rs:1). | [guidance_write.rs](/home/john/clarion/crates/clarion-storage/tests/guidance_write.rs:1). | -| L4 | Resolved | Capped graph scans use deterministic ordering before `LIMIT` in [shortcuts.rs](/home/john/clarion/crates/clarion-mcp/src/catalogue/shortcuts.rs:1). | `cargo test -p clarion-mcp scan_truncates -- --nocapture`. | -| L5 | Resolved | Shared federation/config/scan-result helpers moved to [crates/clarion-federation](/home/john/clarion/crates/clarion-federation/src/lib.rs:1), with MCP retaining re-export shims and CLI importing the narrower crate. | `cargo check -p clarion-federation --all-targets`, `cargo check -p clarion-cli --all-targets`, and no remaining CLI references to `clarion_mcp::config`, `clarion_mcp::filigree`, `clarion_mcp::filigree_url`, or `clarion_mcp::scan_results`. | +| H1 | Resolved | Combined entity, edge, and finding admission is enforced in [host.rs](/home/john/loomweave/crates/loomweave-core/src/plugin/host.rs:982) and [host.rs](/home/john/loomweave/crates/loomweave-core/src/plugin/host.rs:1174); cap semantics remain documented in [limits.rs](/home/john/loomweave/crates/loomweave-core/src/plugin/limits.rs:128). | Host cap tests in [host.rs](/home/john/loomweave/crates/loomweave-core/src/plugin/host.rs:2440) and [host.rs](/home/john/loomweave/crates/loomweave-core/src/plugin/host.rs:2524). | +| H2 | Resolved | macOS/Linux resource-limit cfgs now align in [host.rs](/home/john/loomweave/crates/loomweave-core/src/plugin/host.rs:52), [host.rs](/home/john/loomweave/crates/loomweave-core/src/plugin/host.rs:107), [limits.rs](/home/john/loomweave/crates/loomweave-core/src/plugin/limits.rs:301), and [limits.rs](/home/john/loomweave/crates/loomweave-core/src/plugin/limits.rs:320). | Workspace check/build/clippy gates below compile the local targets; macOS target CI remains the stronger remote proof. | +| H3 | Resolved | Analyze maps plugin output to canonical `core:file:*` anchors in [analyze.rs](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:4137) and [analyze.rs](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:4395); storage rejects module/function anchors in [writer.rs](/home/john/loomweave/crates/loomweave-storage/src/writer.rs:590). | Anchor tests in [writer_actor.rs](/home/john/loomweave/crates/loomweave-storage/tests/writer_actor.rs:1324) and [writer_actor.rs](/home/john/loomweave/crates/loomweave-storage/tests/writer_actor.rs:1374). | +| H4 | Resolved by contract reconciliation | ADR-041 makes v1.x resume an idempotent re-emit rather than checkpoint recovery in [ADR-041-resume-is-idempotent-reemit.md](/home/john/loomweave/docs/loomweave/adr/ADR-041-resume-is-idempotent-reemit.md:12), and amends ADR-005/ADR-011 status lines. | Resume behavior test in [analyze.rs](/home/john/loomweave/crates/loomweave-cli/tests/analyze.rs:2261). | +| H5 | Resolved | Plugin file output streams through a bounded channel in [analyze.rs](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:765), [analyze.rs](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:776), and [analyze.rs](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:3673); cross-file edges are queued until both endpoints are inserted in [analyze.rs](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:797) and [analyze.rs](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:3593). | Failure-mode coverage in [analyze_failure_modes.rs](/home/john/loomweave/crates/loomweave-cli/tests/analyze_failure_modes.rs:439), plus full workspace and Phase 3 e2e tests. | +| H6 | Resolved by honest capability contract | Python Wardline capability claims and v1 ontology docs were reconciled in [plugin.toml](/home/john/loomweave/plugins/python/plugin.toml:1), [requirements.md](/home/john/loomweave/docs/loomweave/1.0/requirements.md:1), [system-design.md](/home/john/loomweave/docs/loomweave/1.0/system-design.md:1), and [detailed-design.md](/home/john/loomweave/docs/loomweave/1.0/detailed-design.md:1). | Ontology test in [test_package.py](/home/john/loomweave/plugins/python/tests/test_package.py:1). | +| H7 | Resolved | Runs now persist `owner_pid` and `heartbeat_at`; stale running rows are repaired by [runs.rs](/home/john/loomweave/crates/loomweave-storage/src/runs.rs:15), analyze startup in [analyze.rs](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:371), MCP status in [status.rs](/home/john/loomweave/crates/loomweave-mcp/src/tools/status.rs:184), and analyze status in [analyze.rs](/home/john/loomweave/crates/loomweave-mcp/src/tools/analyze.rs:261). | Tests in [storage_tools.rs](/home/john/loomweave/crates/loomweave-mcp/tests/storage_tools.rs:4292) and [analyze_lifecycle.rs](/home/john/loomweave/crates/loomweave-mcp/tests/analyze_lifecycle.rs:265). | +| H8 | Resolved | Summary inputs compose applicable guidance and hash it into the cache key in [summary.rs](/home/john/loomweave/crates/loomweave-mcp/src/tools/summary.rs:39), [summary.rs](/home/john/loomweave/crates/loomweave-mcp/src/tools/summary.rs:77), and [summary.rs](/home/john/loomweave/crates/loomweave-mcp/src/tools/summary.rs:500). | Prompt/cache regression in [storage_tools.rs](/home/john/loomweave/crates/loomweave-mcp/tests/storage_tools.rs:1421). | +| H9 | Resolved | Python import extraction now records `type_only` and `scope`, and MCP runtime import algorithms filter them in [extractor.py](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/extractor.py:456), [extractor.py](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/extractor.py:550), and [shortcuts.rs](/home/john/loomweave/crates/loomweave-mcp/src/catalogue/shortcuts.rs:60). | Python package tests plus MCP shortcut tests. | +| H10 | Resolved | Dead-code reachability expands ambiguous call candidates from edge properties in [shortcuts.rs](/home/john/loomweave/crates/loomweave-mcp/src/catalogue/shortcuts.rs:823) and [shortcuts.rs](/home/john/loomweave/crates/loomweave-mcp/src/catalogue/shortcuts.rs:829). | Ambiguous candidate tests in [catalogue_tools.rs](/home/john/loomweave/crates/loomweave-mcp/tests/catalogue_tools.rs:1205). | +| H11 | Resolved | Duplicate-definition disposition now suppresses dropped duplicate bodies before call/reference collection in [extractor.py](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/extractor.py:896). | Regression in [test_extractor.py](/home/john/loomweave/plugins/python/tests/test_extractor.py:565). | +| H12 | Resolved | Unresolved-call reads require current caller content hashes in [query.rs](/home/john/loomweave/crates/loomweave-storage/src/query.rs:961) and [query.rs](/home/john/loomweave/crates/loomweave-storage/src/query.rs:984); analyze writes hash-scoped replacements in [analyze.rs](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:4419). | Tests in [query_helpers.rs](/home/john/loomweave/crates/loomweave-storage/tests/query_helpers.rs:438) and [storage_tools.rs](/home/john/loomweave/crates/loomweave-mcp/tests/storage_tools.rs:3132). | +| H13 | Resolved | Release verify now mirrors CI static guards in [release.yml](/home/john/loomweave/.github/workflows/release.yml:70), [release.yml](/home/john/loomweave/.github/workflows/release.yml:78), [release.yml](/home/john/loomweave/.github/workflows/release.yml:85), and [release.yml](/home/john/loomweave/.github/workflows/release.yml:88). | Guard scripts are covered by self-tests in the release verify job and cargo deny/build gates below. | +| M1 | Resolved | Pyright/LSP matching now converts AST byte columns and LSP UTF-16 positions explicitly in [pyright_session.py](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/pyright_session.py:1451), [pyright_session.py](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/pyright_session.py:1461), and [pyright_session.py](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/pyright_session.py:1471). | Regression in [test_pyright_session.py](/home/john/loomweave/plugins/python/tests/test_pyright_session.py:180). | +| M2 | Resolved | Reference collection tracks enclosing local bindings in [extractor.py](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/extractor.py:650) and [extractor.py](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/extractor.py:717). | Python extractor tests. | +| M3 | Resolved | Finding filters and pagination are pushed into SQL before the cap in [inspection.rs](/home/john/loomweave/crates/loomweave-mcp/src/catalogue/inspection.rs:200) and [inspection.rs](/home/john/loomweave/crates/loomweave-mcp/src/catalogue/inspection.rs:216). | Regression in [catalogue_tools.rs](/home/john/loomweave/crates/loomweave-mcp/tests/catalogue_tools.rs:315). | +| M4 | Resolved | Analyze progress refreshes `heartbeat_at` through live progress snapshots and writer heartbeats in [analyze.rs](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:126), [writer.rs](/home/john/loomweave/crates/loomweave-storage/src/writer.rs:1013), and [analyze.rs](/home/john/loomweave/crates/loomweave-mcp/src/tools/analyze.rs:147). | Analyze status and stale-run tests listed under H7. | +| M5 | Resolved | The normative batch fixture is exercised by [serve.rs](/home/john/loomweave/crates/loomweave-cli/tests/serve.rs:1). | `cargo test -p loomweave-cli --test serve serve_http_responses_match_federation_fixture_contracts -- --nocapture`. | +| M6 | Resolved | Wardline qualname fixture vectors are loaded directly by storage/Python tests in [wardline_taint.rs](/home/john/loomweave/crates/loomweave-storage/src/wardline_taint.rs:1). | Full storage and Python gates. | +| M7 | Resolved | Analyze stamps entity git provenance in [analyze.rs](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:3540), with writer preserving first-seen and refreshing last-seen in [writer.rs](/home/john/loomweave/crates/loomweave-storage/src/writer.rs:501). | Regression in [analyze.rs](/home/john/loomweave/crates/loomweave-cli/tests/analyze.rs:3100). | +| M8 | Resolved | Source-walk failures now persist stats/findings in [analyze.rs](/home/john/loomweave/crates/loomweave-cli/src/analyze.rs:4270). | `cargo test -p loomweave-cli analyze::tests::source_walk -- --nocapture`. | +| M9 | Resolved | Federation auth uses `hmac`/`subtle` plus timestamp and nonce replay checks in [auth.rs](/home/john/loomweave/crates/loomweave-cli/src/http_read/auth.rs:1), with ADR-042 documenting the contract. | `cargo test -p loomweave-cli http_read::auth::tests::hmac -- --nocapture` and `cargo test -p loomweave-cli --test serve hmac_identity -- --nocapture`. | +| M10 | Resolved by contract reconciliation | v1.0 Python ontology now explicitly limits emitted kinds/edges and defers the absent ontology in [requirements.md](/home/john/loomweave/docs/loomweave/1.0/requirements.md:1), [system-design.md](/home/john/loomweave/docs/loomweave/1.0/system-design.md:1), and [detailed-design.md](/home/john/loomweave/docs/loomweave/1.0/detailed-design.md:1). | [test_package.py](/home/john/loomweave/plugins/python/tests/test_package.py:1). | +| L1 | Resolved | Handshake-failure zombie reaping is asserted on Linux in [host_subprocess.rs](/home/john/loomweave/crates/loomweave-core/tests/host_subprocess.rs:1). | `cargo test -p loomweave-core --test host_subprocess t9 -- --nocapture`. | +| L2 | Resolved | Python protocol header decoding converts non-ASCII malformed headers to `ProtocolError` in [server.py](/home/john/loomweave/plugins/python/src/loomweave_plugin_python/server.py:1). | [test_server.py](/home/john/loomweave/plugins/python/tests/test_server.py:1). | +| L3 | Resolved | Guidance create is now insert-only and atomic in [guidance.rs](/home/john/loomweave/crates/loomweave-storage/src/guidance.rs:1) and [guidance.rs](/home/john/loomweave/crates/loomweave-cli/src/guidance.rs:1). | [guidance_write.rs](/home/john/loomweave/crates/loomweave-storage/tests/guidance_write.rs:1). | +| L4 | Resolved | Capped graph scans use deterministic ordering before `LIMIT` in [shortcuts.rs](/home/john/loomweave/crates/loomweave-mcp/src/catalogue/shortcuts.rs:1). | `cargo test -p loomweave-mcp scan_truncates -- --nocapture`. | +| L5 | Resolved | Shared federation/config/scan-result helpers moved to [crates/loomweave-federation](/home/john/loomweave/crates/loomweave-federation/src/lib.rs:1), with MCP retaining re-export shims and CLI importing the narrower crate. | `cargo check -p loomweave-federation --all-targets`, `cargo check -p loomweave-cli --all-targets`, and no remaining CLI references to `loomweave_mcp::config`, `loomweave_mcp::filigree`, `loomweave_mcp::filigree_url`, or `loomweave_mcp::scan_results`. | ### Verification Run Focused regression checks: -- `cargo test -p clarion-core --test host_subprocess t9 -- --nocapture` -- `cargo test -p clarion-storage --test guidance_write insert_guidance_sheet_rejects_existing_id_without_overwrite -- --nocapture` -- `cargo test -p clarion-mcp scan_truncates -- --nocapture` -- `cargo test -p clarion-federation -- --nocapture` -- `cargo test -p clarion-cli http_read::auth::tests::hmac -- --nocapture` -- `cargo test -p clarion-cli http_read::tests -- --nocapture` -- `cargo test -p clarion-cli http_read::wardline::tests -- --nocapture` -- `cargo test -p clarion-cli http_read::linkages::tests -- --nocapture` -- `cargo test -p clarion-cli --test serve hmac_identity -- --nocapture` -- `cargo test -p clarion-cli --test serve serve_http_responses_match_federation_fixture_contracts -- --nocapture` -- `cargo test -p clarion-cli --test analyze analyze_stamps_entities_with_git_head_commit -- --nocapture` -- `cargo test -p clarion-cli analyze::tests::source_walk -- --nocapture` -- `cargo test -p clarion-cli --test analyze analyze_migrates_a_stale_db_instead_of_failing -- --nocapture` -- `cargo test -p clarion-cli --test install install_applies_each_migration_exactly_once -- --nocapture` -- `cargo test -p clarion-cli --test wp1_e2e wp1_walking_skeleton_end_to_end -- --nocapture` -- `cargo test -p clarion-cli --test analyze_failure_modes analyze_defers_cross_file_edges_until_target_entity_batch_arrives -- --nocapture` +- `cargo test -p loomweave-core --test host_subprocess t9 -- --nocapture` +- `cargo test -p loomweave-storage --test guidance_write insert_guidance_sheet_rejects_existing_id_without_overwrite -- --nocapture` +- `cargo test -p loomweave-mcp scan_truncates -- --nocapture` +- `cargo test -p loomweave-federation -- --nocapture` +- `cargo test -p loomweave-cli http_read::auth::tests::hmac -- --nocapture` +- `cargo test -p loomweave-cli http_read::tests -- --nocapture` +- `cargo test -p loomweave-cli http_read::wardline::tests -- --nocapture` +- `cargo test -p loomweave-cli http_read::linkages::tests -- --nocapture` +- `cargo test -p loomweave-cli --test serve hmac_identity -- --nocapture` +- `cargo test -p loomweave-cli --test serve serve_http_responses_match_federation_fixture_contracts -- --nocapture` +- `cargo test -p loomweave-cli --test analyze analyze_stamps_entities_with_git_head_commit -- --nocapture` +- `cargo test -p loomweave-cli analyze::tests::source_walk -- --nocapture` +- `cargo test -p loomweave-cli --test analyze analyze_migrates_a_stale_db_instead_of_failing -- --nocapture` +- `cargo test -p loomweave-cli --test install install_applies_each_migration_exactly_once -- --nocapture` +- `cargo test -p loomweave-cli --test wp1_e2e wp1_walking_skeleton_end_to_end -- --nocapture` +- `cargo test -p loomweave-cli --test analyze_failure_modes analyze_defers_cross_file_edges_until_target_entity_batch_arrives -- --nocapture` - `plugins/python/.venv/bin/pytest plugins/python/tests/test_package.py -q` - `plugins/python/.venv/bin/pytest plugins/python/tests/test_server.py::test_malformed_non_ascii_header_uses_protocol_error_exit_path -q` diff --git a/docs/implementation/handoffs/2026-04-18-wp2-plugin-host-handoff.md b/docs/implementation/handoffs/2026-04-18-wp2-plugin-host-handoff.md index 177dbecc..b38debda 100644 --- a/docs/implementation/handoffs/2026-04-18-wp2-plugin-host-handoff.md +++ b/docs/implementation/handoffs/2026-04-18-wp2-plugin-host-handoff.md @@ -1,4 +1,4 @@ -# Clarion Sprint 1 WP2 — Plugin host + hybrid authority (handoff prompt) +# Loomweave Sprint 1 WP2 — Plugin host + hybrid authority (handoff prompt) This file is the starting prompt for a fresh Claude Code session that will execute WP2. It is designed to be pasted directly as the user's first @@ -7,7 +7,7 @@ should not need prior conversation context to pick up cleanly. --- -# Continue Clarion Sprint 1 WP2 execution +# Continue Loomweave Sprint 1 WP2 execution I'm handing off WP2 (plugin host + hybrid authority) after completing WP1 (scaffold + storage). You are continuing the same sprint with the same @@ -15,16 +15,16 @@ discipline. This prompt is the full handoff. ## Working directory + branch -- Directory: `/home/john/clarion` +- Directory: `/home/john/loomweave` - Branch: `sprint-1/wp2-plugin-host` (already checked out; off `main` tip `ad8d4ce` which is the WP1 merge commit) - `main` has WP1 already merged via `--no-ff` so `git log --first-parent main` shows WP-level boundaries. - Do NOT amend existing commits; stack new commits on top. ## The project -Clarion is a code-archaeology tool, one of four products in the Loom suite -(Clarion, Filigree, Wardline, proposed Shuttle). Read `CLAUDE.md` at repo -root for the doctrine, ADR precedence rules, and the Loom federation axiom. +Loomweave is a code-archaeology tool, one of four products in the Weft suite +(Loomweave, Filigree, Wardline, proposed Shuttle). Read `CLAUDE.md` at repo +root for the doctrine, ADR precedence rules, and the Weft federation axiom. Sprint 1 ships the walking-skeleton across WP1 (scaffold + storage — DONE), WP2 (plugin host — in progress), WP3 (Python plugin — blocked-by WP2). @@ -32,20 +32,20 @@ You are executing WP2. ## What WP1 delivered (already on `main`) -- Cargo workspace (edition 2024, rustc 1.95) with three crates: `clarion-core`, `clarion-storage`, `clarion-cli`. +- Cargo workspace (edition 2024, rustc 1.95) with three crates: `loomweave-core`, `loomweave-storage`, `loomweave-cli`. - Tooling floor per **ADR-023**: `rust-toolchain.toml`, `rustfmt.toml`, `clippy.toml` (pedantic), `deny.toml`, `.github/workflows/ci.yml`, workspace `[lints]`. -- `clarion-core`: `entity_id()` assembler (L2 3-segment format per ADR-003 + ADR-022), `EntityIdError`, `LlmProvider` trait + `NoopProvider` stub. -- `clarion-storage`: SQLite schema migration `0001_initial_schema.sql` (L1 per detailed-design §3 — tables, FTS5, triggers, generated columns, `guidance_sheets` view), `ReaderPool` (deadpool-sqlite), `Writer` actor (L3 per ADR-011 — `tokio::task::spawn_blocking`, bounded mpsc, per-N batch COMMIT, WriterCmd enum with oneshot acks, `WriterProtocol` error variant for contract violations). -- `clarion-cli`: `clarion install` (creates `.clarion/{clarion.db,config.json,.gitignore}` + `clarion.yaml`; ADR-005 governs the `.gitignore` contents), `clarion analyze` (Sprint 1 stub — BeginRun → CommitRun with status `skipped_no_plugins`; WP2 replaces this stub). +- `loomweave-core`: `entity_id()` assembler (L2 3-segment format per ADR-003 + ADR-022), `EntityIdError`, `LlmProvider` trait + `NoopProvider` stub. +- `loomweave-storage`: SQLite schema migration `0001_initial_schema.sql` (L1 per detailed-design §3 — tables, FTS5, triggers, generated columns, `guidance_sheets` view), `ReaderPool` (deadpool-sqlite), `Writer` actor (L3 per ADR-011 — `tokio::task::spawn_blocking`, bounded mpsc, per-N batch COMMIT, WriterCmd enum with oneshot acks, `WriterProtocol` error variant for contract violations). +- `loomweave-cli`: `loomweave install` (creates `.loomweave/{loomweave.db,config.json,.gitignore}` + `loomweave.yaml`; ADR-005 governs the `.gitignore` contents), `loomweave analyze` (Sprint 1 stub — BeginRun → CommitRun with status `skipped_no_plugins`; WP2 replaces this stub). - 48 tests pass, all 7 ADR-023 gates green (build dev + release, fmt, clippy pedantic `-D warnings`, nextest, doc, deny). **Key exported API that WP2 will consume**: -- `clarion_storage::{Writer, DEFAULT_BATCH_SIZE, DEFAULT_CHANNEL_CAPACITY, EntityRecord, RunStatus, WriterCmd}`. +- `loomweave_storage::{Writer, DEFAULT_BATCH_SIZE, DEFAULT_CHANNEL_CAPACITY, EntityRecord, RunStatus, WriterCmd}`. - `Writer::send_wait(|ack| WriterCmd::...) -> Result` — canonical async helper for enqueuing commands. The WP1 review loop made this the preferred pattern over hand-rolling oneshot channels. - `WriterCmd::InsertEntity { entity: Box, ack }` — note the `Box` on the entity; it is required by clippy's `large_enum_variant` and is part of the L3 locked-in shape. -- `clarion_core::{entity_id, EntityId, EntityIdError, LlmProvider, NoopProvider}`. -- `clarion_storage::StorageError` variants: `Sqlite(#[from] rusqlite::Error)`, `Pool*(#[from] deadpool)`, `PragmaInvariant(String)`, `Migration{version, source}`, `Io(#[from] io::Error)`, `WriterGone`, `WriterProtocol(String)`, `WriterNoResponse`. `StorageError` is `!Sync`; bridge to `anyhow` via `.map_err(|e| anyhow::anyhow!("{e}"))`. +- `loomweave_core::{entity_id, EntityId, EntityIdError, LlmProvider, NoopProvider}`. +- `loomweave_storage::StorageError` variants: `Sqlite(#[from] rusqlite::Error)`, `Pool*(#[from] deadpool)`, `PragmaInvariant(String)`, `Migration{version, source}`, `Io(#[from] io::Error)`, `WriterGone`, `WriterProtocol(String)`, `WriterNoResponse`. `StorageError` is `!Sync`; bridge to `anyhow` via `.map_err(|e| anyhow::anyhow!("{e}"))`. ## The spec @@ -60,7 +60,7 @@ Outline (9 tasks): 5. Plugin discovery (L9). 6. Plugin-host supervisor. 7. Crash-loop breaker. -8. Wire `clarion analyze` to use the plugin host (replaces the Sprint 1 stub). +8. Wire `loomweave analyze` to use the plugin host (replaces the Sprint 1 stub). 9. WP2 end-to-end smoke test. Anchoring ADRs: **ADR-002** (plugin transport JSON-RPC over stdio), **ADR-021** (plugin authority hybrid — declared capabilities + core-enforced minimums), **ADR-022** (core/plugin ontology ownership boundary). @@ -103,7 +103,7 @@ below. - **WP1 issue** `clarion-2eadcfe651` is `delivered`. Do not touch it. -- **Pending observation** `clarion-obs-67175f4486`: priority generated +- **Pending observation** `loomweave-obs-67175f4486`: priority generated column TEXT affinity breaks lexicographic ordering for numeric priorities. Flagged during WP1 Task 3 review. It is a design-doc question (detailed-design.md §3 §737), not a WP2 task. Leave it pending @@ -118,7 +118,7 @@ from Task 1: 1. **Cargo hygiene** - `cargo nextest run` needs `--no-tests=pass` in any CI / script / doc command. - - Internal path deps pin a version: `clarion-core = { path = "...", version = "0.1.0-dev" }` to satisfy `cargo-deny`'s `wildcards = "deny"`. + - Internal path deps pin a version: `loomweave-core = { path = "...", version = "0.1.0-dev" }` to satisfy `cargo-deny`'s `wildcards = "deny"`. - New workspace deps go in `[workspace.dependencies]` and are referenced via `dep.workspace = true` from crate manifests. - `Cargo.lock` is committed. - No `#[allow(...)]` for pedantic warnings — fix in-code. @@ -172,9 +172,9 @@ from Task 1: - **L4 — JSON-RPC method set + Content-Length framing (ADR-002)**: `initialize`, `initialized`, `analyze_file`, `shutdown`, `exit`. Reviewer hotspots: Content-Length header parsing (malformed framing, oversized payloads per L6's 8 MiB ceiling), JSON-RPC error code fidelity, mid-message disconnect handling. - **L5 — `plugin.toml` schema (ADR-022)**: reviewer hotspots: missing-required-field errors, ontology declaration shape (`[ontology].entity_kinds` drives host-side filtering). - **L6 — core-enforced minimums (ADR-021)**: path jail (drop-not-kill on first escape; >10/60s → kill = sub-breaker), 8 MiB Content-Length ceiling, 500k per-run entity cap, 2 GiB `RLIMIT_AS`. Reviewer hotspots: each invariant needs a positive and a negative test; the sub-breaker's sliding-window logic is a timing hazard (flake-check 3x for any test that relies on wall-clock). -- **L9 — plugin discovery**: finds `clarion-plugin-*` binaries on `$PATH`, loads neighbouring `plugin.toml`. Reviewer hotspots: cross-platform `$PATH` handling (WP2 scope is Linux but don't assume POSIX everywhere in code comments), missing-manifest errors, multiple-plugins-with-same-name behaviour. +- **L9 — plugin discovery**: finds `loomweave-plugin-*` binaries on `$PATH`, loads neighbouring `plugin.toml`. Reviewer hotspots: cross-platform `$PATH` handling (WP2 scope is Linux but don't assume POSIX everywhere in code comments), missing-manifest errors, multiple-plugins-with-same-name behaviour. -Additionally, WP2 Task 8 **wires `clarion analyze` to use the plugin host**. The current Sprint-1 stub ends with `RunStatus::SkippedNoPlugins`; after WP2 Task 8 it should use `RunStatus::Completed` when at least one plugin runs and produces output. Keep `SkippedNoPlugins` as a valid status for the no-plugins-installed case. +Additionally, WP2 Task 8 **wires `loomweave analyze` to use the plugin host**. The current Sprint-1 stub ends with `RunStatus::SkippedNoPlugins`; after WP2 Task 8 it should use `RunStatus::Completed` when at least one plugin runs and produces output. Keep `SkippedNoPlugins` as a valid status for the no-plugins-installed case. ## Execution protocol — FULL DISCIPLINE per task @@ -235,7 +235,7 @@ from WP1 practice; adjust task specifics): 1. Run `mcp__filigree__get_issue clarion-9dee2d24c3` to see current WP2 state. 2. Read `docs/implementation/sprint-1/wp2-plugin-host.md` end-to-end. -3. Read `docs/clarion/adr/ADR-002-plugin-transport-json-rpc.md`, `ADR-021-plugin-authority-hybrid.md`, `ADR-022-core-plugin-ontology.md`. +3. Read `docs/loomweave/adr/ADR-002-plugin-transport-json-rpc.md`, `ADR-021-plugin-authority-hybrid.md`, `ADR-022-core-plugin-ontology.md`. 4. Optional: skim `docs/superpowers/plans/2026-04-18-wp1-scaffold-storage.md` to see the plan shape. 5. Invoke `superpowers:writing-plans` and produce `docs/superpowers/plans/-wp2-plugin-host.md`. 6. Optional: run `axiom-planning:plan-review` on the plan. diff --git a/docs/implementation/handoffs/2026-04-19-wp2-tasks-4-to-9-handoff.md b/docs/implementation/handoffs/2026-04-19-wp2-tasks-4-to-9-handoff.md index 0fea4cae..a3fe8049 100644 --- a/docs/implementation/handoffs/2026-04-19-wp2-tasks-4-to-9-handoff.md +++ b/docs/implementation/handoffs/2026-04-19-wp2-tasks-4-to-9-handoff.md @@ -1,4 +1,4 @@ -# Clarion Sprint 1 WP2 — Remaining Tasks 4–9 (handoff prompt) +# Loomweave Sprint 1 WP2 — Remaining Tasks 4–9 (handoff prompt) This file is the starting prompt for a fresh Claude Code session that will execute WP2 Tasks 4–9 (core-enforced minimums, plugin discovery, plugin-host @@ -10,7 +10,7 @@ and was written before Tasks 1–3 landed. --- -# Continue Clarion Sprint 1 WP2 — Tasks 4 through 9 +# Continue Loomweave Sprint 1 WP2 — Tasks 4 through 9 You are picking up WP2 (plugin protocol + hybrid authority) after Tasks 1–3 landed in the previous session. Tasks 4–9 remain. Continue the same @@ -19,7 +19,7 @@ clean on every commit. ## Working directory + branch -- Directory: `/home/john/clarion` +- Directory: `/home/john/loomweave` - Branch: `sprint-1/wp2-plugin-host` - Current HEAD: `bd56cd5` (pre-Task-4 hardening) - Merge base with `main`: `ad8d4ce` (WP1 merge commit) @@ -42,11 +42,11 @@ clean at every commit. Plan spec: `docs/implementation/sprint-1/wp2-plugin-host.md` §6 Task ledger. - **Task 4** — Core-enforced minimums (L6): `plugin/jail.rs`, `plugin/limits.rs`. Path jail, Content-Length ceiling (8 MiB), entity-count cap (500k), `apply_prlimit_as` (2 GiB RSS, Linux-only). -- **Task 5** — Plugin discovery (L9): `plugin/discovery.rs`. PATH scan for `clarion-plugin-*` binaries + neighbouring `plugin.toml`. +- **Task 5** — Plugin discovery (L9): `plugin/discovery.rs`. PATH scan for `loomweave-plugin-*` binaries + neighbouring `plugin.toml`. - **Task 6** — Plugin-host supervisor: `plugin/host.rs`. Spawn subprocess, handshake, request-response loop, jail enforcement on response paths, ontology boundary enforcement, identity-mismatch rejection, shutdown+exit on drop. **Largest task.** - **Task 7** — Crash-loop breaker: `plugin/breaker.rs`. Parameters per ADR-002 + ADR-021 §Layer 3 (>3 crashes/60s general; >10 path-escapes/60s path-escape sub-breaker). -- **Task 8** — Wire `clarion analyze` to plugin host: modify `clarion-cli/src/analyze.rs`. Discover, spawn, iterate files by extension, persist entities via WP1's writer-actor. -- **Task 9** — WP2 E2E smoke test: `clarion-cli/tests/wp2_e2e.rs`. `clarion install` + `clarion analyze fixture_dir/` produces a completed run with persisted mock-plugin entities. +- **Task 8** — Wire `loomweave analyze` to plugin host: modify `loomweave-cli/src/analyze.rs`. Discover, spawn, iterate files by extension, persist entities via WP1's writer-actor. +- **Task 9** — WP2 E2E smoke test: `loomweave-cli/tests/wp2_e2e.rs`. `loomweave install` + `loomweave analyze fixture_dir/` produces a completed run with persisted mock-plugin entities. ## Methodology @@ -70,16 +70,16 @@ Each task ends with one commit. Commit message template: ## Doctrine (read before touching code) -- **`docs/suite/loom.md`** — Loom federation axiom. Any "wouldn't it be +- **`docs/suite/weft.md`** — Weft federation axiom. Any "wouldn't it be easier if we just added X" proposal must pass the §5 failure test. - **`docs/implementation/sprint-1/wp2-plugin-host.md`** — the plan you are executing. §6 Task ledger is your scope boundary. §L5, L6, L9 are the lock-ins. -- **`docs/clarion/adr/ADR-002-plugin-transport-json-rpc.md`** — transport. -- **`docs/clarion/adr/ADR-021-plugin-authority-hybrid.md`** — the four +- **`docs/loomweave/adr/ADR-002-plugin-transport-json-rpc.md`** — transport. +- **`docs/loomweave/adr/ADR-021-plugin-authority-hybrid.md`** — the four core-enforced minimums. §Layer 1 is the manifest runtime sub-block (already implemented in Task 1). §Layer 2 is what Task 4 implements. -- **`docs/clarion/adr/ADR-022-core-plugin-ontology.md`** — identifier +- **`docs/loomweave/adr/ADR-022-core-plugin-ontology.md`** — identifier grammar, reserved kinds, rule-ID namespace. Task 6's ontology-boundary enforcement cites this. - **`docs/implementation/sprint-1/signoffs.md`** — Tier A A.2.1–A.2.12. @@ -90,7 +90,7 @@ Each task ends with one commit. Commit message template: ## Build / test commands (ADR-023 gates — run on every commit) ```bash -cargo nextest run -p clarion-core # unit tests +cargo nextest run -p loomweave-core # unit tests cargo nextest run --workspace --all-features # full suite cargo fmt --all -- --check # formatting cargo clippy --workspace --all-targets --all-features -- -D warnings # lint (pedantic level warn) @@ -114,7 +114,7 @@ equivalent; titles here for orientation: - `clarion-fa35cad487` P4 — extension values not validated. When Task 5 or Task 8 implements extension-to-file-matching, decide the format grammar (`"py"` vs `".py"` vs `"*.py"`) and add parser validation. **Related observation (P3):** -- `clarion-obs-cc9fe7d44c` — **important for Task 6**. The mock's `tick()` preserves the full failing frame on dispatch error so subsequent ticks re-attempt it. This works for the mock (caller controls lifecycle) but Task 6's real supervisor must not copy the pattern unchanged — a plugin that emits a permanently-bad frame would make the supervisor loop forever. Real supervisor should advance past unrecoverable frames and/or count per-frame failures. +- `loomweave-obs-cc9fe7d44c` — **important for Task 6**. The mock's `tick()` preserves the full failing frame on dispatch error so subsequent ticks re-attempt it. This works for the mock (caller controls lifecycle) but Task 6's real supervisor must not copy the pattern unchanged — a plugin that emits a permanently-bad frame would make the supervisor loop forever. Real supervisor should advance past unrecoverable frames and/or count per-frame failures. **Cleanup backlog (P3/P4, can be done anytime, not blockers):** - `clarion-078814da2d` P3 — rule-ID prefix doc comment `*` vs `+` @@ -145,16 +145,16 @@ Key points from the plan: - `PathEscapeBreaker` — rolling counter, trips at >10 escapes in 60 seconds (ADR-021 §2a sub-breaker). Consumed by Task 6's host when `JailError::EscapedRoot` observed on a response. - `apply_prlimit_as(max_rss_mib: u64)` using `nix::sys::resource::setrlimit` inside `CommandExt::pre_exec`. 2 GiB default (ADR-021 §2d). Effective limit = `min(manifest.capabilities.runtime.expected_max_rss_mb, core_default)`. **`#[cfg(target_os = "linux")]`-gate** per UQ-WP2-06; on non-Linux, log-once warning. -- **New dep**: `nix = "0.28"` or `rustix` (pick one, prefer `nix` per plan §4). Add to workspace deps and clarion-core's `Cargo.toml`. +- **New dep**: `nix = "0.28"` or `rustix` (pick one, prefer `nix` per plan §4). Add to workspace deps and loomweave-core's `Cargo.toml`. -- **Deferred subcode** (plan §L6 note): `CLA-INFRA-PLUGIN-ENTITY-OVERRUN-WARNING`. Sprint 1 is one-file-per-invocation, no useful surface. Document the deferral in limits.rs; do not implement. +- **Deferred subcode** (plan §L6 note): `LMWV-INFRA-PLUGIN-ENTITY-OVERRUN-WARNING`. Sprint 1 is one-file-per-invocation, no useful surface. Document the deferral in limits.rs; do not implement. - **Finding subcodes** this task must emit: - - `CLA-INFRA-PLUGIN-PATH-ESCAPE` — first-offense path escape (drop entity, plugin stays alive) - - `CLA-INFRA-PLUGIN-DISABLED-PATH-ESCAPE` — sub-breaker tripped (kill plugin) - - `CLA-INFRA-PLUGIN-FRAME-OVERSIZE` — Content-Length ceiling exceeded - - `CLA-INFRA-PLUGIN-ENTITY-CAP` — entity-count cap exceeded - - `CLA-INFRA-PLUGIN-OOM-KILLED` — plugin killed by RLIMIT_AS (detected via `WIFSIGNALED && WTERMSIG == 9`) + - `LMWV-INFRA-PLUGIN-PATH-ESCAPE` — first-offense path escape (drop entity, plugin stays alive) + - `LMWV-INFRA-PLUGIN-DISABLED-PATH-ESCAPE` — sub-breaker tripped (kill plugin) + - `LMWV-INFRA-PLUGIN-FRAME-OVERSIZE` — Content-Length ceiling exceeded + - `LMWV-INFRA-PLUGIN-ENTITY-CAP` — entity-count cap exceeded + - `LMWV-INFRA-PLUGIN-OOM-KILLED` — plugin killed by RLIMIT_AS (detected via `WIFSIGNALED && WTERMSIG == 9`) - **Tests**: each minimum needs ≥1 positive and ≥1 negative test per signoff A.2.3. @@ -162,11 +162,11 @@ Key points from the plan: **Files**: create `plugin/discovery.rs`. -- UQ-WP2-01 resolution (plan §L9): **PATH-based scan** for executables matching `clarion-plugin-*`, plus manifest load from either (a) next to the binary or (b) `/share/clarion/plugins//plugin.toml`. Neighbor-to-binary first, then install-prefix. +- UQ-WP2-01 resolution (plan §L9): **PATH-based scan** for executables matching `loomweave-plugin-*`, plus manifest load from either (a) next to the binary or (b) `/share/loomweave/plugins//plugin.toml`. Neighbor-to-binary first, then install-prefix. - **Extension-grammar trigger**: ticket `clarion-fa35cad487`. If this task touches extension string handling, decide the format now (likely lowercase, no dot, no wildcard) and add validation to `manifest.rs::parse_manifest`. Close or update the ticket. -- Test: discovery finds a mock `clarion-plugin-*` binary on a test `$PATH` and loads its manifest from the expected location. +- Test: discovery finds a mock `loomweave-plugin-*` binary on a test `$PATH` and loads its manifest from the expected location. ### Task 6 — Plugin-host supervisor (largest task) @@ -180,17 +180,17 @@ This is the integration layer — Tasks 1–5 are building blocks; Task 6 wires - **String paths**: all `project_root` / `file_path` flowing through protocol are `String`. Convert from `PathBuf` via the jail helper at boundaries (jail owns UTF-8 validation per ticket `clarion-77c6971e81`). -- **Reads-outside-project-root refusal** (ADR-021 §Layer 1, already tested at parser level): before sending `initialized`, call `manifest.validate_for_v0_1()`. On `UnsupportedCapability`, emit `CLA-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY`, send `shutdown` + `exit`, do NOT dispatch `analyze_file`. Signoff A.2.12. +- **Reads-outside-project-root refusal** (ADR-021 §Layer 1, already tested at parser level): before sending `initialized`, call `manifest.validate_for_v0_1()`. On `UnsupportedCapability`, emit `LMWV-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY`, send `shutdown` + `exit`, do NOT dispatch `analyze_file`. Signoff A.2.12. -- **Ontology enforcement** (ADR-022, signoff A.2.5): drop entities whose `kind` is not in `manifest.ontology.entity_kinds`. Emit `CLA-INFRA-PLUGIN-UNDECLARED-KIND`. +- **Ontology enforcement** (ADR-022, signoff A.2.5): drop entities whose `kind` is not in `manifest.ontology.entity_kinds`. Emit `LMWV-INFRA-PLUGIN-UNDECLARED-KIND`. -- **Identity-mismatch enforcement** (UQ-WP2-11, signoff A.2.6): reconstruct `EntityId` from `(plugin_id, kind, qualified_name)` and compare against the returned `id`. Mismatch → drop + `CLA-INFRA-PLUGIN-ENTITY-ID-MISMATCH`. +- **Identity-mismatch enforcement** (UQ-WP2-11, signoff A.2.6): reconstruct `EntityId` from `(plugin_id, kind, qualified_name)` and compare against the returned `id`. Mismatch → drop + `LMWV-INFRA-PLUGIN-ENTITY-ID-MISMATCH`. -- **Jail on response paths** (ADR-021 §2a): for each returned entity/edge/finding, jail each `source.file_path` and evidence anchor path. On `JailError::EscapedRoot`: drop record, emit `CLA-INFRA-PLUGIN-PATH-ESCAPE`, tick `PathEscapeBreaker`. If breaker trips: kill plugin, emit `CLA-INFRA-PLUGIN-DISABLED-PATH-ESCAPE`. +- **Jail on response paths** (ADR-021 §2a): for each returned entity/edge/finding, jail each `source.file_path` and evidence anchor path. On `JailError::EscapedRoot`: drop record, emit `LMWV-INFRA-PLUGIN-PATH-ESCAPE`, tick `PathEscapeBreaker`. If breaker trips: kill plugin, emit `LMWV-INFRA-PLUGIN-DISABLED-PATH-ESCAPE`. -- **Inbox drain strategy** (observation `clarion-obs-cc9fe7d44c`): do NOT copy the mock's `tick()` pattern of preserving the failing frame. Real supervisor must advance past unrecoverable failing frames or the plugin will stall. Pick: (a) advance past; (b) per-frame failure count with bail-out; (c) hybrid. +- **Inbox drain strategy** (observation `loomweave-obs-cc9fe7d44c`): do NOT copy the mock's `tick()` pattern of preserving the failing frame. Real supervisor must advance past unrecoverable failing frames or the plugin will stall. Pick: (a) advance past; (b) per-frame failure count with bail-out; (c) hybrid. -- **Crate-root re-export prune** (ticket `clarion-29acbcd042`): once `PluginHost` is the façade, trim `lib.rs` re-exports down to `PluginHost`, `Manifest`, `ManifestError`, `parse_manifest`, maybe `JailError`, `LimitError`, `EntityIdError`. Leave implementation types (`Frame`, `RequestEnvelope`, `TransportError`, etc.) accessible via `clarion_core::plugin::transport::*` for tests but not at the crate root. +- **Crate-root re-export prune** (ticket `clarion-29acbcd042`): once `PluginHost` is the façade, trim `lib.rs` re-exports down to `PluginHost`, `Manifest`, `ManifestError`, `parse_manifest`, maybe `JailError`, `LimitError`, `EntityIdError`. Leave implementation types (`Frame`, `RequestEnvelope`, `TransportError`, etc.) accessible via `loomweave_core::plugin::transport::*` for tests but not at the crate root. - **Integrations forwarding** (ticket `clarion-e46503831c`): does the supervisor need to forward `manifest.integrations` to the plugin? Check WP3 Task 6 plan. If yes, decide the type (either document `toml::Value` exposure or switch to `BTreeMap>`). If no, defer the ticket. @@ -200,7 +200,7 @@ This is the integration layer — Tasks 1–5 are building blocks; Task 6 wires **Files**: create `plugin/breaker.rs`. -- Parameters per UQ-WP2-10 resolution: **>3 crashes in 60s** → plugin disabled, `CLA-INFRA-PLUGIN-DISABLED-CRASH-LOOP`. Hard-coded for Sprint 1; config surface deferred to WP6. +- Parameters per UQ-WP2-10 resolution: **>3 crashes in 60s** → plugin disabled, `LMWV-INFRA-PLUGIN-DISABLED-CRASH-LOOP`. Hard-coded for Sprint 1; config surface deferred to WP6. - Uses `MockPlugin::new_crashing()` — the state guards from commit `bd56cd5` ensure crash-loop tests don't silently re-initialise. @@ -208,9 +208,9 @@ This is the integration layer — Tasks 1–5 are building blocks; Task 6 wires - Signoff A.2.7. -### Task 8 — Wire `clarion analyze` +### Task 8 — Wire `loomweave analyze` -**Files**: modify `clarion-cli/src/analyze.rs` (existing skeleton from WP1's Task 8 commit `10005e8`). +**Files**: modify `loomweave-cli/src/analyze.rs` (existing skeleton from WP1's Task 8 commit `10005e8`). - Discover plugins via Task 5's `discovery.rs`. - For each discovered plugin, spawn via Task 6's `PluginHost::spawn`. @@ -222,10 +222,10 @@ This is the integration layer — Tasks 1–5 are building blocks; Task 6 wires ### Task 9 — E2E smoke test -**Files**: create `clarion-cli/tests/wp2_e2e.rs`. +**Files**: create `loomweave-cli/tests/wp2_e2e.rs`. - Integration test using the fixture mock-plugin binary (from Task 6) and a test `$PATH`. -- `clarion install` in a tempdir + `clarion analyze fixture_dir/` produces a completed `runs` row with the mock's expected entity persisted. +- `loomweave install` in a tempdir + `loomweave analyze fixture_dir/` produces a completed `runs` row with the mock's expected entity persisted. - Signoff A.2.8 (same as Task 8; this is the E2E proof). @@ -257,7 +257,7 @@ When WP2 is done, the user can choose to either merge to main (via 5. **Header-line memory DoS** — `BufRead::read_line` is unbounded. Use `MAX_HEADER_LINE_BYTES = 8 * 1024` cap. Already fixed in Task 2. -6. **Reserved prefix check must run AFTER grammar check** — `CLA-INFRA-` passes the grammar `CLA-[A-Z]+(-[A-Z0-9]+)+` and must be rejected as `ReservedPrefix`, not `Malformed`. Task 1 already does this right; mirror in any grammar work Task 4/5 add. +6. **Reserved prefix check must run AFTER grammar check** — `LMWV-INFRA-` passes the grammar `LMWV-[A-Z]+(-[A-Z0-9]+)+` and must be rejected as `ReservedPrefix`, not `Malformed`. Task 1 already does this right; mirror in any grammar work Task 4/5 add. 7. **`plugin_id` ≠ `plugin.name`** — commit `bd56cd5` split these. Anywhere that needs to feed `entity_id()`, use `manifest.plugin.plugin_id`. `name` is informational / human-readable. @@ -268,11 +268,11 @@ When WP2 is done, the user can choose to either merge to main (via Before dispatching Task 4's first subagent, confirm the starting state: ```bash -cd /home/john/clarion +cd /home/john/loomweave git log --oneline sprint-1/wp2-plugin-host -10 # should show: bd56cd5, b27594d, f3ca3ab, 4b20244, c2cb07a, ad8d4ce, ... -cargo nextest run -p clarion-core +cargo nextest run -p loomweave-core # should show: 74 tests, all pass cargo clippy --workspace --all-targets --all-features -- -D warnings diff --git a/docs/implementation/handoffs/2026-04-23-wp2-full-scrub-handoff.md b/docs/implementation/handoffs/2026-04-23-wp2-full-scrub-handoff.md index 395f8ec7..f18a86b8 100644 --- a/docs/implementation/handoffs/2026-04-23-wp2-full-scrub-handoff.md +++ b/docs/implementation/handoffs/2026-04-23-wp2-full-scrub-handoff.md @@ -1,4 +1,4 @@ -# Clarion Sprint 1 WP2 — Full Scrub Before WP3 (handoff prompt) +# Loomweave Sprint 1 WP2 — Full Scrub Before WP3 (handoff prompt) This file is the starting prompt for a fresh Claude Code session that will do a **second full scrub of WP2** before the project moves on to WP3 @@ -11,7 +11,7 @@ answers is *"is it actually ready to sign off A.2 and move to WP3?"* --- -# Continue Clarion Sprint 1 WP2 — full review + fix pass +# Continue Loomweave Sprint 1 WP2 — full review + fix pass You are picking up WP2 (plugin protocol + hybrid authority) **after code is in place and two review passes have already landed fixes**. The sprint @@ -26,7 +26,7 @@ third perspective usually turns up real issues the first two missed. ## Working directory + branch -- Directory: `/home/john/clarion` +- Directory: `/home/john/loomweave` - Branch: `sprint-1/wp2-plugin-host` - Current HEAD: `a1cc3be` (after the four P1 review-2 bugs landed) - Merge base with `main`: `ad8d4ce` (WP1 merge commit) @@ -43,13 +43,13 @@ the four P1 review-2 bugs closed on 2026-04-23: |---|---|---| | `5c5c3ee` | clarion-b6d7e077fd | RawEntity string fields bounded to 4 KiB (host RAM DoS) | | `0fcc57f` | clarion-64b53d174e | Reap child on `PluginHost::spawn` handshake failure (no zombies) | -| `37b56d9` | clarion-f56dc6ee43 | `clarion analyze` exits non-zero on FailRun | +| `37b56d9` | clarion-f56dc6ee43 | `loomweave analyze` exits non-zero on FailRun | | `a1cc3be` | clarion-978c8d6f15 | CrashLoopBreaker wired into analyze.rs + one-plugin-crash no longer tanks run | The last one introduced a behavioural change worth flagging to reviewers: `RunOutcome::SoftFailed` (plugin crashed, other plugins' entities still commit) vs `RunOutcome::HardFailed` (writer-actor error, rollback). See -`crates/clarion-cli/src/analyze.rs` §run-outcome. This split is new and +`crates/loomweave-cli/src/analyze.rs` §run-outcome. This split is new and warrants scrutiny — it widens the CommitRun/FailRun semantics and implicitly extends the L3 writer-actor contract. @@ -58,9 +58,9 @@ implicitly extends the L3 writer-actor contract. WP2 covers: - **L4** — JSON-RPC transport (Content-Length framing, typed protocol), - `crates/clarion-core/src/plugin/transport.rs` + `protocol.rs` + `crates/loomweave-core/src/plugin/transport.rs` + `protocol.rs` - **L5** — `plugin.toml` manifest parser/validator per ADR-022, - `crates/clarion-core/src/plugin/manifest.rs` + `crates/loomweave-core/src/plugin/manifest.rs` - **L6** — Core-enforced minimums per ADR-021 §Layer 2 (path jail, Content-Length ceiling, entity cap, `RLIMIT_AS`), `jail.rs` + `limits.rs` @@ -71,18 +71,18 @@ WP2 covers: - **Crash-loop breaker** — ADR-002 + UQ-WP2-10 (>3 crashes/60s), `breaker.rs` - **Analyze CLI wiring** — discover → walk → per-plugin spawn → - writer-actor, `crates/clarion-cli/src/analyze.rs` + writer-actor, `crates/loomweave-cli/src/analyze.rs` Anchoring documents to read-with: - `docs/implementation/sprint-1/wp2-plugin-host.md` — the WP doc - `docs/implementation/sprint-1/signoffs.md` §A.2 — the gate -- `docs/clarion/adr/ADR-002-crash-loop-breaker.md` -- `docs/clarion/adr/ADR-021-plugin-authority-hybrid.md` -- `docs/clarion/adr/ADR-022-core-plugin-ontology.md` -- `docs/clarion/adr/ADR-023-tooling-baseline.md` -- `docs/clarion/1.0/requirements.md` — REQ / NFR / CON IDs WP2 addresses -- `docs/clarion/1.0/system-design.md` §§4–6 (host, plugin protocol, limits) -- `docs/clarion/1.0/detailed-design.md` §§4–5 (wire schemas, rule catalogues) +- `docs/loomweave/adr/ADR-002-crash-loop-breaker.md` +- `docs/loomweave/adr/ADR-021-plugin-authority-hybrid.md` +- `docs/loomweave/adr/ADR-022-core-plugin-ontology.md` +- `docs/loomweave/adr/ADR-023-tooling-baseline.md` +- `docs/loomweave/1.0/requirements.md` — REQ / NFR / CON IDs WP2 addresses +- `docs/loomweave/1.0/system-design.md` §§4–6 (host, plugin protocol, limits) +- `docs/loomweave/1.0/detailed-design.md` §§4–5 (wire schemas, rule catalogues) ## Open WP2 review-2 tail at session start @@ -132,7 +132,7 @@ the same code but from different angles, and finding overlap is signal. 1. **`axiom-rust-engineering:rust-code-reviewer`** on the four WP2 source modules: `plugin/host.rs`, `plugin/transport.rs`, - `plugin/jail.rs`, `plugin/limits.rs`. Also `crates/clarion-cli/src/analyze.rs`. + `plugin/jail.rs`, `plugin/limits.rs`. Also `crates/loomweave-cli/src/analyze.rs`. Ask specifically about: error handling integrity, API surface, async correctness, lifetime soundness. *Do not* ask for style nits — clippy pedantic already caught those. @@ -151,9 +151,9 @@ the same code but from different angles, and finding overlap is signal. across the fork/exec boundary. 4. **`ordis-quality-engineering:test-suite-reviewer`** on - `crates/clarion-core/src/plugin/*/tests` and - `crates/clarion-core/tests/host_subprocess.rs` + - `crates/clarion-cli/tests/wp2_e2e.rs` + `.../analyze.rs`. Look for: + `crates/loomweave-core/src/plugin/*/tests` and + `crates/loomweave-core/tests/host_subprocess.rs` + + `crates/loomweave-cli/tests/wp2_e2e.rs` + `.../analyze.rs`. Look for: sleepy assertions, test interdependence, brittle ordering, tests that prove the mock rather than the code under test. @@ -229,10 +229,10 @@ Not a requirements list — pointers based on what I know is thin: cost of serde_json parsing a 6 MiB pathological payload? - **Discovery dedup + symlink semantics.** `discover()` finds - `clarion-plugin-*` binaries on PATH. If two PATH entries resolve to + `loomweave-plugin-*` binaries on PATH. If two PATH entries resolve to the same canonical file, what happens? If a symlink changes between discovery and spawn, what happens? If a PATH entry is a world- - writable directory, can an attacker inject a `clarion-plugin-*` and + writable directory, can an attacker inject a `loomweave-plugin-*` and have it picked up? - **Writer-actor contract.** The Reading-A′ change uses diff --git a/docs/implementation/handoffs/2026-04-23-wp2-scrub-findings.md b/docs/implementation/handoffs/2026-04-23-wp2-scrub-findings.md index 83e948ac..2331c48b 100644 --- a/docs/implementation/handoffs/2026-04-23-wp2-scrub-findings.md +++ b/docs/implementation/handoffs/2026-04-23-wp2-scrub-findings.md @@ -34,7 +34,7 @@ no-alloc, no-drop, no-unwind, and failure-path checks. No action. | 1 | `spawn_blocking` JoinError in `analyze.rs:230` bypasses CommitRun/FailRun → `runs.status` stuck at `'running'` on plugin-task panic | Rust F1 | CRITICAL | **FIX-NOW** | Same category as 37b56d9 (exit-code decoupling); blocks A.2 | | 2 | `EntityCapExceeded` never reached through `analyze_file` in any test | Coverage 1 | HIGH | **FIX-NOW** | A.2.3 signoff requires positive+negative tests; host wiring (`host.rs:674–685`) untested | | 3 | Crash-loop breaker trip is mock-proves-itself (`breaker_06`) — never drives `analyze.rs` wiring added in `a1cc3be` | Test C-1 / Coverage 5 | HIGH | **FIX-NOW** | A.2.7 signoff literally states "test with `MockPlugin::new_crashing`"; existing test does not exercise production wiring | -| 4 | `analyze_without_plugins_writes_skipped_run_row` leaks parent `$PATH` | Test C-2 | HIGH | **FIX-NOW** | Any machine with `clarion-plugin-fixture` installed fails this; A.1.8 reliability bomb | +| 4 | `analyze_without_plugins_writes_skipped_run_row` leaks parent `$PATH` | Test C-2 | HIGH | **FIX-NOW** | Any machine with `loomweave-plugin-fixture` installed fails this; A.1.8 reliability bomb | | 5 | `make_request` / `make_notification` in `protocol.rs:298,314` are `pub` with `.expect()` panic | Rust F3 | MEDIUM | **FIX-NOW** | Same shape as filed 0b1f8bc940 (`pub` footgun); 2-line visibility fix | | 6 | `walk_dir` silently swallows per-entry I/O errors (`analyze.rs:672–674`) | Rust F4 | MEDIUM | **FIX-NOW** | Same WP1 anti-pattern as `read_applied_versions`; `warn!` + counter | | 7 | T6 path-escape breaker test uses `any()` instead of pinning count to 10 | Test C-4 | MEDIUM | **FIX-NOW** | Bundle with existing `clarion-f45dd6056f` (T3 has the same weakness) — one commit, closes both | @@ -81,7 +81,7 @@ nextest, doc, deny). before return), assert `runs.status='failed'` + exit 1. 2. **Finding #3** — E2E crash-loop breaker trip test. New filigree issue. Mock plugin that crashes every call + 4+ input files, run - `clarion analyze`, assert `FINDING_DISABLED_CRASH_LOOP` and subsequent + `loomweave analyze`, assert `FINDING_DISABLED_CRASH_LOOP` and subsequent plugins skipped. 3. **Finding #2** — `EntityCapExceeded` host-level test. New filigree issue. Construct `PluginHost` with `EntityCountCap::new(2)`, feed @@ -120,7 +120,7 @@ nextest, doc, deny). **Finding #10** is the discriminating call. `host.rs:382` constructs `Command::new(&manifest.plugin.executable)` — the host runs whatever -path the manifest names, not the `clarion-plugin-*` binary discovery +path the manifest names, not the `loomweave-plugin-*` binary discovery found on `$PATH`. A malicious or simply misconfigured `plugin.toml` can put `executable = "/bin/sh"` or `executable = "python3"` and the host will run that. diff --git a/docs/implementation/handoffs/2026-04-24-post-wp2-scrub-handoff.md b/docs/implementation/handoffs/2026-04-24-post-wp2-scrub-handoff.md index e16f4247..9d54850a 100644 --- a/docs/implementation/handoffs/2026-04-24-post-wp2-scrub-handoff.md +++ b/docs/implementation/handoffs/2026-04-24-post-wp2-scrub-handoff.md @@ -1,4 +1,4 @@ -# Clarion Sprint 1 — post-WP2-scrub handoff +# Loomweave Sprint 1 — post-WP2-scrub handoff This file is the starting prompt for the next Claude Code session after the WP2 phase-3 scrub closed out on 2026-04-24. Paste it as the user's @@ -10,7 +10,7 @@ all legitimately FILE-ONLY with documented deferral rationale. --- -# Continue Clarion Sprint 1 — WP2 signoff close + WP3 kickoff +# Continue Loomweave Sprint 1 — WP2 signoff close + WP3 kickoff WP2 is code-complete and ready to tick. WP3 (Python plugin) is the next work package. This session's job is the human-gated A.2 signoff @@ -23,7 +23,7 @@ perspective on WP3 will inevitably surface things WP2 missed. ## Working directory + branch -- Directory: `/home/john/clarion` +- Directory: `/home/john/loomweave` - Branch: `sprint-1/wp2-plugin-host` - Current HEAD: `7c0e396` (last WP2 scrub commit — world-writable dir refusal) @@ -168,7 +168,7 @@ This is the bigger slice. WP3 builds an editable Python package at lock-ins: - **L7**: qualname reconstruction per - `docs/clarion/1.0/detailed-design.md §§4–5` (module-level, nested, + `docs/loomweave/1.0/detailed-design.md §§4–5` (module-level, nested, class, async, nested-class). Shared test fixture at `/fixtures/entity_id.json` must pass byte-for-byte in both Rust and Python — this is the L2+L7 alignment proof (A.3.4). @@ -189,7 +189,7 @@ The scrub changed several surfaces that WP3 will touch: a third argument (the discovered binary path). Manifest's `plugin.executable` must be a bare basename that matches the discovered filename, or `HostError::Spawn` fires before exec. WP3's - Python plugin must declare `executable = "clarion-plugin-python"` + Python plugin must declare `executable = "loomweave-plugin-python"` — no paths. 2. **Plugin's stderr is piped, not inherited.** The Python plugin's @@ -214,7 +214,7 @@ The scrub changed several surfaces that WP3 will touch: 5. **Resource limits on the plugin child**: `RLIMIT_AS` from manifest's `expected_max_rss_mb` (min of that and 2 GiB default), plus fixed `RLIMIT_NOFILE = 256`, `RLIMIT_NPROC = 32`. The Python plugin boot - path (`python3 -m clarion_plugin_python`) must fit — CPython alone + path (`python3 -m loomweave_plugin_python`) must fit — CPython alone takes ~30 MiB, plus imports. Realistic RSS ceiling for the Python plugin is 256 MiB+ (set in `plugin.toml`). @@ -237,22 +237,22 @@ The scrub changed several surfaces that WP3 will touch: ### WP2 (what's in place, don't edit unless fixing a bug) -- `crates/clarion-core/src/plugin/host.rs` (~2400 lines; T1–T11 tests +- `crates/loomweave-core/src/plugin/host.rs` (~2400 lines; T1–T11 tests plus 4 new coverage-gap tests) -- `crates/clarion-core/src/plugin/protocol.rs` (ProtocolError has +- `crates/loomweave-core/src/plugin/protocol.rs` (ProtocolError has custom Deserialize now; `AnalyzeFileResult` is typed) -- `crates/clarion-core/src/plugin/discovery.rs` (world-writable check, +- `crates/loomweave-core/src/plugin/discovery.rs` (world-writable check, size caps) -- `crates/clarion-core/src/plugin/manifest.rs` (integrations cap) -- `crates/clarion-core/src/plugin/limits.rs` (NOFILE/NPROC) -- `crates/clarion-cli/src/analyze.rs` (JoinError handled; +- `crates/loomweave-core/src/plugin/manifest.rs` (integrations cap) +- `crates/loomweave-core/src/plugin/limits.rs` (NOFILE/NPROC) +- `crates/loomweave-cli/src/analyze.rs` (JoinError handled; `DiscoveredPlugin.executable` threaded into spawn) ### WP3 (what you'll create) - `plugins/python/` — editable Python package; `pyproject.toml` with `ruff`, `mypy`, `pytest` configured per ADR-023 -- `plugins/python/clarion_plugin_python/` — the module +- `plugins/python/loomweave_plugin_python/` — the module - `plugins/python/tests/` — `test_qualname.py`, `test_wardline_probe.py`, `test_server.py`, `test_round_trip.py` - Shared fixture used by both Rust and Python: @@ -263,14 +263,14 @@ The scrub changed several surfaces that WP3 will touch: - `docs/implementation/sprint-1/wp3-python-plugin.md` — the WP doc - `docs/implementation/sprint-1/signoffs.md §A.3` — the gate - `docs/implementation/sprint-1/README.md §4` — lock-in table -- `docs/clarion/adr/ADR-001-language-plugin-boundary.md` -- `docs/clarion/adr/ADR-002-crash-loop-breaker.md` -- `docs/clarion/adr/ADR-003-entity-id-format.md` -- `docs/clarion/adr/ADR-007-summary-cache-keying.md` — you'll produce +- `docs/loomweave/adr/ADR-001-language-plugin-boundary.md` +- `docs/loomweave/adr/ADR-002-crash-loop-breaker.md` +- `docs/loomweave/adr/ADR-003-entity-id-format.md` +- `docs/loomweave/adr/ADR-007-summary-cache-keying.md` — you'll produce `ontology_version` but not consume it yet -- `docs/clarion/adr/ADR-023-tooling-baseline.md` — Python tooling gates -- `docs/clarion/1.0/detailed-design.md §§4–5` — qualname rules -- `docs/clarion/1.0/requirements.md` — REQ-/NFR- IDs WP3 addresses +- `docs/loomweave/adr/ADR-023-tooling-baseline.md` — Python tooling gates +- `docs/loomweave/1.0/detailed-design.md §§4–5` — qualname rules +- `docs/loomweave/1.0/requirements.md` — REQ-/NFR- IDs WP3 addresses ## Methodology diff --git a/docs/implementation/handoffs/2026-04-30-sprint-2-kickoff.md b/docs/implementation/handoffs/2026-04-30-sprint-2-kickoff.md index 73f80212..2596df0a 100644 --- a/docs/implementation/handoffs/2026-04-30-sprint-2-kickoff.md +++ b/docs/implementation/handoffs/2026-04-30-sprint-2-kickoff.md @@ -1,4 +1,4 @@ -# Clarion Sprint 2 — kickoff handoff +# Loomweave Sprint 2 — kickoff handoff This file is the starting prompt for the next Claude Code session that opens Sprint 2. Paste it as the user's first message; it is @@ -11,7 +11,7 @@ continuation of an in-flight branch. --- -# Begin Clarion Sprint 2 — Tier B (catalog-emitting) + carryover +# Begin Loomweave Sprint 2 — Tier B (catalog-emitting) + carryover Sprint 1's walking-skeleton tagged at `v0.1-sprint-1` on 2026-04-28. Tier A is fully ticked; nine lock-ins (L1–L9) are ratified. Sprint 2's @@ -26,7 +26,7 @@ that supersedes the relevant Accepted one rather than editing in place. ## Working directory + branch -- Directory: `/home/john/clarion` +- Directory: `/home/john/loomweave` - Branch: start a new branch off `main` for Sprint 2 work, e.g. `sprint-2/wp4-pipeline` or per-WP branches as you go. The `sprint-1/wp2-plugin-host` branch is deleted both locally and on @@ -36,10 +36,10 @@ that supersedes the relevant Accepted one rather than editing in place. ## Sprint 1 in one paragraph (so this handoff is self-contained) -`clarion analyze` against a fixture project with a single +`loomweave analyze` against a fixture project with a single `def hello(): ...` Python file persists exactly one entity row, `python:function:demo.hello`, with `kind="function"`, into -`.clarion/clarion.db`. The pipeline is: Rust plugin host (WP2) discovers +`.loomweave/loomweave.db`. The pipeline is: Rust plugin host (WP2) discovers the editable Python plugin (WP3) on `$PATH`, spawns it, completes a JSON-RPC handshake, sends one `analyze_file` request, receives one entity, and writes it through the writer-actor (WP1). Every stage has @@ -60,10 +60,10 @@ Mapping to v0.1-plan.md WPs: | B.1 — Phase 0 (discovery): multi-file dispatch | WP4 | Pipeline walks the source tree and dispatches one `analyze_file` per matching file per plugin. | | B.2 — Python plugin emits classes + module entities | WP3-feature-complete | Extractor lifts beyond functions; ontology manifest updated. | | B.3 — Python plugin emits `contains` edges | WP3-feature-complete | Module → function, class → method `contains` relationships emitted. | -| B.4 — `catalog.json` rendered after analyze | WP4 | File materialised under `.clarion/`; schema per `detailed-design.md` §3. | -| B.5 — Per-subsystem markdown files | WP4 | Rendered to `.clarion/`; subsystem detection may be naive (single flat subsystem) for Tier B; clustering proper is WP4 Phase 3. | +| B.4 — `catalog.json` rendered after analyze | WP4 | File materialised under `.loomweave/`; schema per `detailed-design.md` §3. | +| B.5 — Per-subsystem markdown files | WP4 | Rendered to `.loomweave/`; subsystem detection may be naive (single flat subsystem) for Tier B; clustering proper is WP4 Phase 3. | | B.6 — Demo against elspeth-slice fixture | sprint demo | `catalog.json` lists ≥95% of visible Python classes/functions. | -| B.7 — No Filigree/Wardline changes | invariant | Same rule as Sprint 1; this is still Clarion-internal work. | +| B.7 — No Filigree/Wardline changes | invariant | Same rule as Sprint 1; this is still Loomweave-internal work. | **Recommended sequencing** (no flag-day refactors; each step lands a working DB row): @@ -75,7 +75,7 @@ working DB row): `plugin.toml` since the entity-kind set changes — bumping is the ADR-007 cache-key signal that downstream caches must invalidate. 3. WP3-feature-complete: `contains` edges (B.3). This is the first - edge type Clarion has ever persisted; the writer-actor (WP1) wrote + edge type Loomweave has ever persisted; the writer-actor (WP1) wrote the schema for it but never exercised it. Watch for L3 surprises. 4. WP4 Phase 0 + Phase 1: multi-file walk + per-file dispatch (B.1). The CLI's `analyze.rs` already does a basic walk — extend it to @@ -94,10 +94,10 @@ backlog. `clarion-889200006a` — L7 qualname divergence with Wardline's `FingerprintEntry` storage. Wardline stores `(module, qualified_name)` -as separate fields; Clarion's L7 emits the combined dotted string. +as separate fields; Loomweave's L7 emits the combined dotted string. -When this fires: WP9 (Loom integrations) attempts the first -cross-product join. If Sprint 2 stays Clarion-internal (Tier B), this +When this fires: WP9 (Weft integrations) attempts the first +cross-product join. If Sprint 2 stays Loomweave-internal (Tier B), this ticket can stay deferred — but it's the natural pre-emptive read for anyone touching WP9 plans. @@ -107,7 +107,7 @@ Small, well-scoped, in code you've already touched: - `clarion-5e03cfdd21` — `read_applied_versions` uses `.ok()` and swallows DB-locked / corrupt errors. Surface them as `Err`. -- `clarion-ed5017139f` — `clarion install` leaves a partial `.clarion/` +- `clarion-ed5017139f` — `loomweave install` leaves a partial `.loomweave/` on failure, blocking re-install. Add cleanup or atomic move. - `clarion-b5b1029f5a` — `reader_pool` test uses a 100ms sleep to assert a second reader is blocked — flaky under load. Replace with a @@ -151,7 +151,7 @@ locked at `v0.1-sprint-1`. If you want one to change, write an ADR. ### From WP2 (locked on 2026-04-24) -- **Wire entity shape** (`crates/clarion-core/src/plugin/host.rs:132-154`): +- **Wire entity shape** (`crates/loomweave-core/src/plugin/host.rs:132-154`): ```json { "id": "...", @@ -172,7 +172,7 @@ locked at `v0.1-sprint-1`. If you want one to change, write an ADR. discovered binary; `[capabilities.runtime].reads_outside_project_root` must be `false` (true is rejected at handshake); `[ontology].entity_kinds` is the allowlist for emitted entity - kinds (host drops unknown kinds with `CLA-INFRA-PLUGIN-UNDECLARED-KIND`). + kinds (host drops unknown kinds with `LMWV-INFRA-PLUGIN-UNDECLARED-KIND`). When B.2 adds `class` and `module` kinds, both `plugin.toml::[ontology].entity_kinds` and the extractor must change in lockstep. @@ -216,7 +216,7 @@ locked at `v0.1-sprint-1`. If you want one to change, write an ADR. - **Five Rust gates** on every commit: fmt, clippy `-D warnings` (pedantic), nextest, doc `-D warnings`, deny. **Critical addition (CI-only)**: `cargo build --workspace --bins` - must run before `cargo nextest run` so `clarion-plugin-fixture` is + must run before `cargo nextest run` so `loomweave-plugin-fixture` is on disk for `wp2_e2e` integration tests (commit `be7fa60`). - **Four Python gates**: ruff check, ruff format check, `mypy --strict`, pytest. ruff config has pragmatic excludes for @@ -231,7 +231,7 @@ locked at `v0.1-sprint-1`. If you want one to change, write an ADR. `ruff-pre-commit@v0.15.11` and `mirrors-mypy@v1.20.2`. - **CI walking-skeleton job** runs `tests/e2e/sprint_1_walking_skeleton.sh`. Test scripts that need - `clarion analyze` to succeed must scrub `$PATH` (the runner has + `loomweave analyze` to succeed must scrub `$PATH` (the runner has world-writable dirs that trip WP2's discovery refusal — see commits `7c0e396` for the refusal and `be7fa60` for the test-side fix). diff --git a/docs/implementation/handoffs/2026-05-03-skeleton-audit.md b/docs/implementation/handoffs/2026-05-03-skeleton-audit.md index 998e0878..2ef3e496 100644 --- a/docs/implementation/handoffs/2026-05-03-skeleton-audit.md +++ b/docs/implementation/handoffs/2026-05-03-skeleton-audit.md @@ -1,11 +1,11 @@ -# Clarion v0.1 skeleton audit — vocabulary, schema, and test-debt sweep +# Loomweave v0.1 skeleton audit — vocabulary, schema, and test-debt sweep **Date**: 2026-05-03 **Trigger**: Investigating filigree issue `clarion-4cd11905e2` (`entities.priority` TEXT-affinity bug) revealed the issue body assumed P0–P4 numeric urgency while the design says `priority` is a six-level string enum (`project | subsystem | package | module | class | function`). That misframing -is itself a symptom: the same word is doing different work in Clarion vs in +is itself a symptom: the same word is doing different work in Loomweave vs in Filigree, with no ADR mediating the clash. User direction (paraphrased): *"Don't shuffle one bone of the skeleton in @@ -20,9 +20,9 @@ class of problem one issue at a time during Sprint 2. In scope: -1. **Cross-product vocabulary clashes** (Clarion vs Filigree, Clarion vs +1. **Cross-product vocabulary clashes** (Loomweave vs Filigree, Loomweave vs Wardline) — same word, different meaning, no managing ADR. -2. **Within-Clarion overloads** — same word doing distinct jobs in the +2. **Within-Loomweave overloads** — same word doing distinct jobs in the product's own model. 3. **Schema affinity / type concerns** in `0001_initial_schema.sql` — the class of bug `clarion-4cd11905e2` belongs to. @@ -42,14 +42,14 @@ Out of scope (deliberate; flag for separate audit if useful): For each candidate concept: -1. Locate every site in `docs/clarion/1.0/{requirements,system-design,detailed-design}.md`, - `docs/clarion/adr/`, `crates/clarion-storage/migrations/0001_initial_schema.sql`, +1. Locate every site in `docs/loomweave/1.0/{requirements,system-design,detailed-design}.md`, + `docs/loomweave/adr/`, `crates/loomweave-storage/migrations/0001_initial_schema.sql`, and any code path that reads/writes the column. 2. Cross-reference Filigree's vocabulary via `filigree taxonomy` and `filigree types` (the project's `.filigree/` instance, currently active). -3. Cross-reference Wardline references in Clarion docs (we don't have the - Wardline repo vendored, but ADR-015/ADR-017/ADR-018 + `loom.md` §5 - carry the cross-product names Clarion already commits to). +3. Cross-reference Wardline references in Loomweave docs (we don't have the + Wardline repo vendored, but ADR-015/ADR-017/ADR-018 + `weft.md` §5 + carry the cross-product names Loomweave already commits to). 4. Severity-rate by: - Will Tier B / WP4 / WP6 trip on this? (HIGH if yes) - Is there a managing ADR? (downgrade if yes) @@ -59,15 +59,15 @@ For each candidate concept: | ID | Concept | Class | Severity | Status | |----|---------|-------|----------|--------| -| F-1 | `priority` overload | Cross-product (Clarion ↔ Filigree) | **HIGH** | Unmanaged | -| F-2 | `critical` overload | Cross-product (Clarion ↔ Filigree) | **MEDIUM** | Unmanaged | -| F-3 | `source` triple-overload | Within-Clarion + cross-product | **MEDIUM** | Unmanaged | +| F-1 | `priority` overload | Cross-product (Loomweave ↔ Filigree) | **HIGH** | Unmanaged | +| F-2 | `critical` overload | Cross-product (Loomweave ↔ Filigree) | **MEDIUM** | Unmanaged | +| F-3 | `source` triple-overload | Within-Loomweave + cross-product | **MEDIUM** | Unmanaged | | F-4 | `tags` vs `labels` | Cross-product naming difference | **LOW** | Acceptable, document | -| F-5 | `kind` triple-overload (`entity`/`edge`/`finding`) | Within-Clarion | **LOW** | Structural disambiguation; document | -| F-6 | `status` overload (`runs`/`findings`/Filigree issues) | Within-Clarion + cross-product | **LOW** | Already partially managed (finding-status mapping per §7); document | +| F-5 | `kind` triple-overload (`entity`/`edge`/`finding`) | Within-Loomweave | **LOW** | Structural disambiguation; document | +| F-6 | `status` overload (`runs`/`findings`/Filigree issues) | Within-Loomweave + cross-product | **LOW** | Already partially managed (finding-status mapping per §7); document | | F-7 | `priority` schema affinity | Schema design | **HIGH** | Resolved by F-1 rename + new column | | F-8 | `schema_apply.rs` priority test asserts the bug | Test debt | **MEDIUM** | Resolved alongside F-1/F-7 | -| F-9 | `severity` (CLARION/FILIGREE vocabulary) | Cross-product | — | **MANAGED** by ADR-017 (model finding) | +| F-9 | `severity` (LOOMWEAVE/FILIGREE vocabulary) | Cross-product | — | **MANAGED** by ADR-017 (model finding) | | F-10 | `rule_id` namespace | Cross-product | — | **MANAGED** by ADR-017+ADR-022 (model finding) | | F-11 | `finding` cross-product record shape | Cross-product | — | **MANAGED** by ADR-004 (model finding) | | F-12 | L7 qualname vs Wardline `FingerprintEntry` | Cross-product | — | Tracked: `clarion-889200006a` (deferred to WP9) | @@ -75,27 +75,27 @@ For each candidate concept: ## F-1: `priority` is the same word for two different things **Sites**: -- Clarion: `docs/clarion/1.0/detailed-design.md:453` — guidance entity +- Loomweave: `docs/loomweave/1.0/detailed-design.md:453` — guidance entity property; values `"project" | "subsystem" | "package" | "module" | "class" | "function"`. -- Clarion: `docs/clarion/1.0/system-design.md:346` — same definition, +- Loomweave: `docs/loomweave/1.0/system-design.md:346` — same definition, semantic ordering `project → subsystem → package → module → class → function` (outer-overrides-inner composition order). -- Clarion schema: `crates/clarion-storage/migrations/0001_initial_schema.sql:163-165` +- Loomweave schema: `crates/loomweave-storage/migrations/0001_initial_schema.sql:163-165` generated column + index. -- Clarion test: `crates/clarion-storage/tests/schema_apply.rs:142-167`. +- Loomweave test: `crates/loomweave-storage/tests/schema_apply.rs:142-167`. - Filigree: `P0 | P1 | P2 | P3 | P4` numeric urgency; CLI `--priority=N`, MCP tool field, taxonomy entry `priority`. Different domain, different shape, different meaning. **Why HIGH**: The bug at `clarion-4cd11905e2` was misframed because the issue author defaulted to Filigree's meaning of "priority" (P0–P4) when reading -Clarion's design. The same misread will happen to every cross-product reader +Loomweave's design. The same misread will happen to every cross-product reader until the word is disambiguated. WP6 (briefing serving) will write the first `ORDER BY priority` query against this column, and TEXT-affinity lexicographic order doesn't match the semantic ordering — INTEGER affinity wouldn't help either, because the input is a string enum, not a number. Both the **bug** and the **vocabulary clash** are downstream of the same root cause: "priority" -isn't the right name for what Clarion stores. +isn't the right name for what Loomweave stores. **Suggested fix**: @@ -110,8 +110,8 @@ isn't the right name for what Clarion stores. 3. Update the test in `schema_apply.rs:142` to round-trip a real enum value (`{"composition_level": "subsystem"}`) and assert both columns. 4. Write a short ADR (suggested **ADR-024**: "Guidance composition-level - schema and Loom vocabulary") capturing the rename, the rank-mapping table, - and the rule that "priority" in Clarion docs always refers to Filigree's + schema and Weft vocabulary") capturing the rename, the rank-mapping table, + and the rule that "priority" in Loomweave docs always refers to Filigree's P0–P4. **Open policy question** (will recur): edit `0001_initial_schema.sql` in @@ -121,10 +121,10 @@ DB) or stack `0002_*.sql`? Decide once for the project. ## F-2: `critical` is the same word for two different things **Sites**: -- Clarion: `docs/clarion/1.0/detailed-design.md:467` — guidance entity +- Loomweave: `docs/loomweave/1.0/detailed-design.md:467` — guidance entity `critical: bool` flag — semantics: "preserved across token-budget pressure" (i.e., do-not-drop). -- Clarion: `detailed-design.md:449` — `tags: ["critical"]` literal as the +- Loomweave: `detailed-design.md:449` — `tags: ["critical"]` literal as the serialised form (also overlapping the dedicated boolean field). - Filigree: `severity:critical` enum value (highest severity tier) AND P0 priority is conventionally "Critical" in informal usage. @@ -143,7 +143,7 @@ Filigree's severity tier. authoritative source. 3. Bundle into the same ADR-024 if F-1 lands. -## F-3: `source` is overloaded three ways inside Clarion plus once outside +## F-3: `source` is overloaded three ways inside Loomweave plus once outside **Sites**: - `entity.source` = `SourceRange { file_id, byte_start, byte_end, line_start, line_end }` @@ -172,23 +172,23 @@ three different things. 3. Rename `guidance.source` → `guidance.origin` (describes authorship origin). 4. Bundle into ADR-024 if F-1 lands; otherwise file as separate ADR. -## F-4: `tags` (Clarion) vs `labels` (Filigree) +## F-4: `tags` (Loomweave) vs `labels` (Filigree) **Sites**: -- Clarion: `entity_tags(entity_id, tag)` table; `entity.tags: BTreeSet` +- Loomweave: `entity_tags(entity_id, tag)` table; `entity.tags: BTreeSet` (free-form). - Filigree: namespaced `labels` (`area:`, `cluster:`, `effort:`, …) with a reserved-namespace taxonomy. -**Why LOW**: Different word, different semantics — Clarion's `tags` are +**Why LOW**: Different word, different semantics — Loomweave's `tags` are free-form and plugin/LLM-emitted; Filigree's `labels` are a curated taxonomy. The naming actually reflects the design difference correctly. **Suggested fix**: leave the names. Add a one-paragraph note in the v0.1 glossary (or in ADR-024) that maps the two so a cross-product reader doesn't -assume they're the same shape. WP9 (Loom integrations) will need this anyway. +assume they're the same shape. WP9 (Weft integrations) will need this anyway. -## F-5: `kind` is overloaded three ways within Clarion +## F-5: `kind` is overloaded three ways within Loomweave **Sites**: - `entity.kind` (`detailed-design.md:195`) — entity taxonomy @@ -210,11 +210,11 @@ removes, because `kind` is the right word for all three jobs. namespaces explicit. (Filigree uses `type` for the analogous concept, which is fine — different products, different taste; no clash.) -## F-6: `status` is overloaded across Clarion and Filigree +## F-6: `status` is overloaded across Loomweave and Filigree **Sites**: -- Clarion `runs.status` — analyze-run lifecycle. -- Clarion `findings.status` — `open | acknowledged | suppressed | promoted_to_issue` +- Loomweave `runs.status` — analyze-run lifecycle. +- Loomweave `findings.status` — `open | acknowledged | suppressed | promoted_to_issue` (`detailed-design.md:295`). - Filigree finding-status — `open | acknowledged | fixed | false_positive | unseen_in_latest` (`detailed-design.md:294` comment + ADR-017). @@ -223,7 +223,7 @@ is fine — different products, different taste; no clash.) etc.). **Why LOW**: Already partially managed — `detailed-design.md §7` is supposed -to carry the Clarion-finding-status to Filigree-finding-status mapping, and +to carry the Loomweave-finding-status to Filigree-finding-status mapping, and ADR-017 references it. Distinct state machines on distinct objects are normal and unambiguous given the table/struct context. @@ -241,7 +241,7 @@ the audit's resolution: closing F-1 closes F-7. ## F-8: `schema_apply.rs:142` test asserts the bug, not the design **Sites**: -- `crates/clarion-storage/tests/schema_apply.rs:142` — +- `crates/loomweave-storage/tests/schema_apply.rs:142` — `let props = r#"{"priority": 2, "git_churn_count": 42}"#;` — inserts an integer `2` into `properties.priority`, which is **not a valid value** per `detailed-design.md:453` (priority is a six-level string enum). @@ -274,18 +274,18 @@ time, and which the unmanaged findings above should follow. ### F-9: `severity` — managed by ADR-017 -- Clarion internal: `INFO | WARN | ERROR | CRITICAL` for defects, `NONE` for facts. +- Loomweave internal: `INFO | WARN | ERROR | CRITICAL` for defects, `NONE` for facts. - Filigree wire: `critical | high | medium | low | info` (lowercase). - Mapping: explicit one-to-one table (`detailed-design.md §7`, `ADR-017-severity-and-dedup.md`); round-trip preserved via - `metadata.clarion.internal_severity`. + `metadata.loomweave.internal_severity`. - **Lesson**: explicit ADR + mapping table + metadata round-trip = no clash. ### F-10: `rule_id` namespacing — managed by ADR-017 + ADR-022 -- Namespace prefix per emitter: `CLA-PY-*`, `CLA-INFRA-*`, `CLA-FACT-*`, - `CLA-SEC-*`, `WLN-*`, `FIL-*`. -- Grammar-checked at the Clarion-plugin boundary per ADR-022. +- Namespace prefix per emitter: `LMWV-PY-*`, `LMWV-INFRA-*`, `LMWV-FACT-*`, + `LMWV-SEC-*`, `WLN-*`, `FIL-*`. +- Grammar-checked at the Loomweave-plugin boundary per ADR-022. - **Lesson**: namespacing convention + grammar enforcement = no clash even when many emitters share the field. @@ -313,16 +313,16 @@ viable bundling strategies, in order of my preference: Bundle the three vocabulary renames (`priority`, `critical`, `source` on finding/guidance) plus the schema change and test fix into a single ADR-024 -("Loom vocabulary discipline and guidance schema") and a single PR before +("Weft vocabulary discipline and guidance schema") and a single PR before Tier B starts. Touches: -- `docs/clarion/1.0/detailed-design.md` (multiple sites) -- `docs/clarion/1.0/system-design.md` (one site) -- `docs/clarion/adr/ADR-024-*.md` (new) -- `crates/clarion-storage/migrations/0001_initial_schema.sql` (priority +- `docs/loomweave/1.0/detailed-design.md` (multiple sites) +- `docs/loomweave/1.0/system-design.md` (one site) +- `docs/loomweave/adr/ADR-024-*.md` (new) +- `crates/loomweave-storage/migrations/0001_initial_schema.sql` (priority column + index; possibly add `0002_*.sql` instead — see migration policy question below) -- `crates/clarion-storage/tests/schema_apply.rs` (rewrite the priority test) +- `crates/loomweave-storage/tests/schema_apply.rs` (rewrite the priority test) - Pre-existing filigree issues: close `clarion-4cd11905e2` with an audit-resolution pointer. @@ -405,9 +405,9 @@ naming and split doctrine vs. schema fix) shaped Phase 2. | F-16 | `source_file_id TEXT REFERENCES entities(id)` has implicit "rows referenced by `source_file_id` must have `kind='file'`" invariant; nothing enforces it | MEDIUM | `clarion-523b2eebad` (P2) | | F-17 | `runs.id` ↔ `findings.run_id` is string-match only, no FK; provenance link untracked at the schema layer | LOW | `clarion-ba198ee96b` (P4, doc note) | -F-1 through F-8 are absorbed by ADR-024 (`docs/clarion/adr/ADR-024-guidance-schema-vocabulary.md`) and the Phase 1 + Phase 2 commits on this branch. The original priority-affinity bug `clarion-4cd11905e2` is closed with the audit-resolution comment. +F-1 through F-8 are absorbed by ADR-024 (`docs/loomweave/adr/ADR-024-guidance-schema-vocabulary.md`) and the Phase 1 + Phase 2 commits on this branch. The original priority-affinity bug `clarion-4cd11905e2` is closed with the audit-resolution comment. -F-13 is absorbed by [ADR-031](../../clarion/adr/ADR-031-schema-validation-policy.md) (2026-05-18): CHECK constraints on closed core-owned vocabularies (`findings.{kind,severity,status}`, `runs.status`); writer-actor + ADR-022 manifest acceptance remain the only enforcement layer for plugin-extensible vocabularies (`entities.kind`, `edges.kind`). Filigree issue `clarion-fbe50aa6e1` closed. +F-13 is absorbed by [ADR-031](../../loomweave/adr/ADR-031-schema-validation-policy.md) (2026-05-18): CHECK constraints on closed core-owned vocabularies (`findings.{kind,severity,status}`, `runs.status`); writer-actor + ADR-022 manifest acceptance remain the only enforcement layer for plugin-extensible vocabularies (`entities.kind`, `edges.kind`). Filigree issue `clarion-fbe50aa6e1` closed. ### Naming refinements applied to Phase 2 @@ -432,6 +432,6 @@ ADR-024 lands with the refined names. Both reviewers (critic explicitly, leverage analyst implicitly) called the migration-policy punt the audit's biggest weakness. ADR-024 makes the call: **edit `0001` in place**, with a written retirement trigger -(first external operator pulls a published Clarion build → policy +(first external operator pulls a published Loomweave build → policy switches to stack-only thereafter). No supersession of ADR-024 needed for the trigger; the trigger is observable and named in the ADR. diff --git a/docs/implementation/handoffs/2026-05-16-sprint-2-resume.md b/docs/implementation/handoffs/2026-05-16-sprint-2-resume.md index 030af6e9..c812a0fc 100644 --- a/docs/implementation/handoffs/2026-05-16-sprint-2-resume.md +++ b/docs/implementation/handoffs/2026-05-16-sprint-2-resume.md @@ -1,4 +1,4 @@ -# Clarion Sprint 2 — Resume Handoff (post-amendment) +# Loomweave Sprint 2 — Resume Handoff (post-amendment) This file is the starting prompt for the next Claude Code session that opens the resumed Sprint 2. Paste it as the user's first message; it is @@ -10,7 +10,7 @@ record of what Sprint 2 was originally scoped to do. --- -# Begin Clarion Sprint 2 (resumed under scope amendment 2026-05-16) +# Begin Loomweave Sprint 2 (resumed under scope amendment 2026-05-16) Sprint 2 was opened 2026-04-30 with seven Tier B boxes (B.1–B.7) plus warmup carryover. Two boxes shipped (B.2 class+module entities, one @@ -31,9 +31,9 @@ protocol changes — that's the proof). Forward on the existing work. 1. **The scope amendment** — [`docs/implementation/sprint-2/scope-amendment-2026-05.md`](../../implementation/sprint-2/scope-amendment-2026-05.md). This is the source of truth for what Sprint 2 ships now. Read it cover-to-cover before doing anything else. 2. **The three new ADRs** — - - [ADR-028 — Edge confidence tiers](../../clarion/adr/ADR-028-edge-confidence-tiers.md) - - [ADR-029 — Entity associations binding](../../clarion/adr/ADR-029-entity-associations-binding.md) - - [ADR-030 — On-demand summary scope](../../clarion/adr/ADR-030-on-demand-summary-scope.md) + - [ADR-028 — Edge confidence tiers](../../loomweave/adr/ADR-028-edge-confidence-tiers.md) + - [ADR-029 — Entity associations binding](../../loomweave/adr/ADR-029-entity-associations-binding.md) + - [ADR-030 — On-demand summary scope](../../loomweave/adr/ADR-030-on-demand-summary-scope.md) 3. **The original Sprint 2 kickoff** — [`2026-04-30-sprint-2-kickoff.md`](./2026-04-30-sprint-2-kickoff.md). Read sections "Sprint 1 in one paragraph" (so this handoff stays self-contained on the walking-skeleton contract) and "Carryover" (unfixed warmup bugs are still ready work). 4. **The B.3 design** — [`docs/implementation/sprint-2/b3-contains-edges.md`](../../implementation/sprint-2/b3-contains-edges.md). Implementation pending; this is the next thing to actually code. @@ -44,17 +44,17 @@ Skim only: ## Working directory + branch -- Directory: `/home/john/clarion` +- Directory: `/home/john/loomweave` - Branch: continue on `sprint-2/b3-design` (the in-flight branch). The scope-amendment artifacts (ADRs 028/029/030, scope memo, plan + ADR-README updates) were authored under a `worktree-sprint-2+mvp-reset-additions` worktree; merge that into `sprint-2/b3-design` before starting implementation work, or rebase the amendments on top of B.3 impl as you go. - Latest commit on `sprint-2/b3-design`: `5c510f1` (B.3 design doc). - The `sprint-2/b2-design` branch is closed; B.2 work merged. ## Sprint 1 in one paragraph (so this handoff is self-contained) -`clarion analyze` against a fixture project with a single +`loomweave analyze` against a fixture project with a single `def hello(): ...` Python file persists exactly one entity row, `python:function:demo.hello`, with `kind="function"`, into -`.clarion/clarion.db`. The pipeline is: Rust plugin host discovers +`.loomweave/loomweave.db`. The pipeline is: Rust plugin host discovers the editable Python plugin on `$PATH`, spawns it, completes a JSON-RPC handshake, sends one `analyze_file` request, receives one entity, and writes it through the writer-actor. Every stage has both @@ -93,7 +93,7 @@ warmup (optional) → B.3 impl → B.4* → B.5* B.7 (parallel) Pick one of the unfixed warmup bugs. Both are P2, small, in code you will touch later, and will get you back into the Rust workspace: -- `clarion-ed5017139f` — `clarion install` leaves partial `.clarion/` on failure. Fix: cleanup or atomic move. +- `clarion-ed5017139f` — `loomweave install` leaves partial `.loomweave/` on failure. Fix: cleanup or atomic move. - `clarion-b5b1029f5a` — `reader_pool` flaky 100ms sleep. Fix: replace with deterministic synchronisation primitive. Skip if you want to dive into B.3. @@ -103,7 +103,7 @@ Skip if you want to dive into B.3. Design is committed at [`docs/implementation/sprint-2/b3-contains-edges.md`](../../implementation/sprint-2/b3-contains-edges.md). Implementation follows the §"Locked surfaces" and §"Design decisions Q1–Q6" sections directly. Key shape: - Python plugin emits `contains` edges + adds `parent_id` field to `RawEntity` (dual encoding per Q2). -- Writer-actor enforces the parent_id ↔ contains-edge consistency invariant (`CLA-INFRA-PARENT-CONTAINS-MISMATCH`). +- Writer-actor enforces the parent_id ↔ contains-edge consistency invariant (`LMWV-INFRA-PARENT-CONTAINS-MISMATCH`). - Schema migration drops `edges.id`, promotes `(kind, from_id, to_id)` to natural PK (ADR-026 + ADR-024 edit-in-place policy). - Ontology version bumps `0.2.0 → 0.3.0` (MINOR per ADR-027). @@ -129,7 +129,7 @@ This gate's purpose is to discover scale-bound design problems in week 2, not we **Decision C — storage layout for inferred edges.** Same `edges` table with `confidence='inferred'` rows, or separate `inferred_edges_cache` table? ADR-028 §"Alternative 4" defers this to B.4* implementation. Pick during implementation; document in the B.4* design doc. -The schema migration for `confidence` column lands under ADR-024's edit-in-place policy (no consumer has read an edge row yet; Clarion is unpublished). +The schema migration for `confidence` column lands under ADR-024's edit-in-place policy (no consumer has read an edge row yet; Loomweave is unpublished). ### Step 3 — B.5* references (`clarion-b0cedfd2bb`) @@ -145,9 +145,9 @@ Can start in parallel with B.4*/B.5* — different repo (Filigree), different co - Target Filigree release: 2.1.0 (Filigree is currently at `release/2.0.1`). - Coordinate with the Filigree-side release plan; this is one PR on the Filigree repo. -**Clarion-side**: +**Loomweave-side**: - HTTP client for Filigree's existing API (use `reqwest`; auth per ADR-012 / Filigree's existing UDS+token scheme). -- New MCP tool: `issues_for(entity_id, include_contained=true)` — implementation in the new `clarion-mcp` crate (see B.6 below). +- New MCP tool: `issues_for(entity_id, include_contained=true)` — implementation in the new `loomweave-mcp` crate (see B.6 below). Federation §5 audit is in ADR-029; cite it if any reviewer questions the cross-product coupling. @@ -155,7 +155,7 @@ Federation §5 audit is in ADR-029; cite it if any reviewer questions the cross- ### Step 5 — B.6 WP8 MCP surface (`clarion-e2a3672cc9`) -New crate: `clarion-mcp`. Use `rmcp` (the Rust MCP SDK) or whatever the Rust ecosystem has settled on by the time this work starts. +New crate: `loomweave-mcp`. Use `rmcp` (the Rust MCP SDK) or whatever the Rust ecosystem has settled on by the time this work starts. Seven tools (per ADR-028 §"Decision 2" + ADR-029 §"Decision 2" + ADR-030 §"Decision 1"): - `entity_at(file, line)` — innermost containing entity (uses `ix_entities_source_file` + line-range query) @@ -171,8 +171,8 @@ MCP auth: UDS default per ADR-012; falls back to TCP+token. Same discipline as t ### Step 6 — B.8 elspeth scale-test (`clarion-6222134e0d`) Sprint 2 closes when B.8 ships. Scope per scope-amendment §3: -- `clarion analyze` runs end-to-end against elspeth-slice (~50 files initially; full ~425k LOC if feasible). -- `clarion serve` exposes the seven MCP tools. +- `loomweave analyze` runs end-to-end against elspeth-slice (~50 files initially; full ~425k LOC if feasible). +- `loomweave serve` exposes the seven MCP tools. - Consult-mode agent navigates the corpus via the MCP server. - Measure: cost per `summary(id)` call, latency per MCP query, cache hit rate on second-pass. - Re-scoped WP11 spike: per-query cost (the honest metric for on-demand) rather than per-run cost. @@ -214,7 +214,7 @@ The current Sprint 2 ready landscape (as of 2026-05-16): - `clarion-2d2d1d27b5` (B.4*) — pending, blocked by B.3 - `clarion-b0cedfd2bb` (B.5*) — pending, blocked by B.4* - `clarion-e2a3672cc9` (B.6) — pending, blocked by B.4* -- `clarion-73ab0da435` (B.7) — pending, no clarion-side blocker; coordinates with Filigree-side release +- `clarion-73ab0da435` (B.7) — pending, no loomweave-side blocker; coordinates with Filigree-side release - `clarion-6222134e0d` (B.8) — pending, blocked by B.6 + B.7 - Open warmups: `clarion-ed5017139f`, `clarion-b5b1029f5a` - Triage backlog (don't let it grow further): `clarion-fbe50aa6e1`, `clarion-523b2eebad` (P2 audit findings) diff --git a/docs/implementation/handoffs/2026-05-18-phase3-subsystems-handoff.md b/docs/implementation/handoffs/2026-05-18-phase3-subsystems-handoff.md index 73d1670d..e49b6aaa 100644 --- a/docs/implementation/handoffs/2026-05-18-phase3-subsystems-handoff.md +++ b/docs/implementation/handoffs/2026-05-18-phase3-subsystems-handoff.md @@ -31,32 +31,32 @@ Two things, in order, with a human review gate between them. ## Required reading (in this order) -1. **`docs/clarion/adr/ADR-006-clustering-algorithm.md` plus ADR-032** — the authoritative spec. Read in full. Leiden on directed weighted imports+calls subgraph, seeded for determinism, with `weighted_components` as the named local fallback after ADR-032. Output is one `subsystem` entity per cluster + `in_subsystem` edges from members. Modularity reported, not enforced. -2. **`docs/clarion/adr/ADR-022-core-plugin-ontology.md`** — `subsystem` is a core-reserved entity kind; `in_subsystem` is a core-reserved edge kind. Plugins cannot emit either. The writer-actor's edge-contract validator already knows about plugin-extensible vs core-reserved (`writer.rs:411` per arch-analysis). -3. **`docs/clarion/adr/ADR-003-entity-id-scheme.md`** — subsystem IDs follow `core:subsystem:{cluster_hash}` per ADR-006 §Output. The hash is `sha256(sorted(member_module_ids))` truncated to 12 chars; verify the existing entity-ID validator accepts this shape (it should — `core` is a registered plugin_id per ADR-022). -4. **`docs/clarion/1.0/requirements.md`** — REQ-CATALOG-05 (subsystem entities), REQ-ANALYZE-01 (phased pipeline), REQ-ANALYZE-05 (Phase-7 findings — relevant because `CLA-FACT-CLUSTERING-WEAK-MODULARITY` is named in ADR-006 §Quality assessment). -5. **`docs/clarion/1.0/system-design.md` §6** — pipeline phases. Phase 3 placement, what it reads from storage, what it writes. -6. **`docs/clarion/1.0/detailed-design.md`** — search for `subsystem`, `cluster`, `Phase 3`. Schema shape if present, properties expected on the subsystem entity. +1. **`docs/loomweave/adr/ADR-006-clustering-algorithm.md` plus ADR-032** — the authoritative spec. Read in full. Leiden on directed weighted imports+calls subgraph, seeded for determinism, with `weighted_components` as the named local fallback after ADR-032. Output is one `subsystem` entity per cluster + `in_subsystem` edges from members. Modularity reported, not enforced. +2. **`docs/loomweave/adr/ADR-022-core-plugin-ontology.md`** — `subsystem` is a core-reserved entity kind; `in_subsystem` is a core-reserved edge kind. Plugins cannot emit either. The writer-actor's edge-contract validator already knows about plugin-extensible vs core-reserved (`writer.rs:411` per arch-analysis). +3. **`docs/loomweave/adr/ADR-003-entity-id-scheme.md`** — subsystem IDs follow `core:subsystem:{cluster_hash}` per ADR-006 §Output. The hash is `sha256(sorted(member_module_ids))` truncated to 12 chars; verify the existing entity-ID validator accepts this shape (it should — `core` is a registered plugin_id per ADR-022). +4. **`docs/loomweave/1.0/requirements.md`** — REQ-CATALOG-05 (subsystem entities), REQ-ANALYZE-01 (phased pipeline), REQ-ANALYZE-05 (Phase-7 findings — relevant because `LMWV-FACT-CLUSTERING-WEAK-MODULARITY` is named in ADR-006 §Quality assessment). +5. **`docs/loomweave/1.0/system-design.md` §6** — pipeline phases. Phase 3 placement, what it reads from storage, what it writes. +6. **`docs/loomweave/1.0/detailed-design.md`** — search for `subsystem`, `cluster`, `Phase 3`. Schema shape if present, properties expected on the subsystem entity. 7. **Current code surface** — `cargo` over these files at minimum: - - `crates/clarion-storage/migrations/0001_initial_schema.sql` — does `entities.kind` already accept `subsystem`? Does `edges.kind` accept `in_subsystem`? ADR-031 added CHECK constraints on closed vocabularies; verify the subsystem path is not constrained-shut. - - `crates/clarion-storage/src/writer.rs` (around line 394–411) — `STRUCTURAL_EDGE_KINDS` and `ANCHORED_EDGE_KINDS` registers. Arch-analysis H-2 noted these duplicate the manifest's edge-kind list (now closed as `clarion-4e3cacac90`); confirm what the closure shipped. - - `crates/clarion-storage/src/query.rs` — what helpers exist for module-level edge enumeration? - - `crates/clarion-cli/src/analyze.rs` — where Phase 1 (entity ingest) and Phase 2 (graph completion if implemented) currently end. Find the seam Phase 3 plugs into. Note that the file is `#[allow(clippy::too_many_lines)]` and arch-analysis flagged it (H-1 closed); if you add to it, factor cleanly. - - `crates/clarion-mcp/src/lib.rs` — the 7 MCP tools. Specifically `neighborhood`, `find_entity`, `execution_paths_from` — these will become more useful with subsystems but may need shape changes. **Do not change MCP tool surfaces silently.** Surface every proposed change in the plan. + - `crates/loomweave-storage/migrations/0001_initial_schema.sql` — does `entities.kind` already accept `subsystem`? Does `edges.kind` accept `in_subsystem`? ADR-031 added CHECK constraints on closed vocabularies; verify the subsystem path is not constrained-shut. + - `crates/loomweave-storage/src/writer.rs` (around line 394–411) — `STRUCTURAL_EDGE_KINDS` and `ANCHORED_EDGE_KINDS` registers. Arch-analysis H-2 noted these duplicate the manifest's edge-kind list (now closed as `clarion-4e3cacac90`); confirm what the closure shipped. + - `crates/loomweave-storage/src/query.rs` — what helpers exist for module-level edge enumeration? + - `crates/loomweave-cli/src/analyze.rs` — where Phase 1 (entity ingest) and Phase 2 (graph completion if implemented) currently end. Find the seam Phase 3 plugs into. Note that the file is `#[allow(clippy::too_many_lines)]` and arch-analysis flagged it (H-1 closed); if you add to it, factor cleanly. + - `crates/loomweave-mcp/src/lib.rs` — the 7 MCP tools. Specifically `neighborhood`, `find_entity`, `execution_paths_from` — these will become more useful with subsystems but may need shape changes. **Do not change MCP tool surfaces silently.** Surface every proposed change in the plan. 8. **`docs/implementation/sprint-2/scope-amendment-2026-05.md`** — what was explicitly deferred. WP4 Phase 3 was deferred *with this work in mind*; you are pulling it forward from v0.2 to closing-of-v0.1. -9. **`docs/implementation/arch-analysis-2026-05-20-2124/02-subsystem-catalog.md`** — current RC1 code geography. The `clarion-storage` and `clarion-cli` entries describe the writer-actor, query helpers, and analyze orchestrator in concrete terms; refresh line numbers against current `HEAD` before treating them as evidence. +9. **`docs/implementation/arch-analysis-2026-05-20-2124/02-subsystem-catalog.md`** — current RC1 code geography. The `loomweave-storage` and `loomweave-cli` entries describe the writer-actor, query helpers, and analyze orchestrator in concrete terms; refresh line numbers against current `HEAD` before treating them as evidence. ## Scope discipline — what's IN and what's OUT **In scope (deliver):** - Phase 3 clustering as a new step in `analyze::run`, after structural entity/edge ingest, before run commit. -- Leiden-default clustering over the (`imports` ∪ `calls`) module-level subgraph, weighted by `reference_count`, seeded from `clarion.yaml`, deterministic. +- Leiden-default clustering over the (`imports` ∪ `calls`) module-level subgraph, weighted by `reference_count`, seeded from `loomweave.yaml`, deterministic. - Weighted-components as a config-selectable fallback (`analysis.clustering.algorithm: weighted_components`). - Emit one `core:subsystem:{cluster_hash}` entity per cluster ≥ `min_cluster_size` (default 3). Properties per ADR-006 §Output. - Emit `in_subsystem` edges from each member module to its subsystem entity. Edge ontology updated where required. -- New `clarion.yaml` keys under `analysis.clustering` (algorithm / seed / resolution / min_cluster_size / edge_types / weight_by). Defaults match ADR-006. +- New `loomweave.yaml` keys under `analysis.clustering` (algorithm / seed / resolution / min_cluster_size / edge_types / weight_by). Defaults match ADR-006. - Persist `modularity_score` and the algorithm/seed/resolution to the subsystem entity's properties **and** to `runs.stats` for the run-level record. -- Emit `CLA-FACT-CLUSTERING-WEAK-MODULARITY` (severity INFO) when overall modularity < 0.3, per ADR-006 §Quality assessment. (This is the only Phase-7 finding in scope here — the wider `CLA-*` catalogue remains v0.2.) +- Emit `LMWV-FACT-CLUSTERING-WEAK-MODULARITY` (severity INFO) when overall modularity < 0.3, per ADR-006 §Quality assessment. (This is the only Phase-7 finding in scope here — the wider `LMWV-*` catalogue remains v0.2.) - A new MCP tool `subsystem_members(id)` (or extension of existing `neighborhood` to recognise `core:subsystem:*` entities — design choice; raise it in the plan). - E2E test: a multi-module Python fixture clusters into at least 2 subsystems with the expected memberships. - Determinism test: two consecutive runs against the same fixture produce byte-identical subsystem IDs and modularity scores. @@ -64,10 +64,10 @@ Two things, in order, with a human review gate between them. **Explicitly OUT of scope (do NOT do):** - Subsystem *summarisation* (Phase 6 module/subsystem aggregation). ADR-030 defers this to v0.2; do not invoke the LLM on subsystem entities. `summary(id)` on a `core:subsystem:*` entity should return the existing leaf-summary-not-available envelope shape until v0.2. -- The full Phase-7 `CLA-*` cross-cutting rule catalogue. Only the one weak-modularity finding above. +- The full Phase-7 `LMWV-*` cross-cutting rule catalogue. Only the one weak-modularity finding above. - Catalog artefacts (`catalog.json` + per-subsystem markdown) — REQ-ARTEFACT-01/02 are deferred and have no MVP consumer. - Multi-language plugin support — `imports` and `calls` are Python-plugin emissions for v0.1. Cross-language subsystems are NG-15. -- Schema-breaking migrations. ADR-024 still allows edit-in-place; verify that's the right policy here (it should be — no external operator has built `.clarion/clarion.db` from a published Clarion yet) and call it out explicitly in the plan. +- Schema-breaking migrations. ADR-024 still allows edit-in-place; verify that's the right policy here (it should be — no external operator has built `.loomweave/loomweave.db` from a published Loomweave yet) and call it out explicitly in the plan. - HTTP read API exposure. The HTTP read API is itself unimplemented; surfacing subsystems through it is v0.2. ## Phase 1 — Analysis (your first job) @@ -82,7 +82,7 @@ Read the code; do not trust the ADRs as a description of the *current* implement 2. Where in `analyze::run` does structural ingest end and run-commit begin? Cite the line range. 3. Does the writer-actor have a path for emitting a subsystem entity (no plugin source)? Or do you need a new `WriterCmd` variant (`InsertCoreEntity` / `InsertSubsystem`)? 4. What module-level edge data exists post-Phase-1? (Calls are emitted at function-level per B.4*; you need to aggregate them to module-level — confirm this isn't already done.) -5. What does the existing entity-ID validator (`clarion-core::entity_id`) say about `core:subsystem:abc123def456`? Run the fixtures. +5. What does the existing entity-ID validator (`loomweave-core::entity_id`) say about `core:subsystem:abc123def456`? Run the fixtures. ### B. Open design decisions to surface @@ -91,7 +91,7 @@ Each gets a recommendation + the alternatives + the reason for the recommendatio 1. **Leiden source.** Vendor (~400 LOC per ADR-006) or adopt a maintained crate. Check `crates.io` at planning time. ADR-006 explicitly leaves this open. 2. **`in_subsystem` edge placement.** Is it `structural` or `anchored` in the existing edge ontology? Does it appear in `STRUCTURAL_EDGE_KINDS`? Does the manifest need to declare it (no — core-owned per ADR-022)? 3. **`subsystem_members` MCP tool vs extending `neighborhood`.** Both work. Which keeps the tool catalogue cleaner? -4. **Empty-input handling.** When the imports+calls subgraph is empty (single-module fixture, or analysis fails Phase 1), what does Phase 3 do? Skip silently? Emit zero subsystems and a `CLA-FACT-CLUSTERING-NO-INPUT` finding? Recommend. +4. **Empty-input handling.** When the imports+calls subgraph is empty (single-module fixture, or analysis fails Phase 1), what does Phase 3 do? Skip silently? Emit zero subsystems and a `LMWV-FACT-CLUSTERING-NO-INPUT` finding? Recommend. 5. **`runs.stats` shape.** The existing `runs.stats` is a JSON blob. Where does the modularity score sit in it? Define the JSON shape additively. 6. **Migration policy.** Does this need a new migration, or does ADR-024 edit-in-place still apply? Justify. @@ -110,7 +110,7 @@ A table of every file you will create / modify, with the task that touches it. S - Determinism test green. - The Sprint-1 walking-skeleton E2E continues to pass (no regression on the single-file fixture). - The MCP `summary` tool on a subsystem entity returns the policy envelope (not a budget-consuming LLM call). -- `clarion analyze` against the existing elspeth-slice perf harness still completes within the NFR-PERF-01 envelope; if Phase 3 adds noticeable cost, the plan must include a measurement step. +- `loomweave analyze` against the existing elspeth-slice perf harness still completes within the NFR-PERF-01 envelope; if Phase 3 adds noticeable cost, the plan must include a measurement step. ### F. Risks and unknowns @@ -156,7 +156,7 @@ filigree close --reason="Phase 3 clustering shipped; subsystem ent ## Authorities and overrides - **ADR-006 is authoritative on algorithm.** If your implementation reveals the ADR is wrong (e.g., directed-modularity Leiden produces nonsense on some pattern of the elspeth graph), do NOT silently switch. Stop, document the empirical finding, and propose an ADR amendment. ADRs are immutable once Accepted (`CLAUDE.md` editorial conventions); the right response is a new ADR that supersedes, not a silent code-level change. -- **The Loom federation axiom (`docs/suite/loom.md` §5)** is load-bearing. Phase 3 is internal to Clarion and does not touch sibling products, so this should not bite. If you find yourself proposing a cross-product change, you've gone out of scope. +- **The Weft federation axiom (`docs/suite/weft.md` §5)** is load-bearing. Phase 3 is internal to Loomweave and does not touch sibling products, so this should not bite. If you find yourself proposing a cross-product change, you've gone out of scope. - **The tooling baseline (ADR-023)** is non-negotiable per PR. If a clippy lint blocks you, fix the lint; do not `#[allow]` it without writing the justification into the code and the plan. ## Done condition @@ -167,18 +167,18 @@ This handoff is satisfied when: - All plan tasks are checked off and committed. - The Filigree umbrella is `closed` with a close reason that names the merge commits. - The walking-skeleton E2E + a new subsystems E2E + the determinism test are all green in CI. -- A subsequent `clarion analyze && clarion serve` on a multi-module fixture lets a consult-mode agent ask "what are the subsystems of this project" and get back a non-empty, sensibly-named list of subsystem entities. +- A subsequent `loomweave analyze && loomweave serve` on a multi-module fixture lets a consult-mode agent ask "what are the subsystems of this project" and get back a non-empty, sensibly-named list of subsystem entities. The last bullet is the actual capability the work delivers. If it works in CI but a real agent session against a real fixture doesn't surface meaningful subsystems, you haven't shipped the feature. ## References -- [ADR-006 — Clustering algorithm](../../clarion/adr/ADR-006-clustering-algorithm.md) -- [ADR-022 — Core/plugin ontology](../../clarion/adr/ADR-022-core-plugin-ontology.md) -- [ADR-003 — Entity-ID scheme](../../clarion/adr/ADR-003-entity-id-scheme.md) -- [ADR-024 — Migration edit-in-place policy](../../clarion/adr/ADR-024-guidance-schema-vocabulary.md) -- [ADR-031 — Schema validation policy (CHECK constraints)](../../clarion/adr/ADR-031-schema-validation-policy.md) -- [REQ-CATALOG-05, REQ-ANALYZE-01, REQ-ANALYZE-05](../../clarion/v0.1/requirements.md) +- [ADR-006 — Clustering algorithm](../../loomweave/adr/ADR-006-clustering-algorithm.md) +- [ADR-022 — Core/plugin ontology](../../loomweave/adr/ADR-022-core-plugin-ontology.md) +- [ADR-003 — Entity-ID scheme](../../loomweave/adr/ADR-003-entity-id-scheme.md) +- [ADR-024 — Migration edit-in-place policy](../../loomweave/adr/ADR-024-guidance-schema-vocabulary.md) +- [ADR-031 — Schema validation policy (CHECK constraints)](../../loomweave/adr/ADR-031-schema-validation-policy.md) +- [REQ-CATALOG-05, REQ-ANALYZE-01, REQ-ANALYZE-05](../../loomweave/v0.1/requirements.md) - [v0.1-plan.md WP4](../../implementation/v0.1-plan.md#wp4--core-only-pipeline-phases-03-7-8) - [Sprint-2 scope amendment — explicit Phase 3 deferral with retirement path](../../implementation/sprint-2/scope-amendment-2026-05.md) - [Arch-analysis 2026-05-20 — current RC1 code geography](../arch-analysis-2026-05-20-2124/04-final-report.md) diff --git a/docs/implementation/clarion-dogfood-eval-2026-05-29.md b/docs/implementation/loomweave-dogfood-eval-2026-05-29.md similarity index 90% rename from docs/implementation/clarion-dogfood-eval-2026-05-29.md rename to docs/implementation/loomweave-dogfood-eval-2026-05-29.md index 4c7f712f..892d195b 100644 --- a/docs/implementation/clarion-dogfood-eval-2026-05-29.md +++ b/docs/implementation/loomweave-dogfood-eval-2026-05-29.md @@ -1,31 +1,31 @@ -# Clarion dogfood evaluation — senior-user verdict (2026-05-29) +# Loomweave dogfood evaluation — senior-user verdict (2026-05-29) Evaluator: senior engineer, day-one orientation via the 8 MCP query tools only. Read-only. Every claim below is cited to an actual MCP call or a source `Read`. > **Corpus caveat (this is Finding #1, read it first).** The live MCP server is -> **not** serving the 425k-LOC elspeth the brief described. It is serving Clarion's -> own repo DB (`/home/john/clarion/.clarion/clarion.db`, 1872 entities), which +> **not** serving the 425k-LOC elspeth the brief described. It is serving Loomweave's +> own repo DB (`/home/john/loomweave/.loomweave/loomweave.db`, 1872 entities), which > swept together the `elspeth_mini` test fixture (`tests/perf/elspeth_mini/…`, -> ~30k LOC / 80 files), Clarion's *own* plugin source, and a `.env` file. The +> ~30k LOC / 80 files), Loomweave's *own* plugin source, and a `.env` file. The > real elspeth DB (36,814 entities, 134 subsystems, 259 MB) exists at -> `/home/john/elspeth/.clarion/clarion.db` but the served tools cannot reach it. +> `/home/john/elspeth/.loomweave/loomweave.db` but the served tools cannot reach it. > I evaluated the tool surface against what is actually served, per the only > substrate the 8 tools expose. Clustering/scale findings are therefore partly > corpus-dependent and flagged as such; tool-mechanic findings hold regardless. > **Maintainer reconciliation (added post-run, evidence-verified).** The corpus -> mismatch above is an *operational staging artifact*, not a Clarion +> mismatch above is an *operational staging artifact*, not a Loomweave > analysis-scoping defect — and the way it happened is itself a finding. The MCP > server's configured DB path had been wiped from `/tmp`; the DB was restored by > `rm` + sqlite `.backup` (a **new inode**) while the server was still running. > The server held a pooled connection to the **old, now-unlinked inode** (a -> Clarion self-analysis DB) and silently kept serving it. On-disk the path now +> Loomweave self-analysis DB) and silently kept serving it. On-disk the path now > holds a clean, properly-scoped **real elspeth** snapshot (36,680 elspeth -> entities + 134 subsystems, 0 fixture/clarion rows — verified by direct +> entities + 134 subsystems, 0 fixture/loomweave rows — verified by direct > `sqlite3`), but the live tools never saw it. So: > - **Finding #5 ("corpus contamination / analysis had no scoping") is withdrawn** -> as a Clarion bug — the real elspeth analysis *is* scoped correctly; the wrong +> as a Loomweave bug — the real elspeth analysis *is* scoped correctly; the wrong > DB was simply served. It is re-cast as: *a running `serve` keeps serving a > deleted DB inode with no detection* (minor robustness note) + further proof of > the provenance gap (Finding #1). @@ -95,8 +95,8 @@ whether to trust it.** `ResumePoint` class either, so "who imports this contract" still required `grep -rn`. - **Subsystem layer was useless for orientation.** `subsystem_members` works - mechanically, but the two clusters it returned lump Clarion's own - `clarion_plugin_python.extractor`/`server`/`stdout_guard` in *with* elspeth + mechanically, but the two clusters it returned lump Loomweave's own + `loomweave_plugin_python.extractor`/`server`/`stdout_guard` in *with* elspeth contracts modules (modularity_score `0.093` — near-random). Names are opaque hashes (`Subsystem 9d59f183f130`). "What subsystem owns checkpointing?" had no meaningful answer. @@ -140,12 +140,12 @@ whether to trust it.** |---|---|---|---| | `find_entity` | FTS search over entity rows | **Yes** | Clean, paginates correctly (`cursor:"10"` → `next_cursor:"13"`). But `find_entity("subsystem")` returned only the 2 *hash*-named subsystems; the 2 namespace-named ones (`tests.perf`, `tests.perf.elspeth_mini.elspeth`, confirmed via sqlite) are invisible — subsystems aren't reliably discoverable through search. | | `summary` | On-demand leaf briefing | **Mostly** | Best tool here: `summary(contracts.checkpoint)` was accurate + high-signal. But `summary(Orchestrator class)` failed **twice** with `llm-invalid-json` and **charged $0.0152 each time** (~$0.03 burned, nothing cached, no fallback). Large entities silently exceed the prompt budget and return prose, not JSON. | -| `entity_at` | file+line → innermost entity | **No (broken)** | Every call errors. DB normalizes paths against a dead root `/tmp/clarion-b8-elspeth-full-20260518T0016Z`; the real on-disk absolute path is rejected as "escapes project root," and the relative path errors `No such file or directory`. Unusable on this DB. | +| `entity_at` | file+line → innermost entity | **No (broken)** | Every call errors. DB normalizes paths against a dead root `/tmp/loomweave-b8-elspeth-full-20260518T0016Z`; the real on-disk absolute path is rejected as "escapes project root," and the relative path errors `No such file or directory`. Unusable on this DB. | | `neighborhood` | one-hop callers/callees/refs | **Partial** | `contained` correct. References are tracked at **symbol granularity** (`neighborhood(ResumePoint).references_out` correctly lists `audit.Checkpoint` etc.), but the **module** entity rolls up nothing (`references_in/out: []`), and **upstream** import edges (`who imports ResumePoint`) don't appear as `references_in` on the class either. So downstream-per-class works; module rollup and upstream don't. | | `callers_of` | reverse call edges | **Works for direct calls** | Verified true-positive: `callers_of(_emit_telemetry)` → **11 real `resolved` callers** with byte offsets, all confirmed in source. Returns `[]` for attribute-receiver calls (`ctx.orchestrator.resume()` from `cli.py:1548`) at both confidences — a documented scope limit, not a bug — but the empty answer reads as "safe to change" with no flag that it's incomplete. | -| `subsystem_members` | modules in a subsystem | **Mechanically yes** | Works, but the clusters are near-random (modularity 0.093) and mix Clarion's own source with the fixture. Names are opaque hashes. Low orientation value here — likely corpus-contamination, not pure tool fault. | +| `subsystem_members` | modules in a subsystem | **Mechanically yes** | Works, but the clusters are near-random (modularity 0.093) and mix Loomweave's own source with the fixture. Names are opaque hashes. Low orientation value here — likely corpus-contamination, not pure tool fault. | | `execution_paths_from` | bounded calls-only paths | **Data yes, delivery no** | Paths are accurate and useful, but 128 KB / one line / over the token cap → dumped to a file. Format re-serializes every node in full per path. See Mission C. | -| `issues_for` | Filigree issues on an entity | **Graceful** | Filigree was down; returned a clean `available:false` / `filigree-unreachable` envelope without failing Clarion — exactly the enrich-only degradation the description promises. Could not exercise the populated path. | +| `issues_for` | Filigree issues on an entity | **Graceful** | Filigree was down; returned a clean `available:false` / `filigree-unreachable` envelope without failing Loomweave — exactly the enrich-only degradation the description promises. Could not exercise the populated path. | --- @@ -193,7 +193,7 @@ whether to trust it.** ### Pointless (earned no value in real use — with justification) - **`subsystem_members` (on this corpus only).** I read its intent — map modules to Leiden clusters so an agent can reason at subsystem altitude — and used it - in earnest on both clusters. With modularity 0.093 and Clarion's own source + in earnest on both clusters. With modularity 0.093 and Loomweave's own source mixed into the elspeth fixture, the clusters carry no real architectural signal, so it earned nothing *here*. I attribute this to corpus contamination + 30k-LOC scale, not to the tool's design; on a clean 425k-LOC @@ -205,7 +205,7 @@ whether to trust it.** --- -## 5. What I need from Clarion (prioritized wishlist to the maintainer) +## 5. What I need from Loomweave (prioritized wishlist to the maintainer) 1. **Ship `project_status` (new tool).** Return: served DB path, entity counts by kind, subsystem count, analyzed-at timestamp, project root, and a @@ -255,9 +255,9 @@ whether to trust it.** "no module-level reference rollup") to stop a consult agent from reading `[]` as "safe to change / nothing depends on this." 2. **`entity_at` is broken by a stale project root baked into the DB.** - Error: `escapes project root /tmp/clarion-b8-elspeth-full-20260518T0016Z` + Error: `escapes project root /tmp/loomweave-b8-elspeth-full-20260518T0016Z` for the very absolute path the entity rows store - (`/home/john/clarion/tests/perf/.../core.py`). The DB's normalization root + (`/home/john/loomweave/tests/perf/.../core.py`). The DB's normalization root and its stored `source_file_path`s are mutually inconsistent — an internal data bug, independent of corpus choice. 3. **`summary` bills on deterministic failure.** Two identical @@ -269,9 +269,9 @@ whether to trust it.** `truncation_reason:null` — so the truncation contract the description advertises ("responses say when they are truncated") didn't fire; the MCP layer truncated it instead, out of band. -5. **Corpus contamination (provenance bug).** The served DB analyzed Clarion's - own repo — `/home/john/clarion/.env`, - `clarion_plugin_python/__init__.py`, `server.py` — and lumped them into +5. **Corpus contamination (provenance bug).** The served DB analyzed Loomweave's + own repo — `/home/john/loomweave/.env`, + `loomweave_plugin_python/__init__.py`, `server.py` — and lumped them into subsystems with the elspeth_mini fixture. The analysis run had no scoping to the intended corpus. (Evidence: `sqlite3 … select source_file_path …` and the mixed `subsystem_members` output.) diff --git a/docs/implementation/clarion-dogfood-remediation-2026-05-29.md b/docs/implementation/loomweave-dogfood-remediation-2026-05-29.md similarity index 90% rename from docs/implementation/clarion-dogfood-remediation-2026-05-29.md rename to docs/implementation/loomweave-dogfood-remediation-2026-05-29.md index d7d99568..2fd2d0d9 100644 --- a/docs/implementation/clarion-dogfood-remediation-2026-05-29.md +++ b/docs/implementation/loomweave-dogfood-remediation-2026-05-29.md @@ -1,16 +1,16 @@ -# Clarion dogfood — remediation plan (2026-05-29) +# Loomweave dogfood — remediation plan (2026-05-29) -Companion to [`clarion-dogfood-eval-2026-05-29.md`](clarion-dogfood-eval-2026-05-29.md). +Companion to [`loomweave-dogfood-eval-2026-05-29.md`](loomweave-dogfood-eval-2026-05-29.md). Maps each confirmed finding to a fix and an owner ticket. Most findings were *already anticipated* by the dogfood epic **clarion-8fe3060d4c** (31 children); this plan records which tickets cover what, what's genuinely new, and what was -a test-harness artifact rather than a Clarion defect. +a test-harness artifact rather than a Loomweave defect. ## Status legend - **DONE** — already fixed in HEAD (verify after the served binary is rebuilt). - **TICKETED** — an existing Filigree issue covers it; may need scope tightening. - **GAP** — no ticket yet; file one (proposed below). -- **ARTIFACT** — caused by the eval staging, not Clarion; re-test, don't fix. +- **ARTIFACT** — caused by the eval staging, not Loomweave; re-test, don't fix. --- @@ -23,7 +23,7 @@ a test-harness artifact rather than a Clarion defect. policy (provider/live/cache), and the **resolved** Filigree endpoint. No LLM call. - **Why the agent missed it:** the live MCP server was the pre-`5d4aeaa` binary, so the tool wasn't in the registered list — the exact blindness `project_status` - exists to cure. **Action:** rebuild `target/release/clarion`, restart MCP, + exists to cure. **Action:** rebuild `target/release/loomweave`, restart MCP, re-verify; then close clarion-084e82250c. - **Bonus:** the same commit single-sources Filigree URL resolution at the `FiligreeHttpClient` construction site, fixing the stale-port path that made @@ -60,7 +60,7 @@ a test-harness artifact rather than a Clarion defect. call so the prompt-budget overflow that causes the invalid JSON can't happen; (c) never bill twice for an identical deterministic failure (negative-cache the failure, or don't bill a non-result). Related but not a substitute: - clarion-bacd53a2ad (preview), clarion-bacd…/spend controls are out of the epic's + clarion-bacd53a2ad (preview), loomweave-bacd…/spend controls are out of the epic's deterministic scope per the epic note. ### B3. Incompleteness is invisible (`[]` reads as "none") — **PARTIALLY TICKETED → GAP** @@ -71,7 +71,7 @@ a test-harness artifact rather than a Clarion defect. *correct where it answers* (11 real `_emit_telemetry` callers; correct `ResumePoint` reference edges), so this is a **UX/contract bug, not a graph bug**. - **Tickets touching the area:** clarion-9392f74881 (call_sites evidence), - clarion-893c46cc5f (clarion://context degraded-snapshot signal). Neither adds a + clarion-893c46cc5f (loomweave://context degraded-snapshot signal). Neither adds a per-result `scope_excludes` flag. - **Fix (file a P2):** every `callers_of`/`neighborhood`/`execution_paths_from` result carries a `scope_excludes: [...]` array naming what was *not* searched @@ -121,8 +121,8 @@ a test-harness artifact rather than a Clarion defect. ### C1. `entity_at` "dead" — **ARTIFACT + likely-real fragility, RE-TEST** - **Eval saw:** every call errored — input path "escapes project root - `/tmp/clarion-b8-elspeth-full-…`" while the DB stored - `/home/john/clarion/tests/perf/.../core.py`. That `/tmp` root was the eval's dead + `/tmp/loomweave-b8-elspeth-full-…`" while the DB stored + `/home/john/loomweave/tests/perf/.../core.py`. That `/tmp` root was the eval's dead `serve --config` project_root, **not** a property of a correctly-served instance. - **Re-test** with `--path /home/john/elspeth` (project_root matches the stored `/home/john/elspeth/...` paths). If it still rejects valid in-tree paths, it's a @@ -130,14 +130,14 @@ a test-harness artifact rather than a Clarion defect. do not pin "100% broken" on the tool. ### C2. Subsystem clustering quality (modularity 0.093) — **ARTIFACT, RE-TEST** -- **Eval saw** near-random clusters mixing Clarion's own source with the - `elspeth_mini` fixture — because the served DB was Clarion's 1872-entity +- **Eval saw** near-random clusters mixing Loomweave's own source with the + `elspeth_mini` fixture — because the served DB was Loomweave's 1872-entity self-analysis, not elspeth. Real elspeth has **134** subsystems over 36,814 entities. Re-run subsystem missions there before judging cluster quality. ### C3. "Corpus contamination / analysis had no scoping" — **WITHDRAWN (ARTIFACT)** -- Not a Clarion bug. The real elspeth analysis is correctly scoped (verified: - 36,680 elspeth entities, 0 fixture/clarion rows). The wrong DB was *served*, via +- Not a Loomweave bug. The real elspeth analysis is correctly scoped (verified: + 36,680 elspeth entities, 0 fixture/loomweave rows). The wrong DB was *served*, via the staging artifact below. Remove from the bug list. ### C4. `serve` keeps serving a deleted DB inode — **MINOR ROBUSTNESS, OPTIONAL** diff --git a/docs/implementation/phaseB-rename-agent-brief.md b/docs/implementation/phaseB-rename-agent-brief.md new file mode 100644 index 00000000..6d538587 --- /dev/null +++ b/docs/implementation/phaseB-rename-agent-brief.md @@ -0,0 +1,76 @@ +# Phase B — Rename agent brief (Clarion → Loomweave, Loom → Weft) + +> Standalone task brief for a fresh agent. Assumes zero context from prior sessions. +> Companion to the master plan `docs/implementation/2026-06-05-loomweave-1.0-rename-and-pypi-plan.md` (Phase B is the spec). Phases A/C/D are NOT in scope here. + +--- + +You are executing a large, trap-laden product rename in a Rust + Python monorepo. Work from `/home/john/clarion`. + +## Goal +Rename the product **Clarion → Loomweave** and the framework/suite **Loom → Weft**, and recut the version **1.3.0 → 1.0.0**, as ONE atomic change on a single branch. This is a triage problem (~403 working-set files mention `clarion`), NOT a blind sweep: most renames, a handful of buckets must NOT be touched, a few are cross-product. + +## READ FIRST (authoritative detail) +- `docs/implementation/2026-06-05-loomweave-1.0-rename-and-pypi-plan.md` — **Phase B** is your spec (workstreams WS1–WS9, the reference-triage recipe, the do-not-rename list, the execution strategy, and the verification gate). Phases C/D are NOT yours (PyPI packaging + infra come later). +- Skim `docs/superpowers/specs/2026-06-05-loomweave-pypi-distribution-design.md` for context only. + +## Naming hierarchy (the target end state) +**Weft** framework (was "Loom") › **Loomweave** (flagship, was "Clarion"), **Filigree**, **Wardline**, **Legis** (+ **Shuttle** planned). + +## Exact transformations +- `clarion` → `loomweave` (dirs, paths, crate names, binary, package names — bulk) +- `Clarion` → `Loomweave` (prose, doc titles, `site_name` — bulk) +- `CLARION` → `LOOMWEAVE` (env vars — EXCEPT the LOOM carve-out below) +- `clarion_` → `loomweave_` (Rust identifiers — EXCEPT `clarion_entity_id`, see cross-product) +- `clarion-` → `loomweave-` (crate names, plugin prefix `clarion-plugin-*` → `loomweave-plugin-*` — EXCEPT Filigree issue IDs) +- Framework/federation env vars: `CLARION_LOOM_*` → `WEFT_*` (NOT `LOOMWEAVE_*`, and NOT `LOOMWEAVE_LOOM_*` — that's the double-loom trap) +- Python: package `clarion-plugin-python` → `loomweave-plugin-python`; module `clarion_plugin_python` → `loomweave_plugin_python`; shared-data path `share/clarion/plugins/` → `share/loomweave/plugins/` +- Persisted: `.clarion/` → `.loomweave/`; `clarion.db` → `loomweave.db`; `clarion.yaml` → `loomweave.yaml` +- Version: `1.3.0` → `1.0.0` in root `Cargo.toml` (workspace.package) and every `pyproject.toml`; add a CHANGELOG `## [1.0.0] — Loomweave` entry re-baselining history +- URLs: `github.com/tachyon-beep/clarion` → `github.com/foundryside-dev/loomweave`; docs domain `clarion.foundryside.dev` → `loomweave.foundryside.dev` (in `web/mkdocs.yml` etc.) +- SQLite `application_id` magic `0x434C524E ("CLRN")` → `0x4C4D5756 ("LMWV")` (owner-approved; updates the ForeignDatabase guard + its tests) +- ADR-021: add/keep the discovery-source amendment; its `clarion-plugin-*` refs become `loomweave-plugin-*` + +## DO NOT RENAME (a naive find/replace corrupts these — exclude by path/regex) +1. **Filigree issue IDs** `clarion-[0-9a-f]{8,}` and `clarion-sf-*` anywhere (docs, CHANGELOG, commit messages, ADR filenames) — historical identifiers; the Filigree prefix stays `clarion` by owner decision. +2. **`.filigree.conf`** `project_name` / `prefix` — stay `clarion`. +3. **`/api/loom/...` wire paths and `api_version`** — a versioned federation contract, not the product. LEAVE until Wardline/Filigree move in lockstep. +4. **`docs/archive/`** dated reports — historical record; leave as-is (they also contain `clarion-XXXX` IDs). +5. Recorded **test corpus / golden-snapshot identifiers** that double as keys — verify before touching any fixture. + +## CROSS-PRODUCT — flag to the owner, do NOT break unilaterally +- `--clarion-url` flag consumed by **Wardline** (see repo-root `.mcp.json`): renaming to `--loomweave-url` needs a coordinated change in the Wardline repo (same owner). Flag it; do not break it alone. +- `clarion_entity_id` federation field (`crates/clarion-federation/.../filigree.rs`) is the ADR-029 entity-association contract field read by Filigree. Before renaming to `loomweave_entity_id`, VERIFY Filigree's read path treats `entity_id` as opaque (it should). If unverifiable, leave it and flag. + +## Hard constraints +- **ONE atomic branch** named `rename/clarion-to-loomweave`. Do NOT drip across small PRs — a ~400-file rename races the concurrent agent ("Antigravity") that commits to this repo in real time. Confirm a freeze window with the owner before starting. +- Base the branch so it INCLUDES the already-landed Phase A foundation commits `cecc134` (current_exe() plugin discovery level) and `7305af9` (doctor real-discovery) — they live on branch `docs/pypi-distribution-spec`. Either branch from there, or branch from latest `main` and cherry-pick those two. These commits contain literal `clarion-plugin-`/`share/clarion/` strings that your mechanical pass MUST sweep like everything else — the logic is final, only the names change. +- Use `git mv` for crate directories (`crates/clarion-*` → `crates/loomweave-*`) and `docs/clarion/` → `docs/loomweave/` to preserve history (the owner rejects stale-name "history preservation"; prefer real renames). +- **Do NOT push and do NOT open a PR** — stop and hand back to the owner when the verification gate is green. + +## Execution order (per plan §B.6) +1. Create the branch (freeze window coordinated). Confirm Phase A commits are in its history. +2. Mechanical pass per case-variant (`clarion`/`Clarion`/`CLARION`/`clarion_`/`clarion-`), with the DO-NOT-RENAME buckets excluded by path/regex. Then the framework pass (`Loom`→`Weft` for the framework identity + `CLARION_LOOM_*`→`WEFT_*`). +3. `git mv` crate dirs + `docs/clarion/`. +4. Regenerate `Cargo.lock`. +5. MANUAL review of every carve-out: LOOM→WEFT env vars, `clarion_entity_id`, `--clarion-url`, Filigree issue IDs, `docs/archive/`, DB magic, golden snapshots. +6. Version recut 1.3.0→1.0.0 + CHANGELOG entry. + +## Verification gate — ALL must pass before declaring done +```bash +cargo build --workspace +cargo nextest run +(cd plugins/python && pytest) +# residual audit — every remaining hit must be an INTENTIONAL carve-out: +grep -rniE 'clarion' . | grep -vE '/(target|\.git|.*cache|site-build)/' \ + | grep -vE 'clarion-[0-9a-f]{8}|clarion-sf-|/api/loom|\.filigree|docs/archive' +``` +The residual audit returning ONLY known carve-outs is your completeness proof. Then wipe stale `.clarion/` and run `loomweave analyze /home/john/clarion` to rebuild the index under the new paths. + +## Report back +- Branch name + base, and confirmation the Phase A commits are included. +- Counts: files changed, crates `git mv`'d. +- The residual-audit output (proving only carve-outs remain), with each remaining category named. +- Test/build results (exact pass counts). +- Every cross-product item you flagged rather than changed (Wardline `--clarion-url`, `clarion_entity_id` if unverified). +- Anything you were unsure about — STOP and ask rather than guessing on a destructive rename. diff --git a/docs/implementation/review-sweep-2026-05-29.md b/docs/implementation/review-sweep-2026-05-29.md index f0a7a9c4..98928b34 100644 --- a/docs/implementation/review-sweep-2026-05-29.md +++ b/docs/implementation/review-sweep-2026-05-29.md @@ -1,14 +1,14 @@ # Full-codebase review sweep — 2026-05-29 -A multi-agent code-review sweep over Clarion's own source + tests, chunked into +A multi-agent code-review sweep over Loomweave's own source + tests, chunked into ~2000-line semantically-coherent units. Each chunk is reviewed by a team of five agents (architecture critic, systems thinker, language engineer, quality engineer, + a chunk-specific specialist). Findings are deduped per chunk and filed as Filigree issues. -**Scope:** Clarion's own code only. The `tests/perf/elspeth_mini/` customer +**Scope:** Loomweave's own code only. The `tests/perf/elspeth_mini/` customer fixture corpus (~33k lines) is **excluded** — it is representative customer -code, not Clarion source. +code, not Loomweave source. **Filigree tagging:** every issue gets `from-review-sweep-2026-05-29` (batch source, for bulk triage/revert), a `chunk:` label, a `review:` @@ -25,43 +25,43 @@ one chunk-specific specialist (see table). A sixth agent per chunk synthesizes ## Chunk manifest (35 chunks) -### Rust — clarion-core src +### Rust — loomweave-core src | id | files / ranges | specialist | |----|----------------|------------| -| core-host-a | `clarion-core/src/plugin/host.rs:1-1470` | threat-analyst | -| core-host-b | `clarion-core/src/plugin/host.rs:1471-2935` | silent-failure-hunter | -| core-llm | `clarion-core/src/llm_provider.rs` (2467) | silent-failure-hunter | +| core-host-a | `loomweave-core/src/plugin/host.rs:1-1470` | threat-analyst | +| core-host-b | `loomweave-core/src/plugin/host.rs:1471-2935` | silent-failure-hunter | +| core-llm | `loomweave-core/src/llm_provider.rs` (2467) | silent-failure-hunter | | core-manifest-protocol | `manifest.rs` (1119) + `plugin/protocol.rs` (875) | type-design-analyzer | | core-mock-discovery-transport | `plugin/mock.rs` (876) + `discovery.rs` (667) + `transport.rs` (569) | code-reviewer | | core-entityid-jail-limits | `entity_id.rs` (596) + `plugin/limits.rs` (572) + `breaker.rs` (360) + `jail.rs` (260) + `host_findings.rs` (273) + `mod.rs` + `lib.rs` | threat-analyst | -### Rust — clarion-cli src +### Rust — loomweave-cli src | id | files / ranges | specialist | |----|----------------|------------| -| cli-analyze-a | `clarion-cli/src/analyze.rs:1-1220` | code-reviewer | -| cli-analyze-b | `clarion-cli/src/analyze.rs:1221-2433` | code-reviewer | -| cli-http-read | `clarion-cli/src/http_read.rs` (1736) | api-reviewer | +| cli-analyze-a | `loomweave-cli/src/analyze.rs:1-1220` | code-reviewer | +| cli-analyze-b | `loomweave-cli/src/analyze.rs:1221-2433` | code-reviewer | +| cli-http-read | `loomweave-cli/src/http_read.rs` (1736) | api-reviewer | | cli-secret-scan | `secret_scan.rs` + `secret_scan/{findings,files,anchors,baseline}.rs` + `serve.rs` + `instance.rs` | threat-analyst | | cli-misc | `clustering.rs` + `install.rs` + `skill_pack.rs` + `analyze_lock.rs` + `config.rs` + `main.rs` + `cli.rs` + `stats.rs` + `run_lifecycle.rs` | code-reviewer | -### Rust — clarion-mcp src +### Rust — loomweave-mcp src | id | files / ranges | specialist | |----|----------------|------------| -| mcp-lib-a | `clarion-mcp/src/lib.rs:1-1150` | api-reviewer | -| mcp-lib-b | `clarion-mcp/src/lib.rs:1151-2300` | api-reviewer | -| mcp-lib-c | `clarion-mcp/src/lib.rs:2301-3449` | api-reviewer | +| mcp-lib-a | `loomweave-mcp/src/lib.rs:1-1150` | api-reviewer | +| mcp-lib-b | `loomweave-mcp/src/lib.rs:1151-2300` | api-reviewer | +| mcp-lib-c | `loomweave-mcp/src/lib.rs:2301-3449` | api-reviewer | | mcp-config-filigree | `config.rs` (956) + `filigree.rs` (238) | code-reviewer | -### Rust — clarion-storage src +### Rust — loomweave-storage src | id | files / ranges | specialist | |----|----------------|------------| | storage-query | `query.rs` (1097) + `schema.rs` (209) + `pragma.rs` (109) | embedded-database-reviewer | | storage-writer | `writer.rs` (1080) + `cache.rs` + `commands.rs` + `error.rs` + `reader.rs` + `unresolved.rs` + `lib.rs` | embedded-database-reviewer | -### Rust — clarion-scanner + fixture src +### Rust — loomweave-scanner + fixture src | id | files / ranges | specialist | |----|----------------|------------| -| scanner-src | `clarion-scanner/src/*` (patterns/baseline/lib/entropy) + `clarion-plugin-fixture/src/*` | threat-analyst | +| scanner-src | `loomweave-scanner/src/*` (patterns/baseline/lib/entropy) + `loomweave-plugin-fixture/src/*` | threat-analyst | ### Python plugin src | id | files / ranges | specialist | @@ -72,12 +72,12 @@ one chunk-specific specialist (see table). A sixth agent per chunk synthesizes ### Rust tests | id | files / ranges | specialist | |----|----------------|------------| -| test-serve-a | `clarion-cli/tests/serve.rs:1-1340` | api-reviewer | -| test-serve-b | `clarion-cli/tests/serve.rs:1341-2683` | api-reviewer | -| test-writer-actor-a | `clarion-storage/tests/writer_actor.rs:1-1235` | embedded-database-reviewer | -| test-writer-actor-b | `clarion-storage/tests/writer_actor.rs:1236-2471` | embedded-database-reviewer | -| test-storage-tools-a | `clarion-mcp/tests/storage_tools.rs:1-1115` | api-reviewer | -| test-storage-tools-b | `clarion-mcp/tests/storage_tools.rs:1116-2233` | api-reviewer | +| test-serve-a | `loomweave-cli/tests/serve.rs:1-1340` | api-reviewer | +| test-serve-b | `loomweave-cli/tests/serve.rs:1341-2683` | api-reviewer | +| test-writer-actor-a | `loomweave-storage/tests/writer_actor.rs:1-1235` | embedded-database-reviewer | +| test-writer-actor-b | `loomweave-storage/tests/writer_actor.rs:1236-2471` | embedded-database-reviewer | +| test-storage-tools-a | `loomweave-mcp/tests/storage_tools.rs:1-1115` | api-reviewer | +| test-storage-tools-b | `loomweave-mcp/tests/storage_tools.rs:1116-2233` | api-reviewer | | test-cli-analyze | `analyze.rs` (1221) + `analyze_failure_modes.rs` + `install.rs` | coverage-gap-analyst | | test-storage-query | `query_helpers.rs` (1082) + `reader_pool.rs` + `llm_cache.rs` | embedded-database-reviewer | | test-schema-host | `schema_apply.rs` (1051) + `core/tests/host_subprocess.rs` | embedded-database-reviewer | diff --git a/docs/implementation/sprint-1/README.md b/docs/implementation/sprint-1/README.md index 77972fa4..642cf120 100644 --- a/docs/implementation/sprint-1/README.md +++ b/docs/implementation/sprint-1/README.md @@ -1,4 +1,4 @@ -# Clarion Sprint 1 — Walking Skeleton +# Loomweave Sprint 1 — Walking Skeleton **Status**: DRAFT — pending design review and Filigree seeding **Sprint goal**: [Tier A — walking skeleton](./signoffs.md#tier-a--sprint-1-close-walking-skeleton) @@ -11,9 +11,9 @@ ## 1. Sprint 1 in one paragraph -Sprint 1 ships the smallest end-to-end state of Clarion that exercises every interface -contract the rest of v0.1 depends on. After Sprint 1 closes, `clarion install` creates a -real `.clarion/` directory; `clarion analyze ` spawns the Python +Sprint 1 ships the smallest end-to-end state of Loomweave that exercises every interface +contract the rest of v0.1 depends on. After Sprint 1 closes, `loomweave install` creates a +real `.loomweave/` directory; `loomweave analyze ` spawns the Python plugin, the plugin emits at least one entity (a Python function), the writer-actor persists it to SQLite, and a shell-level `sqlite3` query reads it back. No clustering, no rendering, no findings, no LLM — just the spine. The point is to lock in the storage @@ -53,18 +53,18 @@ cargo build --workspace --release pip install -e plugins/python # 3. Init a scratch project -mkdir -p /tmp/clarion-demo && cd /tmp/clarion-demo +mkdir -p /tmp/loomweave-demo && cd /tmp/loomweave-demo echo 'def hello(): return "world"' > demo.py -# 4. Clarion install → .clarion/ created with DB + default config -clarion install -test -f .clarion/clarion.db +# 4. Loomweave install → .loomweave/ created with DB + default config +loomweave install +test -f .loomweave/loomweave.db -# 5. Clarion analyze → plugin spawned, entity persisted -clarion analyze . +# 5. Loomweave analyze → plugin spawned, entity persisted +loomweave analyze . # 6. Entity visible via raw SQL — column is `id` (L2 EntityId), not `canonical_name` -sqlite3 .clarion/clarion.db "select id, kind from entities;" +sqlite3 .loomweave/loomweave.db "select id, kind from entities;" # expected: python:function:demo.hello|function (per the locked 3-segment L2 format) ``` @@ -75,7 +75,7 @@ Each of these steps is owned by a Sprint 1 WP: | 1 | WP1 | Crate topology, build hygiene | | 2 | WP3 | Python plugin packaging, plugin-discovery convention (L9) | | 3 | (scratch prep) | — | -| 4 | WP1 | `clarion install` + schema migration (L1) + writer-actor init (L3) | +| 4 | WP1 | `loomweave install` + schema migration (L1) + writer-actor init (L3) | | 5 | WP1 + WP2 + WP3 | JSON-RPC transport (L4) + manifest validation (L5) + core-enforced minimums (L6) + qualname production (L7) + REGISTRY probe (L8) | | 6 | WP1 | Schema shape (L1) + entity-ID 3-segment format (L2) | @@ -88,8 +88,8 @@ in its owning WP doc. | # | Lock-in | Owning WP | Canonical section | `↗` cross-product touch | |---|---|---|---|---| -| L1 | SQLite schema shape per [detailed-design §3](../../clarion/1.0/detailed-design.md#3-storage-implementation) — tables `entities`, `entity_tags`, `edges`, `findings`, `summary_cache`, `runs`, `schema_migrations`; `entity_fts` FTS5 virtual table + triggers; generated columns + indexes; `guidance_sheets` view _(locked on 2026-04-18)_ | WP1 | [`wp1-scaffold.md#l1--sqlite-schema-shape`](./wp1-scaffold.md#l1--sqlite-schema-shape) | `↗` Filigree `registry_backend: clarion` (WP10) reads via entity-ID columns | -| L2 | Entity-ID 3-segment format `{plugin_id}:{kind}:{canonical_qualified_name}` per ADR-003 + ADR-022 _(locked on 2026-04-18)_ | WP1 + WP3 | [`wp1-scaffold.md#l2--entity-id-canonical-name-format`](./wp1-scaffold.md#l2--entity-id-canonical-name-format) | `↗` Wardline qualname reconciliation (ADR-018) uses the third segment as its Clarion-side join key | +| L1 | SQLite schema shape per [detailed-design §3](../../loomweave/1.0/detailed-design.md#3-storage-implementation) — tables `entities`, `entity_tags`, `edges`, `findings`, `summary_cache`, `runs`, `schema_migrations`; `entity_fts` FTS5 virtual table + triggers; generated columns + indexes; `guidance_sheets` view _(locked on 2026-04-18)_ | WP1 | [`wp1-scaffold.md#l1--sqlite-schema-shape`](./wp1-scaffold.md#l1--sqlite-schema-shape) | `↗` Filigree `registry_backend: loomweave` (WP10) reads via entity-ID columns | +| L2 | Entity-ID 3-segment format `{plugin_id}:{kind}:{canonical_qualified_name}` per ADR-003 + ADR-022 _(locked on 2026-04-18)_ | WP1 + WP3 | [`wp1-scaffold.md#l2--entity-id-canonical-name-format`](./wp1-scaffold.md#l2--entity-id-canonical-name-format) | `↗` Wardline qualname reconciliation (ADR-018) uses the third segment as its Loomweave-side join key | | L3 | Writer-actor command protocol (`tokio::task` + bounded `mpsc` + per-N commit) per ADR-011 _(locked on 2026-04-18)_ | WP1 | [`wp1-scaffold.md#l3--writer-actor-command-protocol`](./wp1-scaffold.md#l3--writer-actor-command-protocol) | — | | L4 | JSON-RPC method set + Content-Length framing per ADR-002 _(locked on 2026-04-24)_ | WP2 | [`wp2-plugin-host.md#l4--json-rpc-method-set--content-length-framing`](./wp2-plugin-host.md#l4--json-rpc-method-set--content-length-framing) | — | | L5 | `plugin.toml` manifest schema per ADR-022 _(locked on 2026-04-24)_ | WP2 | [`wp2-plugin-host.md#l5--plugintoml-manifest-schema`](./wp2-plugin-host.md#l5--plugintoml-manifest-schema) | — | @@ -99,7 +99,7 @@ in its owning WP doc. | L9 | `plugin.toml` discovery convention (where the manifest lives, how the host finds it) _(locked on 2026-04-24)_ | WP2 + WP3 | [`wp2-plugin-host.md#l9--plugin-discovery-convention`](./wp2-plugin-host.md#l9--plugin-discovery-convention) | — | **Items deliberately NOT locked by Sprint 1** (kept cheap-to-change for later sprints): -- `clarion.yaml` config schema — stubbed only; WP6 (LLM dispatch) forces the full shape. +- `loomweave.yaml` config schema — stubbed only; WP6 (LLM dispatch) forces the full shape. - Findings emission format — ADR-004 already locked; Sprint 1 does not emit findings. - MCP tool catalogue — WP8 owns. - Cluster algorithm + subsystem detection — WP4 owns. @@ -128,7 +128,7 @@ more than one WP (and therefore could force rework if resolved late) are: | UQ-S1-01 | ~~Rust async runtime choice~~ — **resolved by ADR-011**: `tokio`. Writer-actor is a `tokio::task`; WP2/WP8 inherit the same runtime. | WP1 + WP2 | Resolved pre-sprint | | UQ-S1-02 | ~~SQLite crate choice~~ — **resolved by ADR-011**: `rusqlite` (with `deadpool-sqlite` for the reader pool). | WP1 | Resolved pre-sprint | | UQ-S1-03 | JSON-RPC library choice (hand-rolled over `serde_json` vs `jsonrpsee` vs other) | WP2 | Before WP2 Task 2 | -| UQ-S1-04 | Plugin discovery mechanism (PATH-based like `git` subcommands vs explicit `~/.config/clarion/plugins/` vs config-listed paths) | WP2 + WP3 | Before WP2 Task 5 | +| UQ-S1-04 | Plugin discovery mechanism (PATH-based like `git` subcommands vs explicit `~/.config/loomweave/plugins/` vs config-listed paths) | WP2 + WP3 | Before WP2 Task 5 | | UQ-S1-05 | ~~Whether WP3 imports `wardline.core.registry.REGISTRY` or stubs the probe~~ — **resolved "fully wire"**; symbol existence verified at `wardline/src/wardline/core/registry.py:55` and `wardline/src/wardline/__init__.py:3` (2026-04-18 pre-sprint check). | WP3 | Resolved pre-sprint | | UQ-S1-06 | Minimum supported Python version for the plugin (3.11? 3.12?) | WP3 | Before WP3 Task 1 | | UQ-S1-07 | Whether the writer-actor batches acks per-transaction or per-command | WP1 | Before WP1 Task 6 | @@ -146,11 +146,11 @@ re-litigate during the sprint: - Clustering, subsystem detection, cross-cutting findings — all WP4 work. - LLM dispatch, summary cache, briefings — WP6 work. - Guidance system — WP7. -- `clarion serve` (MCP or HTTP) — WP8; Sprint 1 ships only `install` and `analyze` subcommands. +- `loomweave serve` (MCP or HTTP) — WP8; Sprint 1 ships only `install` and `analyze` subcommands. - Secret scanner — WP5. - Findings emission to Filigree, observation transport — WP9. -- `registry_backend: clarion` mode in Filigree — WP10. -- Full Python-plugin feature coverage (all `CLA-PY-*` rules, every entity/edge kind) — deferred to the WP3-feature-complete sprint. +- `registry_backend: loomweave` mode in Filigree — WP10. +- Full Python-plugin feature coverage (all `LMWV-PY-*` rules, every entity/edge kind) — deferred to the WP3-feature-complete sprint. - macOS/Windows support — Sprint 1 is Linux only. Cross-platform is future work. ## 8. Filigree seeding @@ -172,14 +172,14 @@ and pointed at [`signoffs.md`](./signoffs.md) Tier A. ## 9. References -- [Clarion v0.1 high-level implementation plan](../v0.1-plan.md) -- [Clarion system design](../../clarion/1.0/system-design.md) — §2 (core/plugin), §4 (storage) -- [Clarion detailed design](../../clarion/1.0/detailed-design.md) — §1 (plugin transport), §3 (storage impl) -- [ADR-001 Rust for core](../../clarion/adr/ADR-001-rust-for-core.md) -- [ADR-002 Plugin transport JSON-RPC](../../clarion/adr/ADR-002-plugin-transport-json-rpc.md) -- [ADR-003 Entity ID scheme](../../clarion/adr/ADR-003-entity-id-scheme.md) -- [ADR-011 Writer-actor concurrency](../../clarion/adr/ADR-011-writer-actor-concurrency.md) -- [ADR-018 Identity reconciliation](../../clarion/adr/ADR-018-identity-reconciliation.md) -- [ADR-021 Plugin authority hybrid](../../clarion/adr/ADR-021-plugin-authority-hybrid.md) -- [ADR-022 Core/plugin ontology](../../clarion/adr/ADR-022-core-plugin-ontology.md) -- [ADR-023 Rust + Python tooling baseline](../../clarion/adr/ADR-023-tooling-baseline.md) +- [Loomweave v0.1 high-level implementation plan](../v0.1-plan.md) +- [Loomweave system design](../../loomweave/1.0/system-design.md) — §2 (core/plugin), §4 (storage) +- [Loomweave detailed design](../../loomweave/1.0/detailed-design.md) — §1 (plugin transport), §3 (storage impl) +- [ADR-001 Rust for core](../../loomweave/adr/ADR-001-rust-for-core.md) +- [ADR-002 Plugin transport JSON-RPC](../../loomweave/adr/ADR-002-plugin-transport-json-rpc.md) +- [ADR-003 Entity ID scheme](../../loomweave/adr/ADR-003-entity-id-scheme.md) +- [ADR-011 Writer-actor concurrency](../../loomweave/adr/ADR-011-writer-actor-concurrency.md) +- [ADR-018 Identity reconciliation](../../loomweave/adr/ADR-018-identity-reconciliation.md) +- [ADR-021 Plugin authority hybrid](../../loomweave/adr/ADR-021-plugin-authority-hybrid.md) +- [ADR-022 Core/plugin ontology](../../loomweave/adr/ADR-022-core-plugin-ontology.md) +- [ADR-023 Rust + Python tooling baseline](../../loomweave/adr/ADR-023-tooling-baseline.md) diff --git a/docs/implementation/sprint-1/signoffs.md b/docs/implementation/sprint-1/signoffs.md index ef26a51f..5ce39ae2 100644 --- a/docs/implementation/sprint-1/signoffs.md +++ b/docs/implementation/sprint-1/signoffs.md @@ -1,4 +1,4 @@ -# Clarion Sprint 1 — Sign-off Ladder +# Loomweave Sprint 1 — Sign-off Ladder **Status**: DRAFT — becomes the closing gate for Sprint 1 **Scope**: [WP1](./wp1-scaffold.md), [WP2](./wp2-plugin-host.md), [WP3](./wp3-python-plugin.md) @@ -29,37 +29,37 @@ locked design requires a follow-up ADR and cross-WP impact analysis. - [x] **A.1.2c** — `cargo deny check` passes — advisories, licenses, bans, sources all green (ADR-023 gate). Proof: CI log. - [x] **A.1.2d** — `cargo doc --no-deps --all-features` builds without warnings (ADR-023 gate). Proof: CI log. - [x] **A.1.2e** — GitHub Actions CI workflow exists at `.github/workflows/ci.yml` and all five jobs (fmt, clippy, nextest, doc, deny) are green on the WP1 PR (ADR-023 gate). Proof: PR URL + green-checks screenshot or CI log. -- [x] **A.1.3** — **L1 locked**: migration file `0001_initial_schema.sql` contains every table, virtual table, trigger, generated column, and view from [detailed-design.md §3](../../clarion/v0.1/detailed-design.md#3-storage-implementation): tables `entities`, `entity_tags`, `edges`, `findings`, `summary_cache`, `runs`, `schema_migrations`; virtual table `entity_fts` (FTS5); triggers `entities_ai`, `entities_au`, `entities_ad`; generated columns `entities.priority` + `ix_entities_priority`, `entities.git_churn_count` + `ix_entities_churn`; view `guidance_sheets`. Proof: migration file commit; verification via `sqlite3 < migrations/0001_initial_schema.sql` against a fresh DB produces the expected schema; `schema_apply` integration test (WP1 Task 3) passes all assertions. _Locked on 2026-04-18._ _Post-lock amendment 2026-05-03 ([ADR-024](../../clarion/adr/ADR-024-guidance-schema-vocabulary.md)): the `entities.priority` generated column + `ix_entities_priority` index were renamed/split to `entities.scope_level` + `entities.scope_rank` + `ix_entities_scope_rank`. The L1 lock language above describes the shape locked at sprint close; ADR-024 is the post-Sprint-1 schema correction under the in-place migration-edit policy named in that ADR._ -- [x] **A.1.4** — **L2 locked**: `entity_id()` Rust assembler produces the 3-segment `{plugin_id}:{kind}:{canonical_qualified_name}` form per ADR-003 + ADR-022 and passes all rows in `/fixtures/entity_id.json`. Proof: passing test in `clarion-core`. _Locked on 2026-04-18._ -- [x] **A.1.5** — **L3 locked**: `WriterCmd` enum and per-N-batch writer-actor shipped; per-command ack, batch-boundary commit, rollback on `FailRun` each have a passing test. Proof: tests in `clarion-storage`. _Locked on 2026-04-18._ -- [x] **A.1.6** — `clarion install` in a fresh tempdir produces `.clarion/{clarion.db, config.json, .gitignore}` **plus** a `clarion.yaml` stub at the project root (per [detailed-design.md §File layout](../../clarion/v0.1/detailed-design.md#file-layout); `.clarion/` holds internal state, `clarion.yaml` is the user-edited config). Proof: integration test passing. -- [x] **A.1.7** — `clarion install` refuses to overwrite an existing `.clarion/` without `--force`. Proof: negative integration test passing. -- [x] **A.1.8** — `clarion analyze .` in a plugin-less scratch dir produces a `runs` row with status `skipped_no_plugins`. Proof: integration test passing. -- [x] **A.1.9** — **ADR-005 authored** and moved from backlog to Accepted in [`../../clarion/adr/README.md`](../../clarion/adr/README.md). Proof: ADR file commit. +- [x] **A.1.3** — **L1 locked**: migration file `0001_initial_schema.sql` contains every table, virtual table, trigger, generated column, and view from [detailed-design.md §3](../../loomweave/v0.1/detailed-design.md#3-storage-implementation): tables `entities`, `entity_tags`, `edges`, `findings`, `summary_cache`, `runs`, `schema_migrations`; virtual table `entity_fts` (FTS5); triggers `entities_ai`, `entities_au`, `entities_ad`; generated columns `entities.priority` + `ix_entities_priority`, `entities.git_churn_count` + `ix_entities_churn`; view `guidance_sheets`. Proof: migration file commit; verification via `sqlite3 < migrations/0001_initial_schema.sql` against a fresh DB produces the expected schema; `schema_apply` integration test (WP1 Task 3) passes all assertions. _Locked on 2026-04-18._ _Post-lock amendment 2026-05-03 ([ADR-024](../../loomweave/adr/ADR-024-guidance-schema-vocabulary.md)): the `entities.priority` generated column + `ix_entities_priority` index were renamed/split to `entities.scope_level` + `entities.scope_rank` + `ix_entities_scope_rank`. The L1 lock language above describes the shape locked at sprint close; ADR-024 is the post-Sprint-1 schema correction under the in-place migration-edit policy named in that ADR._ +- [x] **A.1.4** — **L2 locked**: `entity_id()` Rust assembler produces the 3-segment `{plugin_id}:{kind}:{canonical_qualified_name}` form per ADR-003 + ADR-022 and passes all rows in `/fixtures/entity_id.json`. Proof: passing test in `loomweave-core`. _Locked on 2026-04-18._ +- [x] **A.1.5** — **L3 locked**: `WriterCmd` enum and per-N-batch writer-actor shipped; per-command ack, batch-boundary commit, rollback on `FailRun` each have a passing test. Proof: tests in `loomweave-storage`. _Locked on 2026-04-18._ +- [x] **A.1.6** — `loomweave install` in a fresh tempdir produces `.loomweave/{loomweave.db, config.json, .gitignore}` **plus** a `loomweave.yaml` stub at the project root (per [detailed-design.md §File layout](../../loomweave/v0.1/detailed-design.md#file-layout); `.loomweave/` holds internal state, `loomweave.yaml` is the user-edited config). Proof: integration test passing. +- [x] **A.1.7** — `loomweave install` refuses to overwrite an existing `.loomweave/` without `--force`. Proof: negative integration test passing. +- [x] **A.1.8** — `loomweave analyze .` in a plugin-less scratch dir produces a `runs` row with status `skipped_no_plugins`. Proof: integration test passing. +- [x] **A.1.9** — **ADR-005 authored** and moved from backlog to Accepted in [`../../loomweave/adr/README.md`](../../loomweave/adr/README.md). Proof: ADR file commit. - [x] **A.1.9a** — **ADR-023 authored** (tooling baseline) and Accepted in the ADR index. Every artefact listed in ADR-023 §Decision is present in Task 1's commit: `rust-toolchain.toml`, `rustfmt.toml`, `clippy.toml`, `deny.toml`, workspace `[lints]` block with every member crate opting in via `lints.workspace = true`, and `.github/workflows/ci.yml`. Proof: ADR file commit + artefact listing in the Task-1 commit message. - [x] **A.1.10** — Every UQ-WP1-* marked resolved in [`wp1-scaffold.md §5`](./wp1-scaffold.md#5-unresolved-questions). UQ-WP1-09 specifically reads "resolved by ADR-023" rather than the original "fine to document and move on" framing. Proof: doc commit showing resolution state. ### A.2 Plugin host (WP2) -- [x] **A.2.1** — **L4 locked**: JSON-RPC method set (`initialize`, `initialized`, `analyze_file`, `shutdown`, `exit`) + Content-Length framing round-trip tested. Proof: tests in `clarion-core::plugin::transport` + end-to-end via `wp2_e2e_smoke_fixture_plugin_round_trip` (T1). _Locked on 2026-04-24._ -- [x] **A.2.2** — **L5 locked**: `plugin.toml` schema parsed and validated; rejects manifests missing required fields. Proof: 30+ positive/negative tests in `clarion-core::plugin::manifest`. _Locked on 2026-04-24._ -- [x] **A.2.3** — **L6 locked**: path jail (drop-not-kill on first offense per ADR-021 §2a; >10 escapes/60s sub-breaker), 8 MiB Content-Length ceiling, 500k per-run entity-count cap, 2 GiB `RLIMIT_AS` each have both positive and negative tests passing. Jail coverage is **`analyze_file` response paths only** — `file_list` RPC and its jail enforcement point are deferred to Tier B per WP2 §L4 and §L6. Proof: tests in `clarion-core::plugin::{jail, limits}` + host-level `content_length_ceiling_surfaces_through_plugin_host` + host-level entity-cap test (T9) + `apply_prlimit_linux_returns_ok`. _Locked on 2026-04-24._ -- [x] **A.2.4** — **L9 locked**: plugin discovery finds `clarion-plugin-*` binaries on `$PATH` and loads neighboring `plugin.toml`. Proof: tests T1–T8 in `clarion-core::plugin::discovery`, plus T10/T11 spawn-safety tests (manifest `executable` must be bare basename matching discovered binary; scrub commit `eb0a41d`) and `DiscoveryError::WorldWritableDir` refusal (scrub commit `7c0e396`). _Locked on 2026-04-24._ +- [x] **A.2.1** — **L4 locked**: JSON-RPC method set (`initialize`, `initialized`, `analyze_file`, `shutdown`, `exit`) + Content-Length framing round-trip tested. Proof: tests in `loomweave-core::plugin::transport` + end-to-end via `wp2_e2e_smoke_fixture_plugin_round_trip` (T1). _Locked on 2026-04-24._ +- [x] **A.2.2** — **L5 locked**: `plugin.toml` schema parsed and validated; rejects manifests missing required fields. Proof: 30+ positive/negative tests in `loomweave-core::plugin::manifest`. _Locked on 2026-04-24._ +- [x] **A.2.3** — **L6 locked**: path jail (drop-not-kill on first offense per ADR-021 §2a; >10 escapes/60s sub-breaker), 8 MiB Content-Length ceiling, 500k per-run entity-count cap, 2 GiB `RLIMIT_AS` each have both positive and negative tests passing. Jail coverage is **`analyze_file` response paths only** — `file_list` RPC and its jail enforcement point are deferred to Tier B per WP2 §L4 and §L6. Proof: tests in `loomweave-core::plugin::{jail, limits}` + host-level `content_length_ceiling_surfaces_through_plugin_host` + host-level entity-cap test (T9) + `apply_prlimit_linux_returns_ok`. _Locked on 2026-04-24._ +- [x] **A.2.4** — **L9 locked**: plugin discovery finds `loomweave-plugin-*` binaries on `$PATH` and loads neighboring `plugin.toml`. Proof: tests T1–T8 in `loomweave-core::plugin::discovery`, plus T10/T11 spawn-safety tests (manifest `executable` must be bare basename matching discovered binary; scrub commit `eb0a41d`) and `DiscoveryError::WorldWritableDir` refusal (scrub commit `7c0e396`). _Locked on 2026-04-24._ - [x] **A.2.5** — Ontology-boundary enforcement drops entities whose `kind` is not in the manifest's `[ontology].entity_kinds`. Proof: host integration test T3 with pinned entity-count assertion. - [x] **A.2.6** — Identity-mismatch enforcement (UQ-WP2-11 resolution) drops entities whose `id` doesn't match `entity_id(plugin_id, kind, qualified_name)`. Proof: host integration test T4 + `cross_plugin_plugin_id_spoof_is_rejected` (scrub commit `89b2da0`). - [x] **A.2.7** — Crash-loop breaker trips after the configured number of crashes in the configured window. Proof: `breaker_*` unit tests + `wp2_crash_loop_breaker_trips_and_skips_remaining_plugins` end-to-end (scrub commit `7f8fc9a`). -- [x] **A.2.8** — `clarion analyze` with the fixture mock plugin produces ≥1 persisted entity. Proof: `wp2_e2e_smoke_fixture_plugin_round_trip` integration test. +- [x] **A.2.8** — `loomweave analyze` with the fixture mock plugin produces ≥1 persisted entity. Proof: `wp2_e2e_smoke_fixture_plugin_round_trip` integration test. - [x] **A.2.9** — Every UQ-WP2-* marked resolved in [`wp2-plugin-host.md §5`](./wp2-plugin-host.md#5-unresolved-questions). UQ-WP2-10 resolved by ADR-002 + ADR-021 §Layer 3; UQ-WP2-11 resolved by identity check / T4. Proof: doc commit (this one). -- [x] **A.2.10** — Manifest with malformed identifier grammar (entity kind violating `[a-z][a-z0-9_]*` or `rule_id_prefix` violating `CLA-[A-Z]+(-[A-Z0-9]+)+`) is rejected at parse with `CLA-INFRA-MANIFEST-MALFORMED` per ADR-022. Includes the reserved-prefix rejections (`rule_id_prefix = "CLA-INFRA-"` and `"CLA-FACT-"` → `CLA-INFRA-RULE-ID-NAMESPACE`). Proof: negative tests in `clarion-core::plugin::manifest`. -- [x] **A.2.11** — Manifest declaring a core-reserved entity kind (`file`, `subsystem`, or `guidance`) in `entity_kinds` is rejected at parse with `CLA-INFRA-MANIFEST-RESERVED-KIND` per ADR-022 §Core owns. Proof: negative test in `clarion-core::plugin::manifest`. -- [x] **A.2.12** — Manifest declaring `capabilities.runtime.reads_outside_project_root = true` is refused at `initialize` with `CLA-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY` per ADR-021 §Layer 1; the plugin process is terminated before any `analyze_file` dispatch. Proof: host integration test T2 in `clarion-core::plugin::host` with strengthened no-`initialized`-sent assertion (scrub commit `89b2da0`). +- [x] **A.2.10** — Manifest with malformed identifier grammar (entity kind violating `[a-z][a-z0-9_]*` or `rule_id_prefix` violating `LMWV-[A-Z]+(-[A-Z0-9]+)+`) is rejected at parse with `LMWV-INFRA-MANIFEST-MALFORMED` per ADR-022. Includes the reserved-prefix rejections (`rule_id_prefix = "LMWV-INFRA-"` and `"LMWV-FACT-"` → `LMWV-INFRA-RULE-ID-NAMESPACE`). Proof: negative tests in `loomweave-core::plugin::manifest`. +- [x] **A.2.11** — Manifest declaring a core-reserved entity kind (`file`, `subsystem`, or `guidance`) in `entity_kinds` is rejected at parse with `LMWV-INFRA-MANIFEST-RESERVED-KIND` per ADR-022 §Core owns. Proof: negative test in `loomweave-core::plugin::manifest`. +- [x] **A.2.12** — Manifest declaring `capabilities.runtime.reads_outside_project_root = true` is refused at `initialize` with `LMWV-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY` per ADR-021 §Layer 1; the plugin process is terminated before any `analyze_file` dispatch. Proof: host integration test T2 in `loomweave-core::plugin::host` with strengthened no-`initialized`-sent assertion (scrub commit `89b2da0`). ### A.3 Python plugin (WP3) -- [x] **A.3.1** — `pip install -e plugins/python` on a clean Python 3.11 venv places `clarion-plugin-python` on `$PATH`. Proof: verified via `plugins/python/.venv/bin/clarion-plugin-python --version`-equivalent stamp (exit 0) + walking-skeleton script (`tests/e2e/sprint_1_walking_skeleton.sh`). +- [x] **A.3.1** — `pip install -e plugins/python` on a clean Python 3.11 venv places `loomweave-plugin-python` on `$PATH`. Proof: verified via `plugins/python/.venv/bin/loomweave-plugin-python --version`-equivalent stamp (exit 0) + walking-skeleton script (`tests/e2e/sprint_1_walking_skeleton.sh`). - [x] **A.3.2** — **L7 locked**: qualname reconstruction matches the documented rules for module-level, nested, class, async, and nested-class cases. Proof: 9 tests in `test_qualname.py` passing (including UQ-WP3-01 nested class methods and UQ-WP3-07 overloaded methods). _Locked on 2026-04-24._ - [x] **A.3.3** — **L8 locked**: Wardline probe returns the three documented states (`absent`, `enabled`, `version_out_of_range`). The handshake `capabilities` field carries the probe result. Proof: 8 tests in `test_wardline_probe.py` (covering lower-bound inclusive, upper-bound exclusive, missing `__version__`, non-semver) + `test_server.py::test_initialize_roundtrip` verifying wiring. _Locked on 2026-04-24._ -- [x] **A.3.4** — Shared fixture `/fixtures/entity_id.json` passes in both `clarion-core` (Rust `entity_id()`) and `plugins/python` (Python `entity_id()`) test suites. Proof: Rust `entity_id::tests::shared_fixture_byte_for_byte_parity` (140-test clarion-core run) + Python `test_entity_id.py::test_matches_shared_fixture` (41 entity_id + extractor tests); 20 fixture rows covering the representative shapes. **This is L2+L7 byte-for-byte alignment proof.** +- [x] **A.3.4** — Shared fixture `/fixtures/entity_id.json` passes in both `loomweave-core` (Rust `entity_id()`) and `plugins/python` (Python `entity_id()`) test suites. Proof: Rust `entity_id::tests::shared_fixture_byte_for_byte_parity` (140-test loomweave-core run) + Python `test_entity_id.py::test_matches_shared_fixture` (41 entity_id + extractor tests); 20 fixture rows covering the representative shapes. **This is L2+L7 byte-for-byte alignment proof.** - [x] **A.3.5** — Round-trip self-test passes: plugin extracts entities from its own source and the host persists them. Proof: `test_round_trip.py::test_round_trip_self_analysis` passing (drives the installed console_script binary, not `python -m`). - [x] **A.3.6** — Syntax-error files are skipped with a stderr log; the run continues (UQ-WP3-02). Proof: `test_extractor.py::test_syntax_error_yields_empty_list_and_logs_to_stderr` — `extract("def :", "broken.py")` returns `[]` and emits "broken.py" in stderr capture. - [x] **A.3.7** — Every UQ-WP3-* marked resolved in [`wp3-python-plugin.md §5`](./wp3-python-plugin.md#5-unresolved-questions). UQ-WP3-10 reads "resolved by ADR-023" (mypy-strict adopted) rather than the original "defer mypy" framing. Proof: doc commit (this one) resolves UQ-WP3-01/02/05/06/07/08/09/11/12. @@ -69,21 +69,21 @@ locked design requires a follow-up ADR and cross-WP impact analysis. ### A.4 End-to-end walking skeleton -- [x] **A.4.1** — The [README §3 demo script](./README.md#3-demo-script-sprint-1-close-proof) runs end-to-end on a clean machine and each step produces the documented output. Proof: `tests/e2e/sprint_1_walking_skeleton.sh` runs the README §3 sequence verbatim (cargo build → pip install → clarion install → clarion analyze → sqlite3 verify) and exits 0 with `PASS: walking skeleton persisted python:function:demo.hello|function`. -- [x] **A.4.2** — `sqlite3 .clarion/clarion.db "select id, kind from entities;"` after the demo returns `python:function:demo.hello|function` (per the locked 3-segment L2 format). Proof: walking-skeleton script asserts exact equality with this string; CI `walking-skeleton` job reruns on every PR. -- [x] **A.4.3** — No regression in pre-existing Clarion tests (there are none yet, but this box stays for later sprints' sanity). Proof: Rust workspace nextest green (140+ tests in clarion-core; full-workspace 175+ tests) and Python pytest green (52 tests) at commit `7e7a85b`. +- [x] **A.4.1** — The [README §3 demo script](./README.md#3-demo-script-sprint-1-close-proof) runs end-to-end on a clean machine and each step produces the documented output. Proof: `tests/e2e/sprint_1_walking_skeleton.sh` runs the README §3 sequence verbatim (cargo build → pip install → loomweave install → loomweave analyze → sqlite3 verify) and exits 0 with `PASS: walking skeleton persisted python:function:demo.hello|function`. +- [x] **A.4.2** — `sqlite3 .loomweave/loomweave.db "select id, kind from entities;"` after the demo returns `python:function:demo.hello|function` (per the locked 3-segment L2 format). Proof: walking-skeleton script asserts exact equality with this string; CI `walking-skeleton` job reruns on every PR. +- [x] **A.4.3** — No regression in pre-existing Loomweave tests (there are none yet, but this box stays for later sprints' sanity). Proof: Rust workspace nextest green (140+ tests in loomweave-core; full-workspace 175+ tests) and Python pytest green (52 tests) at commit `7e7a85b`. ### A.5 Cross-product stance - [x] **A.5.1** — Sprint 1 has introduced **no changes** in the Filigree repo. Proof: Filigree is a separate repo; no Filigree commits authored during Sprint 1 (single-author project — none would have been silent). Sprint 1 does not emit findings or observations. - [x] **A.5.2** — Sprint 1 has introduced **no changes** in the Wardline repo — only a pinned dependency on existing names (`wardline.core.registry.REGISTRY`, `wardline.__version__`). Proof: pip-installed Wardline editable from `/home/john/wardline` for A.5.3 verification; both symbols verified at the documented locations (`src/wardline/core/registry.py:55`, `src/wardline/__init__.py:3`); no edits to that tree. - [x] **A.5.3** — L8 version-pin range (`min_version`, `max_version` in `plugin.toml`) is compatible with the current Wardline version at Sprint 1 close. Proof: probe returns `{"status": "enabled", "version": "1.0.0"}` against `pip install -e /home/john/wardline` in the dev venv. Pin updated from the pre-sprint placeholder `0.1.0`/`0.2.0` to `1.0.0`/`2.0.0` to match Wardline's actual current version. -- [x] **A.5.4** — Any drift between Clarion's L7 qualname format and what Wardline's REGISTRY uses is documented (the first pass may uncover divergence). Proof: divergence found and documented in `wp3-python-plugin.md §L7` (Wardline stores `(module, qualified_name)` as separate fields; Clarion's L7 emits a combined dotted string). Tracked for ADR-018 amendment in **`clarion-889200006a`** (P3, sprint:2 / wp:9 labels). The join is not exercised in Sprint 1 — the divergence is pre-emptive, not a current regression. +- [x] **A.5.4** — Any drift between Loomweave's L7 qualname format and what Wardline's REGISTRY uses is documented (the first pass may uncover divergence). Proof: divergence found and documented in `wp3-python-plugin.md §L7` (Wardline stores `(module, qualified_name)` as separate fields; Loomweave's L7 emits a combined dotted string). Tracked for ADR-018 amendment in **`clarion-889200006a`** (P3, sprint:2 / wp:9 labels). The join is not exercised in Sprint 1 — the divergence is pre-emptive, not a current regression. ### A.6 Documentation hygiene - [x] **A.6.1** — [`../v0.1-plan.md`](../v0.1-plan.md) WP1/WP2/WP3 sections updated to reflect actual Sprint 1 narrower scope (Sprint 2+ scope clearly deferred). Proof: each of the three WP sections now opens with a "Sprint 1 delivery (narrow walking-skeleton scope)" callout listing the L-rows shipped and pointing at the per-WP signoff section + sprint-1 plan; the rest of each WP section continues to describe the v0.1 completion target. -- [x] **A.6.2** — [`../../clarion/adr/README.md`](../../clarion/adr/README.md) shows ADR-005 and ADR-023 both as Accepted. Proof: ADR index rows at lines 13 (ADR-005) and 26 (ADR-023) both show `Accepted`. +- [x] **A.6.2** — [`../../loomweave/adr/README.md`](../../loomweave/adr/README.md) shows ADR-005 and ADR-023 both as Accepted. Proof: ADR index rows at lines 13 (ADR-005) and 26 (ADR-023) both show `Accepted`. - [x] **A.6.3** — [`README.md`](./README.md) §4 "Lock-in summary" table has every L-row marked with the `locked on ` stamp. Proof: L1/L2/L3 stamped 2026-04-18 (WP1 close); L4/L5/L6/L9 stamped 2026-04-24 (A.2 signoffs, commit `1b127df`); L7/L8 stamped 2026-04-24 (A.3 signoffs, this commit). --- @@ -94,24 +94,24 @@ Tier B is the next natural milestone after the walking skeleton. These checkboxe are **not** required to close Sprint 1 — they live here so the path forward is visible. Sprint 2 may split B across multiple sprints. -- [ ] **B.1** — Phase 0 (discovery): `clarion analyze` walks a directory tree and dispatches one `analyze_file` call per matching file per plugin. Proof: integration test with a multi-file fixture. +- [ ] **B.1** — Phase 0 (discovery): `loomweave analyze` walks a directory tree and dispatches one `analyze_file` call per matching file per plugin. Proof: integration test with a multi-file fixture. - [ ] **B.2** — Python plugin emits **classes** and **module** entities in addition to functions. Ontology manifest updated accordingly. - [ ] **B.3** — Python plugin emits **contains** edges (module → function, class → method). - [ ] **B.4** — `catalog.json` is rendered after an analyze run, listing entities and edges. Proof: file produced, schema matches detailed-design §3. - [ ] **B.5** — Per-subsystem markdown files rendered. (Subsystem detection may be naive — single flat subsystem for Tier B; WP4 clustering fills this in.) - [ ] **B.6** — Demo extended: against the elspeth-slice fixture, `catalog.json` lists ≥95% of the Python classes and functions visible in the source (manually verified). -- [ ] **B.7** — No change in the Filigree or Wardline repos — Tier B is still Clarion-only work. +- [ ] **B.7** — No change in the Filigree or Wardline repos — Tier B is still Loomweave-only work. ## Tier C — WP3 feature-complete Tier C reaches WP3's full scope from `../v0.1-plan.md`. Checkboxes included for ladder completeness; not required to close Sprint 1 or Sprint 2. -- [ ] **C.1** — Every Python entity kind from [detailed-design.md §1](../../clarion/v0.1/detailed-design.md#1-plugin-implementation-detail) emitted (functions, classes, methods, module-level variables, decorators, modules). +- [ ] **C.1** — Every Python entity kind from [detailed-design.md §1](../../loomweave/v0.1/detailed-design.md#1-plugin-implementation-detail) emitted (functions, classes, methods, module-level variables, decorators, modules). - [ ] **C.2** — Every Python edge kind emitted (`imports`, `calls`, `decorates`, `contains`, `inherits`). - [ ] **C.3** — Import resolution: relative and absolute Python imports resolved to canonical entity IDs. Dynamic imports out of scope per NG-05-adjacent rule. - [ ] **C.4** — Call-graph precision: intra-module calls resolved; inter-module calls resolved when import resolution succeeded; unresolved calls emitted as best-effort with a confidence marker. -- [ ] **C.5** — Every `CLA-PY-*` rule in [detailed-design.md §5](../../clarion/v0.1/detailed-design.md#5-pipeline--rule-catalogue-and-example-run) has positive and negative fixture coverage. +- [ ] **C.5** — Every `LMWV-PY-*` rule in [detailed-design.md §5](../../loomweave/v0.1/detailed-design.md#5-pipeline--rule-catalogue-and-example-run) has positive and negative fixture coverage. - [ ] **C.6** — Round-trip test against the full elspeth-slice passes and meets the plugin manifest's declared `max_rss_mb`. - [ ] **C.7** — Identity reconciliation with Wardline exercised end-to-end on a real fixture (this is where L7/L8 divergence gets caught in practice, if any). @@ -121,7 +121,7 @@ ladder completeness; not required to close Sprint 1 or Sprint 2. - **Sprint 1 close requires**: every Tier A box ticked; for each L-row ticked, a `locked on ` stamp present in this doc AND in [`README.md`](./README.md) §4. -- **Who signs**: the sprint owner (John Morrissey) — same author for all three Loom +- **Who signs**: the sprint owner (John Morrissey) — same author for all three Weft products, so no cross-team coordination needed. Sign-off proof is a single commit that updates this doc with all boxes ticked and the `locked on` stamps filled in. - **Post-close**: open Sprint 2 as a new subfolder `docs/implementation/sprint-2/` diff --git a/docs/implementation/sprint-1/wp1-scaffold.md b/docs/implementation/sprint-1/wp1-scaffold.md index 1fb784da..ef841e39 100644 --- a/docs/implementation/sprint-1/wp1-scaffold.md +++ b/docs/implementation/sprint-1/wp1-scaffold.md @@ -1,9 +1,9 @@ # WP1 — Scaffold + Storage (Sprint 1) **Status**: DRAFT — pending sprint kickoff -**Anchoring design**: [system-design.md §4 (Storage)](../../clarion/v0.1/system-design.md#4-storage), [detailed-design.md §3 (Storage impl)](../../clarion/v0.1/detailed-design.md#3-storage-implementation) -**Accepted ADRs**: [ADR-001](../../clarion/adr/ADR-001-rust-for-core.md), [ADR-003](../../clarion/adr/ADR-003-entity-id-scheme.md), [ADR-011](../../clarion/adr/ADR-011-writer-actor-concurrency.md), [ADR-023](../../clarion/adr/ADR-023-tooling-baseline.md) -**Backlog ADR that may surface**: ADR-005 (`.clarion/` git-committable subpaths) +**Anchoring design**: [system-design.md §4 (Storage)](../../loomweave/v0.1/system-design.md#4-storage), [detailed-design.md §3 (Storage impl)](../../loomweave/v0.1/detailed-design.md#3-storage-implementation) +**Accepted ADRs**: [ADR-001](../../loomweave/adr/ADR-001-rust-for-core.md), [ADR-003](../../loomweave/adr/ADR-003-entity-id-scheme.md), [ADR-011](../../loomweave/adr/ADR-011-writer-actor-concurrency.md), [ADR-023](../../loomweave/adr/ADR-023-tooling-baseline.md) +**Backlog ADR that may surface**: ADR-005 (`.loomweave/` git-committable subpaths) **Predecessor**: none — WP1 is the foundation of Sprint 1. **Blocks**: WP2, WP3. @@ -20,14 +20,14 @@ to later sprints where those surfaces are first exercised. - Cargo workspace at repo root. - SQLite schema covering the full table set from - [detailed-design.md §3](../../clarion/v0.1/detailed-design.md#3-storage-implementation): + [detailed-design.md §3](../../loomweave/v0.1/detailed-design.md#3-storage-implementation): tables `entities`, `entity_tags`, `edges`, `findings`, `summary_cache`, `runs`, plus the `entity_fts` FTS5 virtual table and its three insert/update/delete triggers, the generated-column `ALTER TABLE` statements plus partial indexes (`ix_entities_priority`, `ix_entities_churn` — note: `ix_entities_priority` was renamed to `ix_entities_scope_rank` and the underlying column was split into - `scope_level` + `scope_rank` by [ADR-024](../../clarion/adr/ADR-024-guidance-schema-vocabulary.md); + `scope_level` + `scope_rank` by [ADR-024](../../loomweave/adr/ADR-024-guidance-schema-vocabulary.md); the L1 lock at sprint close described the pre-rename shape), the `guidance_sheets` view, and a `schema_migrations` meta table. The walking skeleton only writes rows into `entities` and `runs`, but every other table, virtual table, trigger, @@ -46,13 +46,13 @@ to later sprints where those surfaces are first exercised. - Entity-ID 3-segment format (L2) per ADR-003 — `{plugin_id}:{kind}:{canonical_qualified_name}`. WP1 ships the Rust assembler; WP3 ships the Python-side producer of the `canonical_qualified_name` segment. -- `clarion` binary with two subcommands: - - `clarion install` — creates `.clarion/` (DB + `config.json` + `.gitignore` - seeded with the run-log exclusion) and writes a stub `clarion.yaml` at the +- `loomweave` binary with two subcommands: + - `loomweave install` — creates `.loomweave/` (DB + `config.json` + `.gitignore` + seeded with the run-log exclusion) and writes a stub `loomweave.yaml` at the project root per - [detailed-design.md §File layout](../../clarion/v0.1/detailed-design.md#file-layout) + [detailed-design.md §File layout](../../loomweave/v0.1/detailed-design.md#file-layout) (see L1 note below and ADR-005 trigger in §7). - - `clarion analyze ` — CLI wiring only. Accepts the path, opens the DB, + - `loomweave analyze ` — CLI wiring only. Accepts the path, opens the DB, begins a run, but does **not** yet spawn a plugin (that's WP2's wiring). Exits cleanly with run status = `skipped_no_plugins`. - RecordingProvider trait stub in core (no implementation — the file exists so @@ -61,11 +61,11 @@ to later sprints where those surfaces are first exercised. **Explicitly out of scope for Sprint 1:** - Plugin spawning, JSON-RPC, anything subprocess — WP2. -- `clarion serve`, MCP, HTTP — WP8. +- `loomweave serve`, MCP, HTTP — WP8. - `--shadow-db` flag — deferred; only the default in-place write mode is shipped. - Checkpoint/resume mid-run — deferred; runs are either complete or failed, no mid-run restart. -- `clarion analyze --no-llm`, `--dry-run` — deferred. +- `loomweave analyze --no-llm`, `--dry-run` — deferred. - Findings, clustering, briefings — WP4/WP6/WP7. - Any multi-platform support — Linux only; macOS and Windows are future work. @@ -75,7 +75,7 @@ Sprint 1 decides these. Sprint 2+ reads and writes against them. ### L1 — SQLite schema shape -**What locks**: the full table set from [detailed-design.md §3](../../clarion/v0.1/detailed-design.md#3-storage-implementation), +**What locks**: the full table set from [detailed-design.md §3](../../loomweave/v0.1/detailed-design.md#3-storage-implementation), not just the tables the walking skeleton uses. All tables, columns, types, primary keys, foreign keys, indexes, and `PRAGMA` settings are written into migration `0001_initial_schema.sql` and committed. The walking skeleton only writes rows into @@ -87,7 +87,7 @@ there is no data; changing later forces migration scripts that each need their o tests. Locking the shape now — using the detailed-design as the authoritative source — pushes all that rework up-front. -**Canonical source**: [detailed-design.md §3](../../clarion/v0.1/detailed-design.md#3-storage-implementation). +**Canonical source**: [detailed-design.md §3](../../loomweave/v0.1/detailed-design.md#3-storage-implementation). If the detailed-design and the migration file disagree, the migration file wins from Sprint 1 onward; the detailed-design must be updated to match. @@ -95,22 +95,22 @@ Sprint 1 onward; the detailed-design must be updated to match. - WP4 (clustering, findings) reads from `entities`/`edges` and writes to `findings`/`subsystems` — locked shape means WP4 can begin without touching WP1. - WP6 writes to `summary_cache`; its composite primary key (`entity_id, content_hash, prompt_template_id, model_tier, guidance_fingerprint`) is locked here. `entity_id` is the L2 3-segment string. -- `↗` WP10 — Filigree's `registry_backend: clarion` reads `entities` via the +- `↗` WP10 — Filigree's `registry_backend: loomweave` reads `entities` via the documented schema and joins on the 3-segment `EntityId` (L2). Changing column names or the ID shape after Sprint 1 breaks Filigree's `RegistryProtocol` impl. **Note on `.gitignore` selectivity**: the migration file + the DB itself are git-tracked per ADR-005 (backlog). Run logs, shadow-DB artefacts, and any -`.clarion/tmp/` subpath are not. `clarion install` writes the `.gitignore` rules; +`.loomweave/tmp/` subpath are not. `loomweave install` writes the `.gitignore` rules; this is the point at which ADR-005 stops being backlog and gets authored (see §7 trigger). ### L2 — Entity-ID canonical-name format **What locks**: the function that assembles an `EntityId` string per -[ADR-003](../../clarion/adr/ADR-003-entity-id-scheme.md) and -[ADR-022](../../clarion/adr/ADR-022-core-plugin-ontology.md). The Rust +[ADR-003](../../loomweave/adr/ADR-003-entity-id-scheme.md) and +[ADR-022](../../loomweave/adr/ADR-022-core-plugin-ontology.md). The Rust implementation ships in WP1; WP3 ships the Python-side producer of the `canonical_qualified_name` component that matches byte-for-byte. @@ -133,7 +133,7 @@ module-level `def hello()` in `demo.py`. Sprint 1's Rust implementation plus the shared fixture (§Task 5 in WP3) are the executable spec. **Why now**: this is the cross-sibling identity — every table FK, every finding -locator, every Filigree `registry_backend: clarion` join (ADR-014), and every +locator, every Filigree `registry_backend: loomweave` join (ADR-014), and every Wardline qualname reconciliation (ADR-018) keys off this string. Changing it later requires a schema migration *and* coordinated Filigree/Wardline changes. @@ -141,17 +141,17 @@ later requires a schema migration *and* coordinated Filigree/Wardline changes. - L7 (Python qualname production) produces the `canonical_qualified_name` segment; WP1's Rust assembler concatenates `plugin_id`, `kind`, and that segment. Shared fixture (WP3 §Task 5) is the byte-for-byte parity proof. -- `↗` WP10 — Filigree's `registry_backend: clarion` joins on this 3-segment +- `↗` WP10 — Filigree's `registry_backend: loomweave` joins on this 3-segment ID; `{kind}` is load-bearing because ADR-014's protocol uses the core-owned `file` kind as the join surface. - `↗` ADR-018 — Wardline qualname reconciliation uses the `canonical_qualified_name` segment (not the full ID) as its join key on the - Clarion side. + Loomweave side. ### L3 — Writer-actor command protocol **What locks**: the command message type, the per-N transaction batch -contract, and the ack mechanism per [ADR-011](../../clarion/adr/ADR-011-writer-actor-concurrency.md). +contract, and the ack mechanism per [ADR-011](../../loomweave/adr/ADR-011-writer-actor-concurrency.md). The writer-actor is a `tokio::task` (not an OS thread) per ADR-011's Decision section. @@ -165,7 +165,7 @@ per-N auto-commit — is locked. **Per-N batch default**: `N = 50` per ADR-011 §Per-N-files transactions, with `mpsc` channel bounded at 256 ops (ADR-011's `mpsc::Sender` -capacity). Configurable via `clarion.yaml:storage.tx_batch_size` in later +capacity). Configurable via `loomweave.yaml:storage.tx_batch_size` in later WPs; WP1 ships the ADR-locked default. **Why now**: every persistence call site in every later WP will follow this @@ -182,13 +182,13 @@ shift during WP2 or WP3 if shared types emerge. But a reasonable starting shape: ``` /Cargo.toml # workspace root /crates/ - clarion-core/ # library: domain types, traits, stub LlmProvider + loomweave-core/ # library: domain types, traits, stub LlmProvider src/ lib.rs entity_id.rs # L2 implementation + unit tests entity.rs # entity, edge, kind types llm_provider.rs # trait stub for WP6 - clarion-storage/ # SQLite layer + writer-actor + loomweave-storage/ # SQLite layer + writer-actor src/ lib.rs schema.rs # migration runner, schema-version check @@ -197,11 +197,11 @@ shift during WP2 or WP3 if shared types emerge. But a reasonable starting shape: commands.rs # WriterCmd enum (L3) migrations/ 0001_initial_schema.sql # L1 — full schema, committed now - clarion-cli/ # binary entry point + loomweave-cli/ # binary entry point src/ main.rs - install.rs # `clarion install` - analyze.rs # `clarion analyze` (no-plugin variant) + install.rs # `loomweave install` + analyze.rs # `loomweave analyze` (no-plugin variant) ``` Tests live under each crate's `tests/` directory (integration) or inline `#[cfg(test)]` @@ -250,13 +250,13 @@ if they don't block tasks. Each has a proposed resolution-by trigger. commits)? Per-command is simpler; per-batch is more efficient under high entity volume. **Proposal**: per-command ack in Sprint 1; optimise later if WP3 or WP4 hit throughput issues. **Resolved**: Task 6 — per-command oneshot ack; `Writer.commits_observed` `Arc` counts COMMITs issued. -- **UQ-WP1-04** — **What `.gitignore` rules does `clarion install` seed?** ADR-005 +- **UQ-WP1-04** — **What `.gitignore` rules does `loomweave install` seed?** ADR-005 is backlog; Sprint 1 must decide. **Proposal** (authors ADR-005 as a side effect): - ignore `.clarion/tmp/`, `.clarion/logs/`, `.clarion/*.shadow.db`, `.clarion/*.wal`, - `.clarion/*.shm`; track `.clarion/clarion.db`, `.clarion/config.json`, and the - migration history in the DB itself. (`clarion.yaml` lives at project root and - is outside `.clarion/`; its tracking is governed by the user's existing - repo-root `.gitignore`, not by Clarion.) **Resolved**: Task 5 — ADR-005 authored and Accepted; `.gitignore` contents shipped in `crates/clarion-cli/src/install.rs`. + ignore `.loomweave/tmp/`, `.loomweave/logs/`, `.loomweave/*.shadow.db`, `.loomweave/*.wal`, + `.loomweave/*.shm`; track `.loomweave/loomweave.db`, `.loomweave/config.json`, and the + migration history in the DB itself. (`loomweave.yaml` lives at project root and + is outside `.loomweave/`; its tracking is governed by the user's existing + repo-root `.gitignore`, not by Loomweave.) **Resolved**: Task 5 — ADR-005 authored and Accepted; `.gitignore` contents shipped in `crates/loomweave-cli/src/install.rs`. - **UQ-WP1-05** — **Schema for `runs` table**: does Sprint 1's `runs` row include plugin-invocation metadata (plugin name, manifest version) or only run-level status/timestamps? WP2 will add plugin-invocation metadata; if the schema is @@ -264,10 +264,10 @@ if they don't block tasks. Each has a proposed resolution-by trigger. migration. **Proposal**: lock the full shape from detailed-design §3 now, including plugin-invocation columns; WP1 inserts with NULL, WP2 fills them in. **Resolved**: Task 3 — full `runs` shape shipped in `0001_initial_schema.sql`; plugin-invocation metadata goes into the `config` JSON column, populated by WP2. -- **UQ-WP1-06** — **Error-type boundary**: does `clarion-storage` re-export +- **UQ-WP1-06** — **Error-type boundary**: does `loomweave-storage` re-export `rusqlite::Error`, or wrap it in a crate-local `StorageError` via `thiserror`? Re-export leaks the dependency; wrapping adds boilerplate. **Proposal**: wrap; - the crate boundary is a decoupling point. **Resolved**: Task 3 — `StorageError` wraps `rusqlite::Error` via `thiserror` `#[from]` at the `clarion-storage` crate boundary. + the crate boundary is a decoupling point. **Resolved**: Task 3 — `StorageError` wraps `rusqlite::Error` via `thiserror` `#[from]` at the `loomweave-storage` crate boundary. - **UQ-WP1-07** — **Segment-separator collisions**: ADR-003's 3-segment form uses `:` as the segment separator. A segment (any of `plugin_id`, `kind`, `canonical_qualified_name`) containing a literal `:` would produce an @@ -278,13 +278,13 @@ if they don't block tasks. Each has a proposed resolution-by trigger. documents the grammar contract. If a future non-Python plugin needs `:` in qualified names, introduce escaping via a follow-up ADR amending ADR-003. **Resolved**: Task 2 — `EntityIdError::SegmentContainsColon` surfaces any attempted `:` injection; grammar-restricted segments (`plugin_id`, `kind`) cannot produce it in practice. -- **UQ-WP1-08** — **Does `clarion install` refuse to overwrite an existing - `.clarion/`?** **Proposal**: yes, unless `--force`; `--force` is not implemented - in Sprint 1 but the error message names it for future use. **Resolved**: Task 5 — `clarion install` refuses an existing `.clarion/`; `--force` accepted by clap but errors out (Sprint 1 does not implement overwrite). +- **UQ-WP1-08** — **Does `loomweave install` refuse to overwrite an existing + `.loomweave/`?** **Proposal**: yes, unless `--force`; `--force` is not implemented + in Sprint 1 but the error message names it for future use. **Resolved**: Task 5 — `loomweave install` refuses an existing `.loomweave/`; `--force` accepted by clap but errors out (Sprint 1 does not implement overwrite). - **UQ-WP1-09** — **Rust toolchain + workspace tooling baseline**: ~~"2021 edition, stable channel. MSRV floats with the latest stable at sprint start; fine to document and move on."~~ — **reopened 2026-04-18 - and re-resolved by [ADR-023](../../clarion/adr/ADR-023-tooling-baseline.md)**. + and re-resolved by [ADR-023](../../loomweave/adr/ADR-023-tooling-baseline.md)**. The original "move on" framing was the canonical tell for an unexamined default. ADR-023 adopts **edition 2024**, workspace-level `[lints]` with `clippy::pedantic = "warn"` + `unsafe_code = "forbid"`, @@ -306,21 +306,21 @@ do not parallelise within WP1. Commits are one-per-task unless noted. **Files**: - Create `/Cargo.toml` (workspace root — `[workspace.package]`, `[workspace.dependencies]`, and `[workspace.lints]` per ADR-023) -- Create `/crates/clarion-core/{Cargo.toml,src/lib.rs}` -- Create `/crates/clarion-storage/{Cargo.toml,src/lib.rs}` -- Create `/crates/clarion-cli/{Cargo.toml,src/main.rs}` +- Create `/crates/loomweave-core/{Cargo.toml,src/lib.rs}` +- Create `/crates/loomweave-storage/{Cargo.toml,src/lib.rs}` +- Create `/crates/loomweave-cli/{Cargo.toml,src/main.rs}` - Create `/rust-toolchain.toml` pinning stable with `clippy` + `rustfmt` + `llvm-tools-preview` - Create `/rustfmt.toml` (`edition = "2024"`, `max_width = 100`, Unix newlines) - Create `/clippy.toml` (relaxed pedantic thresholds per ADR-023) - Create `/deny.toml` (cargo-deny v2 schema — advisories, license allowlist, bans, sources) - Create `/.github/workflows/ci.yml` (fmt-check + pedantic clippy + nextest + cargo-doc + cargo-deny) -- Create `/.gitignore` entries for `/target`, `*-wal`, `*-shm` (project-level `.clarion/clarion.db` is tracked per ADR-005 — do **not** blanket-ignore `*.db`) +- Create `/.gitignore` entries for `/target`, `*-wal`, `*-shm` (project-level `.loomweave/loomweave.db` is tracked per ADR-005 — do **not** blanket-ignore `*.db`) Steps: - [ ] Write workspace `Cargo.toml` listing the three members. `[workspace.package]` pins `edition = "2024"` (ADR-023), license, repository, and `rust-version = "1.85"` (or the current stable MSRV at sprint start). Declare `tokio`, `rusqlite` (with `bundled` feature), `deadpool-sqlite`, `thiserror`, `tracing`, `clap`, `anyhow`, `serde` + `serde_json`, plus the `tempfile` and `assert_cmd` dev-deps as workspace dependencies (ADR-011-locked stack; dev-deps centralised). - [ ] Add `[workspace.lints.rust]` with `unsafe_code = "forbid"` and `[workspace.lints.clippy]` with `pedantic = "warn"` plus the pragmatic allows from ADR-023 (`module_name_repetitions`, `must_use_candidate`, `missing_errors_doc`). -- [ ] Write each crate's `Cargo.toml`. Every crate declares `lints.workspace = true` so a later-added crate cannot drift off the ADR-023 floor. `clarion-core` takes `thiserror` + `serde` + `serde_json`. `clarion-storage` takes core + `rusqlite` + `deadpool-sqlite` + `tokio` (features `rt-multi-thread`, `macros`, `sync`). `clarion-cli` takes both + `clap` + `anyhow` + `tracing` + `tracing-subscriber` + `tokio` (same features). +- [ ] Write each crate's `Cargo.toml`. Every crate declares `lints.workspace = true` so a later-added crate cannot drift off the ADR-023 floor. `loomweave-core` takes `thiserror` + `serde` + `serde_json`. `loomweave-storage` takes core + `rusqlite` + `deadpool-sqlite` + `tokio` (features `rt-multi-thread`, `macros`, `sync`). `loomweave-cli` takes both + `clap` + `anyhow` + `tracing` + `tracing-subscriber` + `tokio` (same features). - [ ] Write `rust-toolchain.toml` per ADR-023 (channel `stable`; components `clippy`, `rustfmt`, `llvm-tools-preview`; `profile = "minimal"`). - [ ] Write `rustfmt.toml`, `clippy.toml`, `deny.toml` as specified in ADR-023. - [ ] Write `.github/workflows/ci.yml` with jobs for fmt-check, pedantic clippy, nextest, cargo-doc, cargo-deny. Use `dtolnay/rust-toolchain@stable`, `Swatinem/rust-cache@v2`, and `taiki-e/install-action` for nextest + cargo-deny installs. @@ -337,27 +337,27 @@ Steps: ### Task 2 — Entity-ID assembler (L2) **Files**: -- Create `/crates/clarion-core/src/entity_id.rs` -- Modify `/crates/clarion-core/src/lib.rs` to `pub mod entity_id;` +- Create `/crates/loomweave-core/src/entity_id.rs` +- Modify `/crates/loomweave-core/src/lib.rs` to `pub mod entity_id;` Steps: - [ ] Write failing unit tests in `entity_id.rs` covering ADR-003's 3-segment format. Start with at least five cases, including: module-level function (`python:function:demo.hello`), class method (`python:function:demo.Foo.bar`), nested function (`python:function:demo.outer..inner`), a core-reserved file entity (`core:file:src/demo.py`), and a core-reserved subsystem entity (`core:subsystem:`). -- [ ] Run `cargo test -p clarion-core entity_id`; expect failures referencing the missing `entity_id()` function. +- [ ] Run `cargo test -p loomweave-core entity_id`; expect failures referencing the missing `entity_id()` function. - [ ] Implement `pub fn entity_id(plugin_id: &str, kind: &str, canonical_qualified_name: &str) -> EntityId` (newtype-wrapped `String`) matching ADR-003's three-segment form. Validate each segment against the ADR-022 grammar (`kind` matches `[a-z][a-z0-9_]*`; `plugin_id` matches the same grammar); return a typed error on malformed input. Assert-unreachable on any segment containing a literal `:` (UQ-WP1-07 — segment separator collisions are a bug at the caller). -- [ ] Run `cargo test -p clarion-core entity_id`; expect all pass. +- [ ] Run `cargo test -p loomweave-core entity_id`; expect all pass. - [ ] Commit: `feat(wp1): L2 entity-ID assembler per ADR-003 + ADR-022`. ### Task 3 — Schema migration file (L1) **Files**: -- Create `/crates/clarion-storage/migrations/0001_initial_schema.sql` -- Create `/crates/clarion-storage/src/schema.rs` -- Modify `/crates/clarion-storage/src/lib.rs` to expose the migration runner +- Create `/crates/loomweave-storage/migrations/0001_initial_schema.sql` +- Create `/crates/loomweave-storage/src/schema.rs` +- Modify `/crates/loomweave-storage/src/lib.rs` to expose the migration runner Steps: -- [ ] Transcribe the full schema from [detailed-design.md §3](../../clarion/v0.1/detailed-design.md#3-storage-implementation) into `0001_initial_schema.sql`. Concretely: +- [ ] Transcribe the full schema from [detailed-design.md §3](../../loomweave/v0.1/detailed-design.md#3-storage-implementation) into `0001_initial_schema.sql`. Concretely: - Tables: `entities`, `entity_tags`, `edges`, `findings`, `summary_cache`, `runs`, and `schema_migrations` (meta). Every column, primary key, foreign key, and explicit index as written in §3. - Virtual table: `entity_fts` (FTS5, `tokenize = 'porter unicode61'`). - Triggers: `entities_ai`, `entities_au`, `entities_ad` keeping `entity_fts` synchronised with `entities`. @@ -365,14 +365,14 @@ Steps: - View: `guidance_sheets` over `entities WHERE kind = 'guidance'`. - `PRAGMA foreign_keys = ON` applied at connection open (not in the migration file itself — PRAGMA is connection-scoped and is set in `reader.rs` / `writer.rs` on each open per ADR-011's connection open list). - Do not truncate; every table, trigger, generated column, and view ships in Sprint 1 even though only `entities` and `runs` are written. -- [ ] Write failing integration test at `/crates/clarion-storage/tests/schema_apply.rs` that opens a fresh DB, runs migrations, and asserts: +- [ ] Write failing integration test at `/crates/loomweave-storage/tests/schema_apply.rs` that opens a fresh DB, runs migrations, and asserts: - Every expected table exists via `SELECT name FROM sqlite_master WHERE type='table'`. - The FTS5 virtual table `entity_fts` exists and is queryable (`SELECT * FROM entity_fts LIMIT 0`). - All three FTS triggers exist via `SELECT name FROM sqlite_master WHERE type='trigger'`. - The generated columns round-trip: insert an entity with a `properties` JSON containing `priority`, `SELECT priority FROM entities` returns the extracted value. _Post-Sprint-1 note: per ADR-024 the test now uses `scope_level`/`scope_rank` instead of `priority`._ - The `guidance_sheets` view exists and is queryable. - Idempotency: running migrations twice does not fail. -- [ ] Run `cargo test -p clarion-storage schema_apply`; expect failures referencing missing `apply_migrations()`. +- [ ] Run `cargo test -p loomweave-storage schema_apply`; expect failures referencing missing `apply_migrations()`. - [ ] Implement `schema.rs` with `pub fn apply_migrations(conn: &Connection) -> Result<()>` that reads files from `migrations/` in lexical order, skips those already in `schema_migrations`, and records each successful apply. - [ ] Run the integration test; expect pass. - [ ] Commit: `feat(wp1): L1 SQLite schema migration framework`. @@ -380,45 +380,45 @@ Steps: ### Task 4 — Reader pool **Files**: -- Create `/crates/clarion-storage/src/reader.rs` -- Modify `/crates/clarion-storage/src/lib.rs` to export `ReaderPool` +- Create `/crates/loomweave-storage/src/reader.rs` +- Modify `/crates/loomweave-storage/src/lib.rs` to export `ReaderPool` Steps: - [ ] Write failing `#[tokio::test]` integration test: open a DB, apply migrations, construct `ReaderPool::new(path, max_size=2)`, acquire two readers concurrently via `tokio::join!`, each runs a trivial `SELECT 1` without blocking the other. -- [ ] Run `cargo test -p clarion-storage reader`; expect failure. +- [ ] Run `cargo test -p loomweave-storage reader`; expect failure. - [ ] Implement `ReaderPool` as a thin wrapper around `deadpool-sqlite` (per ADR-011) with `max_size` default 16 (ADR-011 §Reader pool). Readers are opened with `OpenFlags::SQLITE_OPEN_READ_ONLY` and the ADR-011 PRAGMAs applied on each open via the pool's `Manager::post_create` hook. - [ ] Run the test; expect pass. - [ ] Commit: `feat(wp1): reader pool for concurrent read connections`. -### Task 5 — `clarion install` subcommand +### Task 5 — `loomweave install` subcommand **Files**: -- Create `/crates/clarion-cli/src/install.rs` -- Modify `/crates/clarion-cli/src/main.rs` for clap subcommand wiring +- Create `/crates/loomweave-cli/src/install.rs` +- Modify `/crates/loomweave-cli/src/main.rs` for clap subcommand wiring Steps: -- [ ] Write failing integration test at `/crates/clarion-cli/tests/install.rs` using `assert_cmd`: run `clarion install` in a tempdir; assert `.clarion/clarion.db` exists, `.clarion/config.json` exists with `{"schema_version": 1}`, `.clarion/.gitignore` exists with expected rules (UQ-WP1-04 resolution), **`/clarion.yaml`** (project root, not `.clarion/`; per [detailed-design.md §File layout](../../clarion/v0.1/detailed-design.md#file-layout) and [system-design.md §Config resolution](../../clarion/v0.1/system-design.md#config-resolution)) exists with stub content, and `schema_migrations` row count = 1. +- [ ] Write failing integration test at `/crates/loomweave-cli/tests/install.rs` using `assert_cmd`: run `loomweave install` in a tempdir; assert `.loomweave/loomweave.db` exists, `.loomweave/config.json` exists with `{"schema_version": 1}`, `.loomweave/.gitignore` exists with expected rules (UQ-WP1-04 resolution), **`/loomweave.yaml`** (project root, not `.loomweave/`; per [detailed-design.md §File layout](../../loomweave/v0.1/detailed-design.md#file-layout) and [system-design.md §Config resolution](../../loomweave/v0.1/system-design.md#config-resolution)) exists with stub content, and `schema_migrations` row count = 1. - [ ] Second test: running `install` twice in the same dir without `--force` returns a non-zero exit and a clear error message referencing `--force`. - [ ] Run tests; expect failure. - [ ] Implement `install.rs`: - - Refuse if `.clarion/` already exists (UQ-WP1-08). - - Create `.clarion/` directory. + - Refuse if `.loomweave/` already exists (UQ-WP1-08). + - Create `.loomweave/` directory. - Open fresh DB and apply migrations. - - Write `.clarion/config.json` with the internal-state stub (`{"schema_version": 1, "last_run_id": null}`). - - Write **`clarion.yaml` at the project root** (not inside `.clarion/`) — a comment-only stub noting "config TBD, see v0.1 design". This matches detailed-design §File layout: `.clarion/` holds internal state; `clarion.yaml` is a user-edited config that lives beside the user's source. - - Write `.clarion/.gitignore` with the UQ-WP1-04 rules. + - Write `.loomweave/config.json` with the internal-state stub (`{"schema_version": 1, "last_run_id": null}`). + - Write **`loomweave.yaml` at the project root** (not inside `.loomweave/`) — a comment-only stub noting "config TBD, see v0.1 design". This matches detailed-design §File layout: `.loomweave/` holds internal state; `loomweave.yaml` is a user-edited config that lives beside the user's source. + - Write `.loomweave/.gitignore` with the UQ-WP1-04 rules. - [ ] Author ADR-005 as a side effect of this task — short doc recording the `.gitignore` decision. Commit ADR-005 alongside this task. - [ ] Run tests; expect pass. -- [ ] Commit: `feat(wp1): clarion install subcommand; author ADR-005`. +- [ ] Commit: `feat(wp1): loomweave install subcommand; author ADR-005`. ### Task 6 — Writer-actor (L3) **Files**: -- Create `/crates/clarion-storage/src/commands.rs` -- Create `/crates/clarion-storage/src/writer.rs` -- Modify `/crates/clarion-storage/src/lib.rs` to export `Writer` and `WriterCmd` +- Create `/crates/loomweave-storage/src/commands.rs` +- Create `/crates/loomweave-storage/src/writer.rs` +- Modify `/crates/loomweave-storage/src/lib.rs` to export `Writer` and `WriterCmd` Steps: @@ -435,53 +435,53 @@ Steps: - [ ] Run tests; expect pass. - [ ] Commit: `feat(wp1): L3 writer-actor (tokio::task) with per-N transaction batch`. -### Task 7 — `clarion analyze` skeleton (no plugin) +### Task 7 — `loomweave analyze` skeleton (no plugin) **Files**: -- Create `/crates/clarion-cli/src/analyze.rs` -- Modify `/crates/clarion-cli/src/main.rs` for subcommand wiring +- Create `/crates/loomweave-cli/src/analyze.rs` +- Modify `/crates/loomweave-cli/src/main.rs` for subcommand wiring Steps: -- [ ] Write failing integration test: `clarion install` in tempdir, `clarion analyze .`, assert exit code 0, assert a row exists in `runs` with status `skipped_no_plugins`, assert no entities persisted. +- [ ] Write failing integration test: `loomweave install` in tempdir, `loomweave analyze .`, assert exit code 0, assert a row exists in `runs` with status `skipped_no_plugins`, assert no entities persisted. - [ ] Run test; expect failure. - [ ] Implement `analyze.rs`: - - Resolve `.clarion/` (error if missing, naming `clarion install`). + - Resolve `.loomweave/` (error if missing, naming `loomweave install`). - Open DB, reader pool, writer. - `BeginRun` → `CommitRun` immediately with status `skipped_no_plugins`. Log a `tracing::info!` message "no plugins registered (WP2 will wire this)". - [ ] Run test; expect pass. -- [ ] Commit: `feat(wp1): clarion analyze skeleton (plugin wiring deferred to WP2)`. +- [ ] Commit: `feat(wp1): loomweave analyze skeleton (plugin wiring deferred to WP2)`. ### Task 8 — LlmProvider trait stub **Files**: -- Create `/crates/clarion-core/src/llm_provider.rs` -- Modify `/crates/clarion-core/src/lib.rs` to re-export +- Create `/crates/loomweave-core/src/llm_provider.rs` +- Modify `/crates/loomweave-core/src/lib.rs` to re-export Steps: - [ ] Define `pub trait LlmProvider { fn name(&self) -> &str; }` — intentionally trivial; WP6 fills it out. Provide a `NoopProvider` unit struct that panics if `name()` is called (guard: nothing in Sprint 1 should call it). - [ ] Add a unit test that asserts the trait compiles and `NoopProvider` implements it. -- [ ] `cargo test -p clarion-core`. +- [ ] `cargo test -p loomweave-core`. - [ ] Commit: `feat(wp1): LlmProvider trait stub for WP6`. ### Task 9 — End-to-end WP1 smoke test **Files**: -- Create `/crates/clarion-cli/tests/wp1_e2e.rs` +- Create `/crates/loomweave-cli/tests/wp1_e2e.rs` Steps: -- [ ] Integration test that runs the full Sprint 1 WP1 slice: `clarion install` in tempdir, `clarion analyze .`, then open the DB with `rusqlite` directly and assert schema version, run count, and entity count (0 entities, 1 run with `skipped_no_plugins` status). +- [ ] Integration test that runs the full Sprint 1 WP1 slice: `loomweave install` in tempdir, `loomweave analyze .`, then open the DB with `rusqlite` directly and assert schema version, run count, and entity count (0 entities, 1 run with `skipped_no_plugins` status). - [ ] Run; expect pass. - [ ] Commit: `test(wp1): end-to-end smoke test`. ## 7. ADR triggers -- **ADR-005** (`.clarion/` git-committable subpaths) — **authored during Task 5**. +- **ADR-005** (`.loomweave/` git-committable subpaths) — **authored during Task 5**. Sprint 1 must decide which subpaths land in `.gitignore` (see UQ-WP1-04); writing the decision down is the act of authoring ADR-005. The ADR file lives at - [`../../clarion/adr/ADR-005-clarion-dir-tracking.md`](../../clarion/adr/) and is + [`../../loomweave/adr/ADR-005-loomweave-dir-tracking.md`](../../loomweave/adr/) and is moved from backlog to Accepted in the ADR index. ## 8. Exit criteria @@ -495,7 +495,7 @@ WP1 is done for Sprint 1 when **all** of the following hold: - `cargo deny check` passes (ADR-023 gate; license allowlist + advisories + bans + sources all green). - `cargo doc --no-deps --all-features` builds without warnings (ADR-023 gate). - **GitHub Actions CI green** on the WP1 PR across all five jobs (fmt, clippy, nextest, doc, deny) (ADR-023 gate). -- `clarion install && clarion analyze .` in a fresh tempdir produces the expected +- `loomweave install && loomweave analyze .` in a fresh tempdir produces the expected `skipped_no_plugins` run row with zero entities. - L1 (full schema migration `0001`), L2 (`entity_id()` implementation), L3 (WriterCmd + per-N-batch writer-actor) are each covered by at least one test. diff --git a/docs/implementation/sprint-1/wp2-plugin-host.md b/docs/implementation/sprint-1/wp2-plugin-host.md index adbb7230..94c09965 100644 --- a/docs/implementation/sprint-1/wp2-plugin-host.md +++ b/docs/implementation/sprint-1/wp2-plugin-host.md @@ -1,8 +1,8 @@ # WP2 — Plugin Host and Hybrid Authority (Sprint 1) **Status**: DRAFT — blocked-by WP1 -**Anchoring design**: [system-design.md §2 (Core/Plugin Architecture)](../../clarion/v0.1/system-design.md#2-core--plugin-architecture), [detailed-design.md §1 (Plugin implementation detail)](../../clarion/v0.1/detailed-design.md#1-plugin-implementation-detail) -**Accepted ADRs**: [ADR-002](../../clarion/adr/ADR-002-plugin-transport-json-rpc.md), [ADR-021](../../clarion/adr/ADR-021-plugin-authority-hybrid.md), [ADR-022](../../clarion/adr/ADR-022-core-plugin-ontology.md) +**Anchoring design**: [system-design.md §2 (Core/Plugin Architecture)](../../loomweave/v0.1/system-design.md#2-core--plugin-architecture), [detailed-design.md §1 (Plugin implementation detail)](../../loomweave/v0.1/detailed-design.md#1-plugin-implementation-detail) +**Accepted ADRs**: [ADR-002](../../loomweave/adr/ADR-002-plugin-transport-json-rpc.md), [ADR-021](../../loomweave/adr/ADR-021-plugin-authority-hybrid.md), [ADR-022](../../loomweave/adr/ADR-022-core-plugin-ontology.md) **Predecessor**: [WP1](./wp1-scaffold.md). **Blocks**: WP3. @@ -39,7 +39,7 @@ enforcement points are what lock L6 and determine the plugin API contract. proves the breaker trips. Respawn logic is implemented but the walking-skeleton demo does not exercise it (one analyze run = one spawn). - In-process mock plugin used by tests. -- Wiring WP1's `clarion analyze` to discover and spawn plugins. +- Wiring WP1's `loomweave analyze` to discover and spawn plugins. **Explicitly out of scope for Sprint 1:** @@ -54,8 +54,8 @@ enforcement points are what lock L6 and determine the plugin API contract. ### L4 — JSON-RPC method set + Content-Length framing **What locks**: the over-the-wire protocol between core and any plugin, per -[ADR-002](../../clarion/adr/ADR-002-plugin-transport-json-rpc.md) and -[detailed-design.md §1](../../clarion/v0.1/detailed-design.md#1-plugin-implementation-detail). +[ADR-002](../../loomweave/adr/ADR-002-plugin-transport-json-rpc.md) and +[detailed-design.md §1](../../loomweave/v0.1/detailed-design.md#1-plugin-implementation-detail). **Sprint 1 method set** (minimum viable for walking skeleton): @@ -83,23 +83,23 @@ Changing framing later = breaking every plugin implementation. **Downstream impact**: - WP3 implements the plugin side against this spec. - `↗` No direct cross-product touch in Sprint 1, but if Wardline ever becomes a - Clarion plugin (not planned for v0.1), it inherits this protocol. + Loomweave plugin (not planned for v0.1), it inherits this protocol. ### L5 — `plugin.toml` manifest schema **What locks**: the schema of the manifest file every plugin must provide, per -[ADR-022](../../clarion/adr/ADR-022-core-plugin-ontology.md). WP2 ships the Rust +[ADR-022](../../loomweave/adr/ADR-022-core-plugin-ontology.md). WP2 ships the Rust parser + validator; WP3 ships the first real manifest. **Schema (Sprint 1)**: ```toml [plugin] -name = "clarion-plugin-python" # package name; informational (hyphens OK, human-readable) +name = "loomweave-plugin-python" # package name; informational (hyphens OK, human-readable) plugin_id = "python" # identifier fed to entity_id(); must match [a-z][a-z0-9_]* (ADR-022) version = "0.1.0" # semver protocol_version = "1.0" # matches ADR-002 version -executable = "clarion-plugin-python" # command on PATH (see L9) +executable = "loomweave-plugin-python" # command on PATH (see L9) language = "python" # informational tag extensions = ["py"] # file extensions this plugin claims @@ -110,14 +110,14 @@ extensions = ["py"] # file extensions this plugin claims # minimums (Content-Length ceiling, entity-count cap, RSS limit, path jail) # are core-enforced with fixed defaults a plugin cannot raise — see §L6. expected_max_rss_mb = 512 # plugin's own RSS estimate; effective prlimit = min(this, core default 2 GiB) -expected_entities_per_file = 5000 # triggers CLA-INFRA-PLUGIN-ENTITY-OVERRUN-WARNING well before the 500k hard cap (warning impl deferred — see Task 4) +expected_entities_per_file = 5000 # triggers LMWV-INFRA-PLUGIN-ENTITY-OVERRUN-WARNING well before the 500k hard cap (warning impl deferred — see Task 4) wardline_aware = true # plugin reads wardline.core.registry.REGISTRY (WP3 L8) reads_outside_project_root = false # opt-out declaration; v0.1 refuses `true` at initialize (see Task 1 + Task 6) [ontology] entity_kinds = ["function", "class", "module", "decorator"] edge_kinds = ["imports", "calls", "decorates", "contains"] -rule_id_prefix = "CLA-PY-" # every emitted rule-ID must start with this; must match ADR-022 grammar (see Task 1) +rule_id_prefix = "LMWV-PY-" # every emitted rule-ID must start with this; must match ADR-022 grammar (see Task 1) ontology_version = "0.1.0" # bump when entity/edge/rule set changes ``` @@ -130,31 +130,31 @@ per-edge-kind semantic annotations, so this compliance is automatic — a manifest listing `contains` in `edge_kinds` parses successfully (Task 1 test). -**Rule-ID namespace**. Per ADR-022, `CLA-INFRA-*` is core-only (pipeline -findings), `CLA-FACT-*` is shared (any plugin or the core), and -`CLA-{PLUGIN_ID_UPPERCASE}-*` is reserved to that plugin. A manifest -declaring `rule_id_prefix = "CLA-INFRA-"` or `"CLA-FACT-"` is rejected at +**Rule-ID namespace**. Per ADR-022, `LMWV-INFRA-*` is core-only (pipeline +findings), `LMWV-FACT-*` is shared (any plugin or the core), and +`LMWV-{PLUGIN_ID_UPPERCASE}-*` is reserved to that plugin. A manifest +declaring `rule_id_prefix = "LMWV-INFRA-"` or `"LMWV-FACT-"` is rejected at parse (Task 1); emission-time enforcement of off-namespace rule IDs -(`CLA-INFRA-RULE-ID-NAMESPACE`) is deferred to the findings-emitting Tier B +(`LMWV-INFRA-RULE-ID-NAMESPACE`) is deferred to the findings-emitting Tier B sprint — Sprint 1's walking skeleton emits no findings, so the RPC-time check has nothing to fire against. **Manifest-validation finding codes surfaced by this WP**: -- `CLA-INFRA-MANIFEST-MALFORMED` — kind strings or `rule_id_prefix` fail +- `LMWV-INFRA-MANIFEST-MALFORMED` — kind strings or `rule_id_prefix` fail ADR-022's identifier grammar (`[a-z][a-z0-9_]*` for kinds, - `CLA-[A-Z]+(-[A-Z0-9]+)+` for rule-ID prefixes). Rejected at `initialize`; + `LMWV-[A-Z]+(-[A-Z0-9]+)+` for rule-ID prefixes). Rejected at `initialize`; plugin fails to start. -- `CLA-INFRA-MANIFEST-RESERVED-KIND` — manifest declares `file`, `subsystem`, +- `LMWV-INFRA-MANIFEST-RESERVED-KIND` — manifest declares `file`, `subsystem`, or `guidance` in `entity_kinds`. Rejected at `initialize`. -- `CLA-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY` — manifest declares +- `LMWV-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY` — manifest declares `reads_outside_project_root = true`; v0.1 has no mechanism to allow it. Rejected at `initialize`. -- `CLA-INFRA-RULE-ID-NAMESPACE` — manifest declares a reserved - `rule_id_prefix` (`CLA-INFRA-` or `CLA-FACT-`). Rejected at parse. +- `LMWV-INFRA-RULE-ID-NAMESPACE` — manifest declares a reserved + `rule_id_prefix` (`LMWV-INFRA-` or `LMWV-FACT-`). Rejected at parse. *Emission-time* enforcement (plugin emits a rule ID outside its namespace) is deferred to the findings-emitting Tier B sprint. -- `CLA-INFRA-PLUGIN-ENTITY-OVERRUN-WARNING` — per-file entity count exceeds +- `LMWV-INFRA-PLUGIN-ENTITY-OVERRUN-WARNING` — per-file entity count exceeds `expected_entities_per_file`. Implementation deferred to the catalog-emitting Tier B sprint (Sprint 1 is one file per invocation; the warning has no useful surface area yet). @@ -181,14 +181,14 @@ manifests against it, schema changes become breaking. Plugins cannot opt out; plugin authors rely on these running for every run. **Enforcement points** (defaults and subcodes per -[ADR-021](../../clarion/adr/ADR-021-plugin-authority-hybrid.md) §2): +[ADR-021](../../loomweave/adr/ADR-021-plugin-authority-hybrid.md) §2): | Minimum | Where enforced | Default | Behaviour on violation | |---|---|---|---| -| Path jail (ADR-021 §2a) | Every path the plugin returns on `analyze_file` responses (file_path in source range, evidence anchors) | canonicalise via `std::fs::canonicalize`, reject if outside `project_root` | **Drop** the offending entity/edge/finding on **first offense** (do NOT kill plugin — path escape is more often a correctness bug than a live attack). Emit `CLA-INFRA-PLUGIN-PATH-ESCAPE` with `metadata.clarion.offending_path`. A dedicated sub-breaker counts repeats: **>10 path-escapes in 60s → plugin killed**, `CLA-INFRA-PLUGIN-DISABLED-PATH-ESCAPE`. | -| Content-Length ceiling (ADR-021 §2b) | Every inbound JSON-RPC frame from the plugin | **8 MiB** per frame (floor 1 MiB) | Framing parser refuses the frame before deserialising; plugin killed (SIGTERM → SIGKILL if non-responsive); emit `CLA-INFRA-PLUGIN-FRAME-OVERSIZE` with observed vs ceiling bytes. Crash-loop counter increments. | -| Entity-count cap (ADR-021 §2c) | Cumulative across all plugin-emitted `entity + edge + finding` records within one run | **500,000** combined records (floor 10,000) | In-flight batch flushed; plugin killed; run enters partial-results state; emit `CLA-INFRA-PLUGIN-ENTITY-CAP`. | -| Per-plugin RSS limit (ADR-021 §2d) | On spawn | **2 GiB** virtual-memory cap (`RLIMIT_AS`, floor 512 MiB) | `prlimit(RLIMIT_AS)` on Linux / `setrlimit` on macOS applied via `pre_exec`. Process killed by OS on exceed; core detects `WIFSIGNALED && WTERMSIG == 9` and emits `CLA-INFRA-PLUGIN-OOM-KILLED`. | +| Path jail (ADR-021 §2a) | Every path the plugin returns on `analyze_file` responses (file_path in source range, evidence anchors) | canonicalise via `std::fs::canonicalize`, reject if outside `project_root` | **Drop** the offending entity/edge/finding on **first offense** (do NOT kill plugin — path escape is more often a correctness bug than a live attack). Emit `LMWV-INFRA-PLUGIN-PATH-ESCAPE` with `metadata.loomweave.offending_path`. A dedicated sub-breaker counts repeats: **>10 path-escapes in 60s → plugin killed**, `LMWV-INFRA-PLUGIN-DISABLED-PATH-ESCAPE`. | +| Content-Length ceiling (ADR-021 §2b) | Every inbound JSON-RPC frame from the plugin | **8 MiB** per frame (floor 1 MiB) | Framing parser refuses the frame before deserialising; plugin killed (SIGTERM → SIGKILL if non-responsive); emit `LMWV-INFRA-PLUGIN-FRAME-OVERSIZE` with observed vs ceiling bytes. Crash-loop counter increments. | +| Entity-count cap (ADR-021 §2c) | Cumulative across all plugin-emitted `entity + edge + finding` records within one run | **500,000** combined records (floor 10,000) | In-flight batch flushed; plugin killed; run enters partial-results state; emit `LMWV-INFRA-PLUGIN-ENTITY-CAP`. | +| Per-plugin RSS limit (ADR-021 §2d) | On spawn | **2 GiB** virtual-memory cap (`RLIMIT_AS`, floor 512 MiB) | `prlimit(RLIMIT_AS)` on Linux / `setrlimit` on macOS applied via `pre_exec`. Process killed by OS on exceed; core detects `WIFSIGNALED && WTERMSIG == 9` and emits `LMWV-INFRA-PLUGIN-OOM-KILLED`. | **Sprint 1 scope note — `file_list` deferred**. ADR-021 §2a also specifies path-jail enforcement on `file_list` RPC returns. Sprint 1's walking skeleton @@ -202,7 +202,7 @@ Task 4 is reused; no re-design. **Ceilings hierarchy**: the manifest's `capabilities` values are upper bounds *the plugin declares for itself*. The core applies ADR-021's absolute ceilings independently; the effective ceiling is `min(manifest, core)`. -Core ceiling config keys live under `clarion.yaml:plugin_limits.*` but +Core ceiling config keys live under `loomweave.yaml:plugin_limits.*` but Sprint 1 hard-codes the ADR-021 defaults above (config surface deferred to WP6). @@ -218,21 +218,21 @@ against the new behaviour. ### L9 — Plugin discovery convention -**What locks**: how the core finds plugin binaries at `clarion analyze` time. +**What locks**: how the core finds plugin binaries at `loomweave analyze` time. **Three candidate conventions** (UQ-WP2-01 resolves this): - **A. PATH-based**: look up `executable` from manifest on `$PATH` (like `git` finds `git-foo` subcommands). Pro: zero configuration, distro-native. Con: installation is user-dependent. -- **B. Explicit plugin dir**: a `~/.config/clarion/plugins//plugin.toml` +- **B. Explicit plugin dir**: a `~/.config/loomweave/plugins//plugin.toml` layout. Pro: explicit, discoverable. Con: bespoke install step. -- **C. Config-listed paths**: `clarion.yaml` has `[[plugins]] manifest = "path"`. +- **C. Config-listed paths**: `loomweave.yaml` has `[[plugins]] manifest = "path"`. Pro: project-local plugin overrides. Con: requires config before `analyze`. -**Proposal**: **A with a fallback to B**. `clarion analyze` discovers plugins by -scanning `$PATH` for executables matching `clarion-plugin-*`, then loading each -one's `plugin.toml` from `/share/clarion/plugins//plugin.toml` +**Proposal**: **A with a fallback to B**. `loomweave analyze` discovers plugins by +scanning `$PATH` for executables matching `loomweave-plugin-*`, then loading each +one's `plugin.toml` from `/share/loomweave/plugins//plugin.toml` (or next to the binary, whichever is found first). This matches the `git` subcommand idiom and is the lowest-friction path for the WP3 Python plugin which is pip-installable. @@ -247,10 +247,10 @@ the convention later breaks installation docs and packaging. ## 3. File decomposition -Within `clarion-core` (new modules): +Within `loomweave-core` (new modules): ``` -/crates/clarion-core/src/ +/crates/loomweave-core/src/ plugin/ mod.rs # re-exports; the plugin-host facade transport.rs # Content-Length framing; JSON-RPC frame encode/decode @@ -264,14 +264,14 @@ Within `clarion-core` (new modules): mock.rs # in-process mock plugin (test-only; `#[cfg(test)]`) ``` -The decision to put plugin support in `clarion-core` (rather than a new crate) keeps +The decision to put plugin support in `loomweave-core` (rather than a new crate) keeps the plugin types close to the domain types they produce. If that becomes unwieldy -later, splitting to `clarion-plugin-host` is a mechanical refactor. +later, splitting to `loomweave-plugin-host` is a mechanical refactor. -`clarion-cli` gets a small update: +`loomweave-cli` gets a small update: ``` -/crates/clarion-cli/src/ +/crates/loomweave-cli/src/ analyze.rs # modified: discover plugins, spawn, iterate files, persist entities ``` @@ -294,7 +294,7 @@ New workspace dependencies introduced by WP2: - **UQ-WP2-01** — **Plugin discovery convention (L9)**: ~~open~~ — **resolved as PATH + neighbouring `plugin.toml`** per the §2 L9 proposal. `discovery::discover_plugins` scans `$PATH` entries for - `clarion-plugin-*` basenames and loads `plugin.toml` from next to the + `loomweave-plugin-*` basenames and loads `plugin.toml` from next to the binary (install-prefix fallback deferred — WP3's `pip install -e` places both binary and manifest in the same venv `bin/` directory, so the fallback path has no Sprint-1 surface). World-writable `$PATH` entries @@ -321,18 +321,18 @@ New workspace dependencies introduced by WP2: `plugin::jail`. - **UQ-WP2-04** — **Content-Length ceiling default**: ~~open~~ — **resolved by ADR-021 §2b**. Default ceiling is **8 MiB** per frame, - floor 1 MiB, config key `clarion.yaml:plugin_limits.max_frame_bytes` + floor 1 MiB, config key `loomweave.yaml:plugin_limits.max_frame_bytes` (config surface deferred to WP6; Sprint 1 hard-codes the 8 MiB default). On exceed, the framing parser refuses the frame before deserialising, the plugin is killed (SIGTERM → SIGKILL if non-responsive), and - `CLA-INFRA-PLUGIN-FRAME-OVERSIZE` is emitted. **Resolved**: Task 4. + `LMWV-INFRA-PLUGIN-FRAME-OVERSIZE` is emitted. **Resolved**: Task 4. - **UQ-WP2-05** — **Entity-count cap: cap per file or per run?** ~~open~~ — **resolved by ADR-021 §2c**. Per-run cumulative cap on `entity + edge + finding` notifications combined. Default **500,000**, - floor 10,000, config key `clarion.yaml:plugin_limits.max_records_per_run` + floor 10,000, config key `loomweave.yaml:plugin_limits.max_records_per_run` (config surface deferred to WP6; Sprint 1 hard-codes the 500k default). On exceed: current in-flight batch flushed, plugin killed, run enters - partial-results state, `CLA-INFRA-PLUGIN-ENTITY-CAP` emitted. + partial-results state, `LMWV-INFRA-PLUGIN-ENTITY-CAP` emitted. **Resolved**: Task 4. - **UQ-WP2-06** — **prlimit on non-Linux**: ~~open~~ — **resolved as `#[cfg(target_os = "linux")]`-gated enforcement**. @@ -365,22 +365,22 @@ New workspace dependencies introduced by WP2: enforcement); deferred to WP3 for the Python plugin's stdout swap. - **UQ-WP2-09** — **Manifest hot-reload**: ~~open~~ — **resolved as always-reload in Sprint 1**. `discovery::discover_plugins` - is invoked once per `clarion analyze` run and re-reads every + is invoked once per `loomweave analyze` run and re-reads every `plugin.toml` from disk; no in-memory manifest cache is carried across runs. Manifest cache-across-`serve`-sessions is a WP8 concern. **Resolved**: Task 2 / Task 5 (`plugin::{manifest, discovery}`). - **UQ-WP2-10** — **Crash-loop breaker parameters**: ~~open~~ — **resolved by ADR-002 + ADR-021 §Layer 3**. General breaker: - **>3 crashes in 60s** → plugin disabled, `CLA-INFRA-PLUGIN-DISABLED-CRASH-LOOP`. + **>3 crashes in 60s** → plugin disabled, `LMWV-INFRA-PLUGIN-DISABLED-CRASH-LOOP`. Path-escape sub-breaker (ADR-021 §2a): **>10 escapes in 60s** → plugin - killed, `CLA-INFRA-PLUGIN-DISABLED-PATH-ESCAPE`. Sprint 1 hard-codes both + killed, `LMWV-INFRA-PLUGIN-DISABLED-PATH-ESCAPE`. Sprint 1 hard-codes both thresholds; config surface deferred to WP6. **Resolved**: Task 7. - **UQ-WP2-11** — **Plugin-returned `id` vs 3-segment L2 format**: ~~open~~ — **resolved by identity check in `plugin::host` (T4)** per the §UQ-WP2-11 proposal. On every `analyze_file` response, the host reconstructs the expected `EntityId` from `(plugin_id, kind, qualified_name)` and compares against the returned `id`; on mismatch the entity is dropped and - `CLA-INFRA-PLUGIN-ENTITY-ID-MISMATCH` is emitted. This extends ADR-022 + `LMWV-INFRA-PLUGIN-ENTITY-ID-MISMATCH` is emitted. This extends ADR-022 ontology-boundary enforcement to the ADR-003 identity format. The host-level T4 test plus `cross_plugin_plugin_id_spoof_is_rejected` (scrub commit `89b2da0`) cover both the shape-mismatch and cross-plugin @@ -392,9 +392,9 @@ New workspace dependencies introduced by WP2: ### Task 1 — Manifest parser (L5) **Files**: -- Create `/crates/clarion-core/src/plugin/mod.rs` -- Create `/crates/clarion-core/src/plugin/manifest.rs` -- Modify `/crates/clarion-core/src/lib.rs` to `pub mod plugin;` +- Create `/crates/loomweave-core/src/plugin/mod.rs` +- Create `/crates/loomweave-core/src/plugin/manifest.rs` +- Modify `/crates/loomweave-core/src/lib.rs` to `pub mod plugin;` Steps: @@ -405,22 +405,22 @@ Steps: - Negative: missing `[plugin].name` returns a clear error. - Negative: `expected_max_rss_mb = 0` rejected (must be > 0). - Negative: `entity_kinds = []` rejected (must declare at least one). - - Negative (ADR-022 identifier grammar): an entity kind not matching `[a-z][a-z0-9_]*` (e.g. `Function`, `func-tion`, `1function`) is rejected with `CLA-INFRA-MANIFEST-MALFORMED`. - - Negative (ADR-022 identifier grammar): a `rule_id_prefix` not matching `CLA-[A-Z]+(-[A-Z0-9]+)+` (e.g. `PY-`, `cla-py-`, `CLA-py-`) is rejected with `CLA-INFRA-MANIFEST-MALFORMED`. - - Negative (ADR-022 reserved kinds): a manifest declaring `file`, `subsystem`, or `guidance` in `entity_kinds` is rejected with `CLA-INFRA-MANIFEST-RESERVED-KIND`. - - Negative (ADR-022 namespace registry): `rule_id_prefix = "CLA-INFRA-"` rejected with `CLA-INFRA-RULE-ID-NAMESPACE` (core-only namespace). - - Negative (ADR-022 namespace registry): `rule_id_prefix = "CLA-FACT-"` rejected with `CLA-INFRA-RULE-ID-NAMESPACE` (shared namespace; plugins must use their own). - - Negative (ADR-021 §Layer 1): a manifest declaring `reads_outside_project_root = true` produces a validator result that the supervisor (Task 6) surfaces at `initialize` as `CLA-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY`. Task 1's test asserts the validator flags the manifest; Task 6's test asserts the handshake rejection path. + - Negative (ADR-022 identifier grammar): an entity kind not matching `[a-z][a-z0-9_]*` (e.g. `Function`, `func-tion`, `1function`) is rejected with `LMWV-INFRA-MANIFEST-MALFORMED`. + - Negative (ADR-022 identifier grammar): a `rule_id_prefix` not matching `LMWV-[A-Z]+(-[A-Z0-9]+)+` (e.g. `PY-`, `lmwv-py-`, `LMWV-py-`) is rejected with `LMWV-INFRA-MANIFEST-MALFORMED`. + - Negative (ADR-022 reserved kinds): a manifest declaring `file`, `subsystem`, or `guidance` in `entity_kinds` is rejected with `LMWV-INFRA-MANIFEST-RESERVED-KIND`. + - Negative (ADR-022 namespace registry): `rule_id_prefix = "LMWV-INFRA-"` rejected with `LMWV-INFRA-RULE-ID-NAMESPACE` (core-only namespace). + - Negative (ADR-022 namespace registry): `rule_id_prefix = "LMWV-FACT-"` rejected with `LMWV-INFRA-RULE-ID-NAMESPACE` (shared namespace; plugins must use their own). + - Negative (ADR-021 §Layer 1): a manifest declaring `reads_outside_project_root = true` produces a validator result that the supervisor (Task 6) surfaces at `initialize` as `LMWV-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY`. Task 1's test asserts the validator flags the manifest; Task 6's test asserts the handshake rejection path. - [ ] Run tests; expect failures. -- [ ] Implement `pub fn parse_manifest(bytes: &[u8]) -> Result`. Include the two ADR-022 grammar regexes, the reserved-entity-kind list (`file`, `subsystem`, `guidance`), and the reserved-prefix list (`CLA-INFRA-`, `CLA-FACT-`). +- [ ] Implement `pub fn parse_manifest(bytes: &[u8]) -> Result`. Include the two ADR-022 grammar regexes, the reserved-entity-kind list (`file`, `subsystem`, `guidance`), and the reserved-prefix list (`LMWV-INFRA-`, `LMWV-FACT-`). - [ ] Run tests; expect pass. - [ ] Commit: `feat(wp2): L5 plugin manifest parser and validator (ADR-021 §Layer 1 + ADR-022 grammar)`. ### Task 2 — JSON-RPC transport (L4) **Files**: -- Create `/crates/clarion-core/src/plugin/transport.rs` -- Create `/crates/clarion-core/src/plugin/protocol.rs` +- Create `/crates/loomweave-core/src/plugin/transport.rs` +- Create `/crates/loomweave-core/src/plugin/protocol.rs` Steps: @@ -436,7 +436,7 @@ Steps: ### Task 3 — In-process mock plugin (test harness) **Files**: -- Create `/crates/clarion-core/src/plugin/mock.rs` +- Create `/crates/loomweave-core/src/plugin/mock.rs` Steps: @@ -447,8 +447,8 @@ Steps: ### Task 4 — Core-enforced minimums (L6) **Files**: -- Create `/crates/clarion-core/src/plugin/jail.rs` -- Create `/crates/clarion-core/src/plugin/limits.rs` +- Create `/crates/loomweave-core/src/plugin/jail.rs` +- Create `/crates/loomweave-core/src/plugin/limits.rs` Steps: @@ -460,44 +460,44 @@ Steps: - `EntityCountCap` with **500,000 default** per ADR-021 §2c; `try_admit(delta: usize) -> Result<(), CapExceeded>` tracks cumulative `entity + edge + finding` across the run. - `PathEscapeBreaker` with ADR-021 §2a threshold (**>10 escapes in 60s**) — rolling counter consumed by Task 6's host when a `JailError::EscapedRoot` is observed on a plugin response. - `apply_prlimit_as(max_rss_mib: u64)` using `nix::sys::resource::setrlimit` inside `CommandExt::pre_exec` (pre-exec fork path) — applies `RLIMIT_AS` per ADR-021 §2d with **2 GiB default**. Effective limit = `min(manifest.capabilities.runtime.expected_max_rss_mb, core_default)`. `#[cfg(target_os = "linux")]`-gated (UQ-WP2-06); on non-Linux, log-once warning. - - **Deferred**: `CLA-INFRA-PLUGIN-ENTITY-OVERRUN-WARNING` (ADR-021 §Consequences/Negative) — the per-file sanity warning fired when a plugin exceeds its declared `capabilities.runtime.expected_entities_per_file`. Sprint 1's walking skeleton is one file per invocation, so the warning has no useful surface area; implementation lands in the catalog-emitting Tier B sprint alongside multi-file discovery. Documented here so the subcode and declaration field stay wired end-to-end. + - **Deferred**: `LMWV-INFRA-PLUGIN-ENTITY-OVERRUN-WARNING` (ADR-021 §Consequences/Negative) — the per-file sanity warning fired when a plugin exceeds its declared `capabilities.runtime.expected_entities_per_file`. Sprint 1's walking skeleton is one file per invocation, so the warning has no useful surface area; implementation lands in the catalog-emitting Tier B sprint alongside multi-file discovery. Documented here so the subcode and declaration field stay wired end-to-end. - [ ] Tests for each; commit. - [ ] Commit: `feat(wp2): L6 core-enforced minimums — path jail, ceilings, prlimit (ADR-021 defaults)`. ### Task 5 — Plugin discovery (L9) **Files**: -- Create `/crates/clarion-core/src/plugin/discovery.rs` +- Create `/crates/loomweave-core/src/plugin/discovery.rs` Steps: -- [ ] Write failing test: discovery finds a mock `clarion-plugin-*` binary on a test `$PATH` and loads its manifest from the expected location beside it. -- [ ] Implement: scan `$PATH` for entries matching `clarion-plugin-*`; for each, look for `plugin.toml` next to the binary first, fall back to `/share/clarion/plugins//plugin.toml`. +- [ ] Write failing test: discovery finds a mock `loomweave-plugin-*` binary on a test `$PATH` and loads its manifest from the expected location beside it. +- [ ] Implement: scan `$PATH` for entries matching `loomweave-plugin-*`; for each, look for `plugin.toml` next to the binary first, fall back to `/share/loomweave/plugins//plugin.toml`. - [ ] Run; expect pass. - [ ] Commit: `feat(wp2): L9 plugin discovery convention (PATH + neighboring manifest)`. ### Task 6 — Plugin-host supervisor **Files**: -- Create `/crates/clarion-core/src/plugin/host.rs` +- Create `/crates/loomweave-core/src/plugin/host.rs` Steps: - [ ] Failing integration test: using a real subprocess (a tiny Rust binary in `tests/fixtures/` that speaks the protocol), `PluginHost::spawn(manifest, root)` completes a handshake, issues one `analyze_file` for a fixture, receives entities, and shuts down cleanly. Assert plugin exit code 0. -- [ ] Failing test (ADR-021 §Layer 1): a plugin whose manifest declares `capabilities.runtime.reads_outside_project_root = true` is refused at `initialize`; the host emits `CLA-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY` and the plugin process is terminated before any `analyze_file` is issued. v0.1 has no mechanism to allow this capability. -- [ ] Failing test: ontology-boundary enforcement (ADR-022) — the fixture plugin emits an entity with `kind: "unknown"` not in the manifest; host drops it and emits `CLA-INFRA-PLUGIN-UNDECLARED-KIND`. +- [ ] Failing test (ADR-021 §Layer 1): a plugin whose manifest declares `capabilities.runtime.reads_outside_project_root = true` is refused at `initialize`; the host emits `LMWV-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY` and the plugin process is terminated before any `analyze_file` is issued. v0.1 has no mechanism to allow this capability. +- [ ] Failing test: ontology-boundary enforcement (ADR-022) — the fixture plugin emits an entity with `kind: "unknown"` not in the manifest; host drops it and emits `LMWV-INFRA-PLUGIN-UNDECLARED-KIND`. - [ ] Failing test: identity-mismatch rejection (UQ-WP2-11) — fixture plugin emits an entity whose `id` doesn't match `entity_id(plugin_id, kind, qualified_name)`; host drops it. -- [ ] Failing test: path-jail drop-not-kill (ADR-021 §2a) — fixture plugin emits an `analyze_file` response with a `source.file_path` that canonicalises outside `project_root`. Host drops the entity, emits `CLA-INFRA-PLUGIN-PATH-ESCAPE`, and the plugin remains alive for the next request. -- [ ] Failing test: path-escape sub-breaker (ADR-021 §2a) — fixture plugin emits 11 escaping paths within 60s; on the 11th, the host kills the plugin and emits `CLA-INFRA-PLUGIN-DISABLED-PATH-ESCAPE`. +- [ ] Failing test: path-jail drop-not-kill (ADR-021 §2a) — fixture plugin emits an `analyze_file` response with a `source.file_path` that canonicalises outside `project_root`. Host drops the entity, emits `LMWV-INFRA-PLUGIN-PATH-ESCAPE`, and the plugin remains alive for the next request. +- [ ] Failing test: path-escape sub-breaker (ADR-021 §2a) — fixture plugin emits 11 escaping paths within 60s; on the 11th, the host kills the plugin and emits `LMWV-INFRA-PLUGIN-DISABLED-PATH-ESCAPE`. - [ ] Implement `host.rs`: - Spawn subprocess with `std::process::Command`, stdin/stdout piped. - Apply `apply_prlimit_as` (from Task 4) inside `CommandExt::pre_exec` before `exec`, using `min(manifest.capabilities.runtime.expected_max_rss_mb, core_default = 2 GiB)`. - Perform handshake: send `initialize`, await response; send `initialized` notification. - - **Before** sending `initialized`: if `manifest.capabilities.runtime.reads_outside_project_root == true`, emit `CLA-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY`, send `shutdown` + `exit`, and return a host error to the caller. Do not dispatch any `analyze_file` requests. (ADR-021 §Layer 1: v0.1 has no mechanism to allow this capability.) + - **Before** sending `initialized`: if `manifest.capabilities.runtime.reads_outside_project_root == true`, emit `LMWV-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY`, send `shutdown` + `exit`, and return a host error to the caller. Do not dispatch any `analyze_file` requests. (ADR-021 §Layer 1: v0.1 has no mechanism to allow this capability.) - Provide `PluginHost::analyze_file(path: &Path) -> Result>` that: - Runs the request-side path through the jail (jail error on request = host error returned to caller; no plugin involvement). - Sends request, awaits response. - - For each returned entity/edge/finding: run its `source.file_path` and evidence-anchor paths through the jail. On `EscapedRoot`, drop the record, emit `CLA-INFRA-PLUGIN-PATH-ESCAPE`, and tick the `PathEscapeBreaker` counter. If the breaker trips, kill the plugin and emit `CLA-INFRA-PLUGIN-DISABLED-PATH-ESCAPE`. + - For each returned entity/edge/finding: run its `source.file_path` and evidence-anchor paths through the jail. On `EscapedRoot`, drop the record, emit `LMWV-INFRA-PLUGIN-PATH-ESCAPE`, and tick the `PathEscapeBreaker` counter. If the breaker trips, kill the plugin and emit `LMWV-INFRA-PLUGIN-DISABLED-PATH-ESCAPE`. - Validate each surviving entity: ontology kind (ADR-022), `EntityId` reconstruction match. - Returns surviving entities. - On drop, send `shutdown` + `exit` + wait (with timeout). @@ -507,7 +507,7 @@ Steps: ### Task 7 — Crash-loop breaker **Files**: -- Create `/crates/clarion-core/src/plugin/breaker.rs` +- Create `/crates/loomweave-core/src/plugin/breaker.rs` Steps: @@ -516,30 +516,30 @@ Steps: - [ ] Run; expect pass. - [ ] Commit: `feat(wp2): crash-loop breaker`. -### Task 8 — Wire `clarion analyze` to use the plugin host +### Task 8 — Wire `loomweave analyze` to use the plugin host **Files**: -- Modify `/crates/clarion-cli/src/analyze.rs` +- Modify `/crates/loomweave-cli/src/analyze.rs` Steps: -- [ ] Modify `clarion analyze`: +- [ ] Modify `loomweave analyze`: - On start: discover plugins (Task 5). - For each discovered plugin, spawn (Task 6), iterate the source tree, call `analyze_file` per matching file (match against the manifest's `[plugin].extensions` field). - Persist returned entities via the writer-actor (WP1 Task 6). - On plugin error or cap hit, mark run as failed with diagnostic. -- [ ] Failing integration test: using the mock plugin fixture, `clarion analyze fixtures/demo.py` produces a run with `entity_count > 0`. +- [ ] Failing integration test: using the mock plugin fixture, `loomweave analyze fixtures/demo.py` produces a run with `entity_count > 0`. - [ ] Run; expect pass. -- [ ] Commit: `feat(wp2): wire clarion analyze to plugin host`. +- [ ] Commit: `feat(wp2): wire loomweave analyze to plugin host`. ### Task 9 — WP2 end-to-end smoke test **Files**: -- Create `/crates/clarion-cli/tests/wp2_e2e.rs` +- Create `/crates/loomweave-cli/tests/wp2_e2e.rs` Steps: -- [ ] Integration test using the fixture mock-plugin binary: `clarion install` + `clarion analyze fixture_dir/` produces a completed run with the mock's expected entity persisted. +- [ ] Integration test using the fixture mock-plugin binary: `loomweave install` + `loomweave analyze fixture_dir/` produces a completed run with the mock's expected entity persisted. - [ ] Commit: `test(wp2): end-to-end smoke with mock plugin`. ## 7. ADR triggers @@ -553,7 +553,7 @@ WP2 is done for Sprint 1 when all of: - L4 (JSON-RPC method set + transport), L5 (manifest parser), L6 (each of the four minimums), L9 (discovery) each have ≥1 passing positive test and ≥1 passing negative test. -- `clarion analyze` with the mock plugin on a fixture produces persisted entities +- `loomweave analyze` with the mock plugin on a fixture produces persisted entities in the DB. - Every UQ-WP2-* is marked resolved in this doc's §5. - `cargo test --workspace` passes. diff --git a/docs/implementation/sprint-1/wp3-python-plugin.md b/docs/implementation/sprint-1/wp3-python-plugin.md index 789f66fd..baae885a 100644 --- a/docs/implementation/sprint-1/wp3-python-plugin.md +++ b/docs/implementation/sprint-1/wp3-python-plugin.md @@ -1,8 +1,8 @@ # WP3 — Python Plugin v0.1 Baseline (Sprint 1) **Status**: DRAFT — blocked-by WP2 -**Anchoring design**: [detailed-design.md §1 (Plugin implementation — Python specifics)](../../clarion/v0.1/detailed-design.md#1-plugin-implementation-detail), [system-design.md §2](../../clarion/v0.1/system-design.md#2-core--plugin-architecture) -**Accepted ADRs**: [ADR-018](../../clarion/adr/ADR-018-identity-reconciliation.md), [ADR-022](../../clarion/adr/ADR-022-core-plugin-ontology.md), [ADR-023](../../clarion/adr/ADR-023-tooling-baseline.md) +**Anchoring design**: [detailed-design.md §1 (Plugin implementation — Python specifics)](../../loomweave/v0.1/detailed-design.md#1-plugin-implementation-detail), [system-design.md §2](../../loomweave/v0.1/system-design.md#2-core--plugin-architecture) +**Accepted ADRs**: [ADR-018](../../loomweave/adr/ADR-018-identity-reconciliation.md), [ADR-022](../../loomweave/adr/ADR-022-core-plugin-ontology.md), [ADR-023](../../loomweave/adr/ADR-023-tooling-baseline.md) **Predecessor**: [WP2](./wp2-plugin-host.md). **Blocks**: the Sprint 1 walking-skeleton demo. @@ -17,15 +17,15 @@ and makes one real pass at the Wardline `REGISTRY` integration surface (even if pass is a graceful-no-op probe). Feature-completeness for the Python plugin — every entity kind, every edge, every -`CLA-PY-*` rule — is **not** Sprint 1. That's the WP3-feature-complete sprint, +`LMWV-PY-*` rule — is **not** Sprint 1. That's the WP3-feature-complete sprint, reached after WP4 exists and can consume richer plugin output. **In scope for Sprint 1:** -- Python package `clarion-plugin-python` installable via `pip install -e +- Python package `loomweave-plugin-python` installable via `pip install -e plugins/python/` from the monorepo. - `plugin.toml` manifest matching WP2's L5 schema. -- `clarion-plugin-python` executable (entry point) on `$PATH` after install. +- `loomweave-plugin-python` executable (entry point) on `$PATH` after install. - JSON-RPC server speaking WP2's L4 method set: `initialize`, `initialized`, `analyze_file`, `shutdown`, `exit`. - `ast`-based function extraction: for a `.py` file, emit one entity per @@ -58,8 +58,8 @@ reached after WP4 exists and can consume richer plugin output. - All edge kinds — deferred. _Sprint 2 / B.3 realises the first edge kind (**contains**), the dual-encoded `parent_id` field, the per-kind source-range contract, and the writer-actor's parent/contains consistency check; see [`../sprint-2/b3-contains-edges.md`](../sprint-2/b3-contains-edges.md) and the ADRs it locks (ADR-026, ADR-027). Calls/imports/decorates/inherits_from remain deferred to later WP3-feature-complete sprints._ - Dynamic imports (`importlib`, `__import__`) — deferred; deliberately not solved in v0.1 per the out-of-scope list in `../v0.1-plan.md`. -- All `CLA-PY-*` findings — WP4 consumes, WP3-feature-complete emits. -- Type-inference, dataflow, taint — NG-05, never in scope for Clarion. +- All `LMWV-PY-*` findings — WP4 consumes, WP3-feature-complete emits. +- Type-inference, dataflow, taint — NG-05, never in scope for Loomweave. - Multi-file incremental analysis — WP6/WP7 territory. ## 2. Lock-in callouts @@ -95,8 +95,8 @@ assert against hand-written expectations derived from Python's documented `src/` prefix stripping; UQ-WP3-05). **Why now**: `↗` ADR-018 — Wardline stores qualnames produced by the same rule -and uses this segment (not the full 3-segment `EntityId`) as its Clarion-side -join key. Divergence means Filigree's triage state can't join Clarion's +and uses this segment (not the full 3-segment `EntityId`) as its Loomweave-side +join key. Divergence means Filigree's triage state can't join Loomweave's entities to Wardline's annotations. This is the single most important cross-product alignment in Sprint 1. @@ -104,7 +104,7 @@ cross-product alignment in Sprint 1. - WP1's L2 `entity_id()` concatenates `python`, `function`, and this segment. Shared fixture (Task 5) is the byte-for-byte parity proof. - `↗` Wardline must produce the same qualnames. If Wardline's rule diverges, either - Wardline changes or Clarion adds translation. ADR-018 names direct-production as + Wardline changes or Loomweave adds translation. ADR-018 names direct-production as the default; WP3's tests are the executable spec for what "match Wardline" means. **Mitigation if Wardline diverges**: discovered via the first real cross-check (either @@ -116,12 +116,12 @@ change. `FingerprintEntry` (`wardline/src/wardline/manifest/models.py:86-97`) stores `(module: str, qualified_name: str)` as **separate fields** — `module` is the source file path (e.g. `demo.py`) and `qualified_name` -is Python's bare `__qualname__` (e.g. `Foo.bar`). Clarion's L7 emits a +is Python's bare `__qualname__` (e.g. `Foo.bar`). Loomweave's L7 emits a single combined `{dotted_module}.{__qualname__}` string. The two encodings carry the same information but are not byte-equal — joining requires a translator that composes `f"{module_dotted_name(wardline.module)}.{wardline.qualified_name}"` on -the Wardline side using Clarion's `module_dotted_name` rules. Sprint 1 +the Wardline side using Loomweave's `module_dotted_name` rules. Sprint 1 does not exercise the join (the L8 probe verifies presence + version only), so no Sprint-1 code path is broken. Tracked in **`clarion-889200006a`** for ADR-018 amendment when WP9 attempts the @@ -169,7 +169,7 @@ WP3 commits the plugin's consumption pattern. **Cross-product implication**: `↗` **Wardline should not rename `wardline.core.registry.REGISTRY` or drop `wardline.__version__` without a -coordinated Clarion-side release**. ADR-018 already states this; WP3 makes it a +coordinated Loomweave-side release**. ADR-018 already states this; WP3 makes it a real dependency. Same-author note: this is within-scope Wardline discipline, not cross-team coordination — but the constraint is real. @@ -178,7 +178,7 @@ import. Symbol existence was verified pre-sprint (`wardline/src/wardline/core/registry.py:55`, `wardline/src/wardline/__init__.py:3`), so the probe runs against a real `pip install wardline` in the dev venv rather than stubbing behind a -`CLARION_WARDLINE_ENABLED` env var. This locks L8 completely — the +`LOOMWEAVE_WARDLINE_ENABLED` env var. This locks L8 completely — the exercised lock-in is the honest one. ## 3. File decomposition @@ -190,7 +190,7 @@ exercised lock-in is the honest one. .pre-commit-config.yaml # ADR-023: ruff-check, ruff-format, mypy hooks README.md # install + dev notes src/ - clarion_plugin_python/ + loomweave_plugin_python/ __init__.py py.typed # PEP 561 marker so downstream mypy picks up stubs __main__.py # entry point; runs the JSON-RPC server loop @@ -236,7 +236,7 @@ Minimal. `pyproject.toml` declares: runs `ruff check`, `ruff format --check`, `mypy --strict`, and `pytest`. - Optional dep: `wardline` (declared in `[project.optional-dependencies] integrations`). The plugin works without Wardline; declaring it optional allows `pip install - clarion-plugin-python[integrations]` to pull Wardline when desired. + loomweave-plugin-python[integrations]` to pull Wardline when desired. ## 5. Unresolved questions @@ -251,7 +251,7 @@ Minimal. `pyproject.toml` declares: - **UQ-WP3-02** — **Syntax-error handling**: ~~open~~ — **resolved as "skip + stderr log"** per the original proposal. `extract()` catches `SyntaxError` from `ast.parse`, writes one line to `sys.stderr` - (`clarion-plugin-python: skipping : syntax error at line N: `), + (`loomweave-plugin-python: skipping : syntax error at line N: `), and returns `[]`. The run continues; WP4 may later attach a finding. `test_syntax_error_yields_empty_list_and_logs_to_stderr` is the discriminating test. **Resolved**: Task 4 / `plugin.extractor`. @@ -264,13 +264,13 @@ Minimal. `pyproject.toml` declares: **Resolved**: Task 6. - **UQ-WP3-04** — **Minimum Python version**: **Resolved — 3.11**. Picked for `ast.unparse` availability and better error messages; 3.12 raises the - install barrier without a Sprint 1 payoff. Clarion users are developers + install barrier without a Sprint 1 payoff. Loomweave users are developers with reasonable Python versions available. **Resolved**: Task 1. - **UQ-WP3-05** — **Module-path normalisation**: ~~open~~ — **resolved as plugin-side relativisation** (diverges from original proposal). The host sends absolute paths (WP2's CLI canonicalises `project_root` and walks via `entry.path()` — see - `crates/clarion-cli/src/analyze.rs`), so the plugin captures + `crates/loomweave-cli/src/analyze.rs`), so the plugin captures `project_root` from the `initialize` handshake and relativises incoming `file_path` values against it when deriving the dotted-module prefix for `qualified_name`. `source.file_path` emitted on the wire @@ -289,7 +289,7 @@ Minimal. `pyproject.toml` declares: **resolved as "regular function entities"**. Overloaded methods are `FunctionDef`s with a decorator list — the extractor emits each one as a separate entity with the same `qualified_name`, matching Python's - own `__qualname__` behaviour. A future `CLA-PY-OVERLOAD` rule can add + own `__qualname__` behaviour. A future `LMWV-PY-OVERLOAD` rule can add semantic annotation in a later sprint. `test_overloaded_method_gets_ regular_qualname` covers three overloads + the implementation. **Resolved**: Task 3 / `plugin.qualname`. @@ -298,7 +298,7 @@ Minimal. `pyproject.toml` declares: at the repo root has 20 rows covering module-level functions, class methods, ``-marked nested functions, core file/subsystem entities, and hypothetical go/rust plugin IDs. Both - `crates/clarion-core/src/entity_id.rs::tests::shared_fixture_byte_for_byte_parity` + `crates/loomweave-core/src/entity_id.rs::tests::shared_fixture_byte_for_byte_parity` and `plugins/python/tests/test_entity_id.py::test_matches_shared_fixture` consume the same file and assert byte-equal output; divergence fails CI on both sides in lockstep. **Resolved**: Task 5 / `fixtures/entity_id.json`. @@ -307,13 +307,13 @@ Minimal. `pyproject.toml` declares: read-error messages to `sys.stderr` via `sys.stderr.write`. The host captures stderr into a bounded 64 KiB ring buffer (WP2 scrub commit `b3c91a7`, resolving UQ-WP2-07); diagnostics are surfaced via - `host.stderr_tail()`. `.clarion/logs/` as a persistent log destination + `host.stderr_tail()`. `.loomweave/logs/` as a persistent log destination is a Sprint 2+ decision. **Resolved**: Task 2 + Task 4 / `plugin.server`, `plugin.extractor`. - **UQ-WP3-10** — **Testing + tooling infrastructure**: ~~"pytest + ruff; mypy adoption deferred until the plugin grows enough to benefit."~~ — **reopened 2026-04-18 and re-resolved by - [ADR-023](../../clarion/adr/ADR-023-tooling-baseline.md)**. The deferred + [ADR-023](../../loomweave/adr/ADR-023-tooling-baseline.md)**. The deferred framing was the canonical tell for unexamined tech debt: every Python module written without mypy would be a module to retrofit later. ADR-023 adopts `pytest`, `ruff` (strict `select = ["ALL"]` config minus pragmatic @@ -328,7 +328,7 @@ Minimal. `pyproject.toml` declares: **Resolved**: Task 4 / `plugin.extractor`. - **UQ-WP3-12** — **`initialize` response identity**: ~~open~~ — **resolved as "match the manifest exactly"**. The handshake returns - `{name: "clarion-plugin-python", version: "0.1.0", ontology_version: + `{name: "loomweave-plugin-python", version: "0.1.0", ontology_version: "0.1.0", capabilities: {...}}` — every field populated from the package `__version__` + the `ONTOLOGY_VERSION` module constant in `plugin.server`. Cross-check against manifest happens on the host side @@ -343,24 +343,24 @@ Minimal. `pyproject.toml` declares: **Files**: - Create `/plugins/python/pyproject.toml` (package metadata + `[tool.ruff]` strict config + `[tool.mypy]` `strict = true` + `[tool.pytest.ini_options]`) - Create `/plugins/python/.pre-commit-config.yaml` (ruff-check, ruff-format, mypy hooks) -- Create `/plugins/python/src/clarion_plugin_python/__init__.py` -- Create `/plugins/python/src/clarion_plugin_python/py.typed` (PEP 561 marker) -- Create `/plugins/python/src/clarion_plugin_python/__main__.py` +- Create `/plugins/python/src/loomweave_plugin_python/__init__.py` +- Create `/plugins/python/src/loomweave_plugin_python/py.typed` (PEP 561 marker) +- Create `/plugins/python/src/loomweave_plugin_python/__main__.py` - Create `/plugins/python/README.md` - Create `/plugins/python/tests/__init__.py` - Extend `/.github/workflows/ci.yml` with a `python-plugin` job running ruff + mypy + pytest Steps: -- [ ] Write `pyproject.toml` with `project.name = "clarion-plugin-python"`, `requires-python = ">=3.11"` (UQ-WP3-04), `project.scripts.clarion-plugin-python = "clarion_plugin_python.__main__:main"`, no runtime deps, dev deps `pytest`, `pytest-cov`, `ruff`, `mypy`, `pre-commit` (ADR-023). +- [ ] Write `pyproject.toml` with `project.name = "loomweave-plugin-python"`, `requires-python = ">=3.11"` (UQ-WP3-04), `project.scripts.loomweave-plugin-python = "loomweave_plugin_python.__main__:main"`, no runtime deps, dev deps `pytest`, `pytest-cov`, `ruff`, `mypy`, `pre-commit` (ADR-023). - [ ] Configure `[tool.ruff]` with `target-version = "py311"`, `line-length = 100`, `select = ["ALL"]`, pragmatic excludes per ADR-023 (`D` docstring lints relaxed; `COM812`/`ISC001` to avoid format conflict; per-file-ignores for `tests/` and fixtures). `[tool.ruff.format]` matches defaults. - [ ] Configure `[tool.mypy]` with `strict = true`, `python_version = "3.11"`, `warn_unused_configs = true`. Add `[[tool.mypy.overrides]]` entries for any third-party modules without stubs (Sprint 1: none yet; Task 6 may add `packaging` once it's pulled in). -- [ ] Configure `[tool.pytest.ini_options]` with `testpaths = ["tests"]`, `addopts = "--strict-markers --cov=clarion_plugin_python --cov-report=term-missing"`. +- [ ] Configure `[tool.pytest.ini_options]` with `testpaths = ["tests"]`, `addopts = "--strict-markers --cov=loomweave_plugin_python --cov-report=term-missing"`. - [ ] Write `.pre-commit-config.yaml` with hooks for `ruff check --fix`, `ruff format`, and `mypy` (using `additional_dependencies` to install stubs mypy needs inside the hook env). - [ ] Write `py.typed` as an empty file — PEP 561 marker making the package's own type hints visible to downstream mypy consumers. -- [ ] Write `__main__.py` with a typed `def main() -> int:` that writes `clarion-plugin-python 0.1.0\n` to `sys.stderr` and returns 0 (so `pip install -e .` produces a verifiable binary with full type coverage). +- [ ] Write `__main__.py` with a typed `def main() -> int:` that writes `loomweave-plugin-python 0.1.0\n` to `sys.stderr` and returns 0 (so `pip install -e .` produces a verifiable binary with full type coverage). - [ ] `pip install -e plugins/python[dev]` (dev extras) and verify locally: - - `which clarion-plugin-python` returns a path and running it exits 0. + - `which loomweave-plugin-python` returns a path and running it exits 0. - `ruff check plugins/python` passes. - `ruff format --check plugins/python` passes. - `mypy --strict plugins/python` passes (Sprint 1's tiny surface makes this trivial; the discipline is set for every subsequent task). @@ -372,22 +372,22 @@ Steps: ### Task 2 — JSON-RPC server loop + stdout discipline **Files**: -- Create `/plugins/python/src/clarion_plugin_python/stdout_guard.py` -- Create `/plugins/python/src/clarion_plugin_python/server.py` -- Modify `/plugins/python/src/clarion_plugin_python/__main__.py` +- Create `/plugins/python/src/loomweave_plugin_python/stdout_guard.py` +- Create `/plugins/python/src/loomweave_plugin_python/server.py` +- Modify `/plugins/python/src/loomweave_plugin_python/__main__.py` Steps: - [ ] In `stdout_guard.py`: on import, replace `sys.stdout` with an object whose `.buffer` is the real stdout bytes stream and whose `.write` raises. Provide a context manager `jsonrpc_output()` that yields the real bytes stream. This enforces WP2 UQ-WP2-08. - [ ] In `server.py`: implement Content-Length frame read/write from `sys.stdin.buffer` / the real stdout bytes stream. Implement a dispatch loop handling `initialize`, `initialized`, `analyze_file`, `shutdown`, `exit` by method name; each dispatches to a handler function. -- [ ] Handlers for `initialize` (returns `{"name": "clarion-plugin-python", "version": "0.1.0", "ontology_version": "0.1.0"}`, UQ-WP3-12) and `shutdown` (returns `null`, then the next loop iteration exits on `exit`). `analyze_file` returns `{"entities": []}` for now; filled in later tasks. +- [ ] Handlers for `initialize` (returns `{"name": "loomweave-plugin-python", "version": "0.1.0", "ontology_version": "0.1.0"}`, UQ-WP3-12) and `shutdown` (returns `null`, then the next loop iteration exits on `exit`). `analyze_file` returns `{"entities": []}` for now; filled in later tasks. - [ ] Failing integration test in `test_server.py`: spin up the server in a subprocess, send an `initialize` frame, receive a response with the expected shape. - [ ] Implement and verify. Commit: `feat(wp3): L4-compatible JSON-RPC server + stdout guard`. ### Task 3 — Qualname reconstruction (L7) **Files**: -- Create `/plugins/python/src/clarion_plugin_python/qualname.py` +- Create `/plugins/python/src/loomweave_plugin_python/qualname.py` - Create `/plugins/python/tests/test_qualname.py` Steps: @@ -400,8 +400,8 @@ Steps: ### Task 4 — Extractor (ast → entities) **Files**: -- Create `/plugins/python/src/clarion_plugin_python/extractor.py` -- Create `/plugins/python/src/clarion_plugin_python/entity_id.py` +- Create `/plugins/python/src/loomweave_plugin_python/extractor.py` +- Create `/plugins/python/src/loomweave_plugin_python/entity_id.py` - Create `/plugins/python/tests/test_extractor.py` - Create `/plugins/python/tests/fixtures/` with sample `.py` files + expected-entity YAML @@ -420,20 +420,20 @@ Steps: **Files**: - Create `/fixtures/entity_id.json` at repo root -- Modify `/crates/clarion-core/src/entity_id.rs` tests to consume it +- Modify `/crates/loomweave-core/src/entity_id.rs` tests to consume it - Modify `/plugins/python/tests/test_entity_id.py` to consume it Steps: - [ ] Write the fixture: JSON array of objects each with `plugin_id`, `kind`, `canonical_qualified_name`, and `expected_entity_id` fields. At least 20 rows covering the representative cases from ADR-003 (both plugin-emitted `python:function:*` rows and core-reserved `core:file:*`/`core:subsystem:*` rows). -- [ ] Add a test in `clarion-core` that loads the fixture at test time, runs `entity_id()` for each row, asserts equality. Same-shaped test in `plugins/python` (the Python side concatenates its three segments; assertion is on the final string). +- [ ] Add a test in `loomweave-core` that loads the fixture at test time, runs `entity_id()` for each row, asserts equality. Same-shaped test in `plugins/python` (the Python side concatenates its three segments; assertion is on the final string). - [ ] Run both test suites; expect pass in both. - [ ] Commit: `test(wp3): shared EntityId fixture (UQ-WP3-08 resolution)`. ### Task 6 — Wardline probe (L8) **Files**: -- Create `/plugins/python/src/clarion_plugin_python/wardline_probe.py` +- Create `/plugins/python/src/loomweave_plugin_python/wardline_probe.py` - Create `/plugins/python/tests/test_wardline_probe.py` Steps: @@ -451,12 +451,12 @@ Steps: **Files**: - Create `/plugins/python/plugin.toml` -- Modify `/plugins/python/src/clarion_plugin_python/server.py` `analyze_file` handler +- Modify `/plugins/python/src/loomweave_plugin_python/server.py` `analyze_file` handler Steps: -- [ ] Write `plugin.toml` matching WP2 L5 schema: `[plugin]` (name, `plugin_id = "python"`, version, protocol_version, executable, `language = "python"`, `extensions = ["py"]`), `[capabilities.runtime]` per ADR-021 §Layer 1 (`expected_max_rss_mb = 512`, `expected_entities_per_file = 5000`, `wardline_aware = true`, `reads_outside_project_root = false`), `[ontology]` (kinds = `["function"]`, edge_kinds = `[]`, `rule_id_prefix = "CLA-PY-"`, `ontology_version = "0.1.0"`), `[integrations.wardline]` (`min_version = "0.1.0"`, `max_version = "0.2.0"`). The Wardline-specific values in `[integrations.wardline]` flow from the `wardline_aware = true` declaration. -- [ ] Arrange installation to place `plugin.toml` where WP2's discovery (L9) finds it: at install-prefix `share/clarion/plugins/clarion-plugin-python/plugin.toml`. Using `tool.setuptools` or `hatch` data-file declarations in `pyproject.toml`. Verify after `pip install -e .` the file is discoverable. +- [ ] Write `plugin.toml` matching WP2 L5 schema: `[plugin]` (name, `plugin_id = "python"`, version, protocol_version, executable, `language = "python"`, `extensions = ["py"]`), `[capabilities.runtime]` per ADR-021 §Layer 1 (`expected_max_rss_mb = 512`, `expected_entities_per_file = 5000`, `wardline_aware = true`, `reads_outside_project_root = false`), `[ontology]` (kinds = `["function"]`, edge_kinds = `[]`, `rule_id_prefix = "LMWV-PY-"`, `ontology_version = "0.1.0"`), `[integrations.wardline]` (`min_version = "0.1.0"`, `max_version = "0.2.0"`). The Wardline-specific values in `[integrations.wardline]` flow from the `wardline_aware = true` declaration. +- [ ] Arrange installation to place `plugin.toml` where WP2's discovery (L9) finds it: at install-prefix `share/loomweave/plugins/loomweave-plugin-python/plugin.toml`. Using `tool.setuptools` or `hatch` data-file declarations in `pyproject.toml`. Verify after `pip install -e .` the file is discoverable. - [ ] Modify `analyze_file` handler: read the requested path, run `extractor.extract()`, return `{"entities": [...]}`. - [ ] Commit: `feat(wp3): plugin.toml manifest + analyze_file wired to extractor`. @@ -467,7 +467,7 @@ Steps: Steps: -- [ ] Test: spawn the installed plugin as a subprocess; complete handshake; call `analyze_file` on `plugins/python/src/clarion_plugin_python/extractor.py`; assert the returned entities include specific expected ones (the `extract` function itself, etc.); shutdown cleanly. +- [ ] Test: spawn the installed plugin as a subprocess; complete handshake; call `analyze_file` on `plugins/python/src/loomweave_plugin_python/extractor.py`; assert the returned entities include specific expected ones (the `extract` function itself, etc.); shutdown cleanly. - [ ] Commit: `test(wp3): round-trip self-test against plugin's own source`. ### Task 9 — Sprint 1 walking-skeleton end-to-end @@ -496,11 +496,11 @@ WP3 is done for Sprint 1 when all of: - The walking-skeleton demo script (README §3) passes end-to-end on a clean machine. - L7 (qualname) and L8 (Wardline probe) each have passing positive and negative tests. - The shared `EntityId` fixture (`fixtures/entity_id.json`) passes in both the - Rust (`clarion-core::entity_id`) and Python (`test_entity_id.py`) test suites. + Rust (`loomweave-core::entity_id`) and Python (`test_entity_id.py`) test suites. - Round-trip self-test passes. - Every UQ-WP3-* is marked resolved in §5. - `pip install -e plugins/python[dev]` works on a clean Python 3.11 venv and - `clarion-plugin-python` is on `$PATH`. + `loomweave-plugin-python` is on `$PATH`. - **ADR-023 gates green** (all four): `ruff check plugins/python`, `ruff format --check plugins/python`, `mypy --strict plugins/python`, and `pytest plugins/python` all pass on the WP3 closing commit. diff --git a/docs/implementation/sprint-2/b2-class-module-entities.md b/docs/implementation/sprint-2/b2-class-module-entities.md index 3203c68e..f79fa11b 100644 --- a/docs/implementation/sprint-2/b2-class-module-entities.md +++ b/docs/implementation/sprint-2/b2-class-module-entities.md @@ -1,8 +1,8 @@ # B.2 — Python plugin: class + module entity emission (Sprint 2 / WP3 feature-complete subset) **Status**: DRAFT — Sprint 2 Tier-B B.2 work-package design -**Anchoring design**: [detailed-design.md §1 (Plugin implementation — Python specifics)](../../clarion/v0.1/detailed-design.md#1-plugin-implementation-detail), [system-design.md §6 (Guidance composition; clustering)](../../clarion/v0.1/system-design.md#6-guidance-system) -**Accepted ADRs**: [ADR-007](../../clarion/adr/ADR-007-summary-cache-key.md), [ADR-018](../../clarion/adr/ADR-018-identity-reconciliation.md), [ADR-021](../../clarion/adr/ADR-021-plugin-authority-hybrid.md), [ADR-022](../../clarion/adr/ADR-022-core-plugin-ontology.md), [ADR-023](../../clarion/adr/ADR-023-tooling-baseline.md) +**Anchoring design**: [detailed-design.md §1 (Plugin implementation — Python specifics)](../../loomweave/v0.1/detailed-design.md#1-plugin-implementation-detail), [system-design.md §6 (Guidance composition; clustering)](../../loomweave/v0.1/system-design.md#6-guidance-system) +**Accepted ADRs**: [ADR-007](../../loomweave/adr/ADR-007-summary-cache-key.md), [ADR-018](../../loomweave/adr/ADR-018-identity-reconciliation.md), [ADR-021](../../loomweave/adr/ADR-021-plugin-authority-hybrid.md), [ADR-022](../../loomweave/adr/ADR-022-core-plugin-ontology.md), [ADR-023](../../loomweave/adr/ADR-023-tooling-baseline.md) **Predecessor**: [WP3 Sprint-1 baseline](../sprint-1/wp3-python-plugin.md) (function-only extraction) **Successor**: B.3 — `contains` edges (planned) **Sprint-2 kickoff handoff**: [`docs/superpowers/handoffs/2026-04-30-sprint-2-kickoff.md`](../handoffs/2026-04-30-sprint-2-kickoff.md) §"What's in scope for Sprint 2" Tier B row B.2 @@ -23,20 +23,20 @@ B.2 extends the Python plugin to emit two additional entity kinds beyond Sprint - Imports, calls, decorated_by, inherits_from edges (later WP3-feature-complete sprints). - `package` entity kind (later — requires multi-file context the per-file `analyze_file` RPC doesn't carry). - `decorator`, `global`, `protocol`, `enum`, `typed_dict`, `type_alias` entity kinds (later). -- All `CLA-PY-*` findings including `CLA-PY-SYNTAX-ERROR` (the `parse_status` property is set on the module entity; the matching finding emission lands when WP4's finding pipeline activates). +- All `LMWV-PY-*` findings including `LMWV-PY-SYNTAX-ERROR` (the `parse_status` property is set on the module entity; the matching finding emission lands when WP4's finding pipeline activates). ## 2. Locked surfaces from Sprint 1 (B.2 reads and writes against these) These are caller-observable surfaces locked at `v0.1-sprint-1` close. B.2 must not change them; if a change is genuinely needed, write an ADR amendment per the kickoff-handoff convention. -- **Wire shape** (`crates/clarion-core/src/plugin/host.rs:132-154`): `RawEntity { id, kind, qualified_name, source: RawSource, extra }` with `#[serde(flatten)] extra`. `RawSource { file_path, extra }` likewise. New top-level fields ride through `extra` non-breakingly. +- **Wire shape** (`crates/loomweave-core/src/plugin/host.rs:132-154`): `RawEntity { id, kind, qualified_name, source: RawSource, extra }` with `#[serde(flatten)] extra`. `RawSource { file_path, extra }` likewise. New top-level fields ride through `extra` non-breakingly. - **L7 qualname format**: `{dotted_module}.{__qualname__}`. `` marker only at function-parent boundaries. `module_dotted_name` strips `src/` prefix, drops `.py`, collapses `pkg/__init__.py` → `pkg`. - **L4 JSON-RPC method set**: `initialize`, `initialized`, `analyze_file`, `shutdown`, `exit`. B.2 changes none of these. - **L5 manifest schema**: B.2 amends only `[ontology].entity_kinds` (adds `"class"`, `"module"`) and `[ontology].ontology_version` (`0.1.0` → `0.2.0`). No structural change. - **L8 Wardline pin**: unchanged. - **`extract()` signature** (`extractor.py:86-99`): `extract(source: str, file_path: str, *, module_prefix_path: str | None = None) -> list[dict[str, Any]]`. B.2 keeps the signature; the return-type annotation tightens to a `TypedDict` form (see §4). - **`ServerState`** (`server.py:60-66`): unchanged. -- **`ONTOLOGY_VERSION` constant** in `server.py`: bumps `0.1.0` → `0.2.0` in lockstep with `plugin.toml::ontology_version`. The bump is the L5 handshake-validation signal that the entity-kind set has shifted; **note that ADR-007's summary-cache key is the 5-tuple `(entity_id, content_hash, prompt_template_id, model_tier, guidance_fingerprint)` and `ontology_version` is NOT a cache-key component** (this corrects an imprecise framing in the kickoff handoff — verified at `docs/clarion/adr/ADR-007-summary-cache-key.md:10`). Cache invalidation when the kind set expands happens organically: new module/class entity_ids miss the cache by component-1 of the 5-tuple. +- **`ONTOLOGY_VERSION` constant** in `server.py`: bumps `0.1.0` → `0.2.0` in lockstep with `plugin.toml::ontology_version`. The bump is the L5 handshake-validation signal that the entity-kind set has shifted; **note that ADR-007's summary-cache key is the 5-tuple `(entity_id, content_hash, prompt_template_id, model_tier, guidance_fingerprint)` and `ontology_version` is NOT a cache-key component** (this corrects an imprecise framing in the kickoff handoff — verified at `docs/loomweave/adr/ADR-007-summary-cache-key.md:10`). Cache invalidation when the kind set expands happens organically: new module/class entity_ids miss the cache by component-1 of the 5-tuple. ## 3. Design decisions (Q1–Q4 panel-resolved) @@ -50,7 +50,7 @@ Each design decision below was taken to a five-reviewer panel (systems thinker, - Module is `leaf: false` in the kind catalogue (`detailed-design.md:74`) — a structural container tied to *files*, not content. Suppressing module entities for empty / broken files would break referential integrity for B.3's `contains` edges (every function/class entity must contain into a module entity that exists) and force B.4 catalog rendering and B.5 per-subsystem markdown to write defensive null-parent guards. - An empty `__init__.py` is a load-bearing Python semantic artifact (package marker). Omitting it from the catalog passes verification but fails validation (the user asks "what packages exist?" and gets a wrong answer). - Syntax-error files are exactly what an archaeologist most needs to see ("why is this file broken?"). The `parse_status` property keeps the truth honest without dropping the entity. -- Top-level `__init__.py` would crash the entity-ID assembler (`crates/clarion-core/src/entity_id.rs:97-101` rejects empty `canonical_qualified_name`). Skipping with a stderr line matches the existing syntax-error posture (`extractor.py:103-106`). +- Top-level `__init__.py` would crash the entity-ID assembler (`crates/loomweave-core/src/entity_id.rs:97-101` rejects empty `canonical_qualified_name`). Skipping with a stderr line matches the existing syntax-error posture (`extractor.py:103-106`). **Supersedes**: UQ-WP3-11 (`wp3-python-plugin.md:322-327` — "empty `.py` file → empty entities array"). UQ-WP3-11 was scoped to *function* extraction at Sprint-1 close. B.2 introduces module as a new kind; for module entities specifically, the always-emit policy applies. The function-extraction part of UQ-WP3-11 still holds: an empty file produces zero *function* entities. @@ -143,8 +143,8 @@ In a single commit: | File | Change | |---|---| | `plugins/python/plugin.toml` | `[ontology].entity_kinds = ["function", "class", "module"]`; `[ontology].ontology_version = "0.2.0"` | -| `plugins/python/src/clarion_plugin_python/server.py` | `ONTOLOGY_VERSION = "0.2.0"` | -| `plugins/python/src/clarion_plugin_python/__init__.py` | bump `__version__` (the package version) — separate decision; see §10 | +| `plugins/python/src/loomweave_plugin_python/server.py` | `ONTOLOGY_VERSION = "0.2.0"` | +| `plugins/python/src/loomweave_plugin_python/__init__.py` | bump `__version__` (the package version) — separate decision; see §10 | **Lint guard suggestion** (from Python-engineer review): a small shell assertion in the CI `python-plugin` job that greps `[ontology].ontology_version` from `plugin.toml` and `ONTOLOGY_VERSION = "..."` from `server.py` and fails if they disagree. Out of scope for B.2 itself but worth filing as a follow-up. @@ -180,7 +180,7 @@ New test in `test_extractor.py` for the helper: ### Integration tests -- `crates/clarion-core/tests/wp2_e2e_*.rs` — host-side: a fixture file with `def hello():` and `class Foo:` produces three entities (one module, one function, one class) with the right kinds and IDs. The existing `wp2_e2e_smoke_fixture_plugin_round_trip` test will need updating; the test should assert on entity-kinds-set rather than exact count. +- `crates/loomweave-core/tests/wp2_e2e_*.rs` — host-side: a fixture file with `def hello():` and `class Foo:` produces three entities (one module, one function, one class) with the right kinds and IDs. The existing `wp2_e2e_smoke_fixture_plugin_round_trip` test will need updating; the test should assert on entity-kinds-set rather than exact count. ### End-to-end @@ -196,7 +196,7 @@ Grow with module + class rows for byte-for-byte parity: - `python:class:pkg.mod.Foo.Bar` (nested class). - `python:class:pkg.mod.f..C` (class-in-function). -Both `crates/clarion-core/src/entity_id.rs::tests::shared_fixture_byte_for_byte_parity` and `plugins/python/tests/test_entity_id.py::test_matches_shared_fixture` consume the same file; divergence fails CI on both sides. +Both `crates/loomweave-core/src/entity_id.rs::tests::shared_fixture_byte_for_byte_parity` and `plugins/python/tests/test_entity_id.py::test_matches_shared_fixture` consume the same file; divergence fails CI on both sides. ### Round-trip self-test @@ -210,7 +210,7 @@ class_entities = [e for e in entities if e["kind"] == "class"] # Invariants — no exact totals (those become merge-conflict generators). assert len(module_entities) == 1, "exactly one module entity per analyzed file" assert all(e["source"]["file_path"] == str(target) for e in entities) -assert any(e["qualified_name"] == "clarion_plugin_python.extractor.extract" for e in function_entities) +assert any(e["qualified_name"] == "loomweave_plugin_python.extractor.extract" for e in function_entities) ``` ## 7. Implementation task ledger @@ -218,7 +218,7 @@ assert any(e["qualified_name"] == "clarion_plugin_python.extractor.extract" for ### Task 1 — TypedDict shapes for `RawEntity` + `_build_module_entity` Files: -- Modify `plugins/python/src/clarion_plugin_python/extractor.py` — add TypedDict imports, define `SourceRange`/`EntitySource`/`RawEntity`, change `extract` return annotation, add `_module_source_range` helper, add `_build_module_entity`. +- Modify `plugins/python/src/loomweave_plugin_python/extractor.py` — add TypedDict imports, define `SourceRange`/`EntitySource`/`RawEntity`, change `extract` return annotation, add `_module_source_range` helper, add `_build_module_entity`. Steps: - Failing test: `test_module_source_range_no_trailing_newline`. @@ -235,7 +235,7 @@ Steps: ### Task 2 — Per-kind builder split + `_build_class_entity` Files: -- Modify `plugins/python/src/clarion_plugin_python/extractor.py` — rename `_build_entity` → `_build_function_entity`; add `_build_class_entity`; switch `_walk` to `match` dispatch; remove `_KIND` module-level constant. +- Modify `plugins/python/src/loomweave_plugin_python/extractor.py` — rename `_build_entity` → `_build_function_entity`; add `_build_class_entity`; switch `_walk` to `match` dispatch; remove `_KIND` module-level constant. Steps: - Failing test: `test_class_entity_simple`. @@ -251,7 +251,7 @@ Steps: Files: - Modify `plugins/python/plugin.toml`. -- Modify `plugins/python/src/clarion_plugin_python/server.py`. +- Modify `plugins/python/src/loomweave_plugin_python/server.py`. Steps: - Update `plugin.toml::[ontology].entity_kinds` to `["function", "class", "module"]`; `ontology_version` to `"0.2.0"`. @@ -266,7 +266,7 @@ Files: Steps: - Add module + class fixture rows. -- Run `cargo nextest run -p clarion-core entity_id::tests::shared_fixture_byte_for_byte_parity`. +- Run `cargo nextest run -p loomweave-core entity_id::tests::shared_fixture_byte_for_byte_parity`. - Run `pytest plugins/python/tests/test_entity_id.py::test_matches_shared_fixture`. - Both green. - Commit: `test(wp3): cross-language fixture parity for class + module entities (B.2)`. @@ -329,8 +329,8 @@ B.2 is done for Sprint 2 when all of: ## 10. Implementation-phase decisions (resolved post-spec-review) -- **`__version__` package bump**: `clarion_plugin_python.__version__` moves from `0.1.0` to `0.1.1` (patch). The package version tracks code releases; the ontology version tracks declared kind set. They are different concepts — no breaking API change ships in B.2, so the patch bump is correct. -- **Top-level `__init__.py` skip — stderr line wording**: `clarion-plugin-python: skipping : top-level __init__.py has no package name\n`. Matches the existing syntax-error skip convention at `extractor.py:103-106` (`clarion-plugin-python: skipping : \n`). +- **`__version__` package bump**: `loomweave_plugin_python.__version__` moves from `0.1.0` to `0.1.1` (patch). The package version tracks code releases; the ontology version tracks declared kind set. They are different concepts — no breaking API change ships in B.2, so the patch bump is correct. +- **Top-level `__init__.py` skip — stderr line wording**: `loomweave-plugin-python: skipping : top-level __init__.py has no package name\n`. Matches the existing syntax-error skip convention at `extractor.py:103-106` (`loomweave-plugin-python: skipping : \n`). - **Lint guard for ontology-version drift**: deferred as a follow-up — filigree [`clarion-8befae708b`](../../../.filigree/) (P3 task; not blocking B.2). B.2 ships with the two values agreeing by inspection; the lint guard is risk insurance against future drift. ## 11. Panel-review record diff --git a/docs/implementation/sprint-2/b3-contains-edges.md b/docs/implementation/sprint-2/b3-contains-edges.md index b6e44619..bb1ea184 100644 --- a/docs/implementation/sprint-2/b3-contains-edges.md +++ b/docs/implementation/sprint-2/b3-contains-edges.md @@ -1,8 +1,8 @@ # B.3 — Python plugin: `contains` edges (Sprint 2 / Tier B / first edge kind) **Status**: DRAFT — Sprint 2 Tier-B B.3 work-package design -**Anchoring design**: [system-design.md §6 (Guidance composition; clustering)](../../clarion/v0.1/system-design.md#6-guidance-system), [detailed-design.md §3 (Schemas, edge tables)](../../clarion/v0.1/detailed-design.md), [B.2 design doc](./b2-class-module-entities.md) (predecessor) -**Accepted ADRs**: [ADR-002](../../clarion/adr/ADR-002-plugin-transport-json-rpc.md), [ADR-003](../../clarion/adr/ADR-003-entity-id-scheme.md), [ADR-006](../../clarion/adr/ADR-006-clustering-algorithm.md), [ADR-007](../../clarion/adr/ADR-007-summary-cache-key.md), [ADR-022](../../clarion/adr/ADR-022-core-plugin-ontology.md), [ADR-023](../../clarion/adr/ADR-023-tooling-baseline.md), [ADR-024](../../clarion/adr/ADR-024-guidance-schema-vocabulary.md), [ADR-026](../../clarion/adr/ADR-026-containment-wire-and-edge-identity.md), [ADR-027](../../clarion/adr/ADR-027-ontology-version-semver.md) +**Anchoring design**: [system-design.md §6 (Guidance composition; clustering)](../../loomweave/v0.1/system-design.md#6-guidance-system), [detailed-design.md §3 (Schemas, edge tables)](../../loomweave/v0.1/detailed-design.md), [B.2 design doc](./b2-class-module-entities.md) (predecessor) +**Accepted ADRs**: [ADR-002](../../loomweave/adr/ADR-002-plugin-transport-json-rpc.md), [ADR-003](../../loomweave/adr/ADR-003-entity-id-scheme.md), [ADR-006](../../loomweave/adr/ADR-006-clustering-algorithm.md), [ADR-007](../../loomweave/adr/ADR-007-summary-cache-key.md), [ADR-022](../../loomweave/adr/ADR-022-core-plugin-ontology.md), [ADR-023](../../loomweave/adr/ADR-023-tooling-baseline.md), [ADR-024](../../loomweave/adr/ADR-024-guidance-schema-vocabulary.md), [ADR-026](../../loomweave/adr/ADR-026-containment-wire-and-edge-identity.md), [ADR-027](../../loomweave/adr/ADR-027-ontology-version-semver.md) **Predecessor**: [B.2 — class + module entity emission](./b2-class-module-entities.md) **Successor**: B.1 (multi-file dispatch — WP4 Phase 0+1) and B.4 (catalog rendering) **Sprint-2 kickoff handoff**: [`docs/superpowers/handoffs/2026-04-30-sprint-2-kickoff.md`](../handoffs/2026-04-30-sprint-2-kickoff.md) §"What's in scope for Sprint 2" Tier B row B.3 @@ -11,7 +11,7 @@ ## 1. Scope -B.3 introduces the first edge kind Clarion has ever persisted. Three things change in lockstep: +B.3 introduces the first edge kind Loomweave has ever persisted. Three things change in lockstep: - **Plugin emits `contains` edges** for every immediate-parent containment relationship (module → top-level function/class, class → method, class → nested class, function → nested function, function → nested class). - **Plugin emits `parent_id` on every entity** (dual encoding with the contains edge for the same fact). The writer-actor enforces consistency. @@ -28,7 +28,7 @@ B.3 introduces the first edge kind Clarion has ever persisted. Three things chan These are caller-observable surfaces locked at `v0.1-sprint-1` close + B.2 close. B.3 must not change them; if a change is genuinely needed, write an ADR amendment per the kickoff-handoff convention. -- **Wire shape entity portion** (`crates/clarion-core/src/plugin/host.rs:132-154`): `RawEntity { id, kind, qualified_name, source: RawSource, extra }` with `#[serde(flatten)] extra`. `RawSource { file_path, extra }` likewise. B.3 adds `parent_id: Option` as a NEW typed field on `RawEntity` (NOT via `extra`) — it's load-bearing for the writer's parent-id consistency check (§3 Q2 below). +- **Wire shape entity portion** (`crates/loomweave-core/src/plugin/host.rs:132-154`): `RawEntity { id, kind, qualified_name, source: RawSource, extra }` with `#[serde(flatten)] extra`. `RawSource { file_path, extra }` likewise. B.3 adds `parent_id: Option` as a NEW typed field on `RawEntity` (NOT via `extra`) — it's load-bearing for the writer's parent-id consistency check (§3 Q2 below). - **L7 qualname format**: `{dotted_module}.{__qualname__}`. Class-in-class chains class names with no ``; function-parent boundary inserts ``. B.3 unchanged. - **L4 JSON-RPC method set**: `initialize`, `initialized`, `analyze_file`, `shutdown`, `exit`. B.3 changes none of these. - **L5 manifest schema**: B.3 amends only `[ontology].edge_kinds` (adds `"contains"`), `[ontology].ontology_version` (`0.2.0` → `0.3.0`), and corresponding constants. No structural change. @@ -83,7 +83,7 @@ Contains edges omit the byte-offset fields entirely (NotRequired absent ⇒ JSON ### Q2 — `parent_id` provenance: dual encoding with writer-actor enforcement -**Decision**: Plugin emits BOTH `parent_id` on `RawEntity` (as a typed top-level field) AND a `contains` edge for the same fact. The writer-actor enforces the consistency invariant on commit: every entity's `parent_id` MUST match exactly one `contains` edge `(kind=contains, from_id=parent_id, to_id=entity.id)`; mismatches reject the run with `CLA-INFRA-PARENT-CONTAINS-MISMATCH`. +**Decision**: Plugin emits BOTH `parent_id` on `RawEntity` (as a typed top-level field) AND a `contains` edge for the same fact. The writer-actor enforces the consistency invariant on commit: every entity's `parent_id` MUST match exactly one `contains` edge `(kind=contains, from_id=parent_id, to_id=entity.id)`; mismatches reject the run with `LMWV-INFRA-PARENT-CONTAINS-MISMATCH`. **Why**: @@ -127,7 +127,7 @@ The full case list: ### Q4 — Edge row identity: drop the `id` column -**Decision**: Drop the `id TEXT PRIMARY KEY` column from `crates/clarion-storage/migrations/0001_initial_schema.sql:66-79`. Promote `(kind, from_id, to_id)` from `UNIQUE` constraint to `PRIMARY KEY`. Add `WITHOUT ROWID` clause for SQLite optimization (the natural PK makes the rowid redundant). +**Decision**: Drop the `id TEXT PRIMARY KEY` column from `crates/loomweave-storage/migrations/0001_initial_schema.sql:66-79`. Promote `(kind, from_id, to_id)` from `UNIQUE` constraint to `PRIMARY KEY`. Add `WITHOUT ROWID` clause for SQLite optimization (the natural PK makes the rowid redundant). **Why**: Panel split 2-2-1; reconciliation favored (c) drop-the-column based on the strongest concrete arguments: @@ -137,7 +137,7 @@ The full case list: **Migration mechanics** (per ADR-024's edit-in-place migration policy): -We're at the zero-cost frontier for the edges table — it has never been written. Edit `migrations/0001_initial_schema.sql` in place rather than authoring a new migration file. ADR-024's retirement trigger (first external operator pulls a published Clarion build) hasn't fired; the in-place edit is permissible. +We're at the zero-cost frontier for the edges table — it has never been written. Edit `migrations/0001_initial_schema.sql` in place rather than authoring a new migration file. ADR-024's retirement trigger (first external operator pulls a published Loomweave build) hasn't fired; the in-place edit is permissible. The schema becomes: @@ -176,7 +176,7 @@ The three indexes survive (they're query-driving, not PK-derived). | `decorates` | MUST be `Some` | | `inherits_from` | MUST be `Some` | -Writer rejects on violation with `CLA-INFRA-EDGE-SOURCE-RANGE-CONTRACT`. +Writer rejects on violation with `LMWV-INFRA-EDGE-SOURCE-RANGE-CONTRACT`. **Why**: Five-reviewer panel unanimous (5×(a) high confidence). Containment is a structural fact derived from the AST; its "location" is identical to the contained entity's source range (already on the entity). Adding line columns to the schema (option b) is scope creep beyond B.3; computing byte offsets in the plugin (option c) requires byte-offset tracking the plugin doesn't otherwise need. The per-kind contract converts the schema's ambient `NULL` permissiveness into a kind-dispatched invariant — consumers can write `assert edge.source_byte_start is not None` for `calls`/`imports` and rely on it. @@ -187,8 +187,8 @@ Writer rejects on violation with `CLA-INFRA-EDGE-SOURCE-RANGE-CONTRACT`. | File | Change | |---|---| | `plugins/python/plugin.toml` | `[ontology].edge_kinds = ["contains"]`; `[ontology].ontology_version = "0.3.0"` | -| `plugins/python/src/clarion_plugin_python/server.py` | `ONTOLOGY_VERSION = "0.3.0"` | -| `plugins/python/src/clarion_plugin_python/__init__.py` | `__version__ = "0.1.2"` (PATCH — no breaking API change; new edge emission is additive via `serde(default)`) | +| `plugins/python/src/loomweave_plugin_python/server.py` | `ONTOLOGY_VERSION = "0.3.0"` | +| `plugins/python/src/loomweave_plugin_python/__init__.py` | `__version__ = "0.1.2"` (PATCH — no breaking API change; new edge emission is additive via `serde(default)`) | ADR-027 is the policy citation: this is a MINOR ontology bump (additive `contains` edge kind). PATCH on package version because the public Python API surface (`extract()` signature) becomes `tuple[list[RawEntity], list[RawEdge]]` — that IS technically a return-type change, but the only consumer is the plugin's own server module which is updated in lockstep, so the patch designation is faithful to "no breaking external API change." @@ -213,7 +213,7 @@ Note: graduating `entities` from `Vec` to `Vec` is a follow-up ## 5. Storage protocol additions -**`WriterCmd::InsertEdge`** new variant in `crates/clarion-storage/src/commands.rs`: +**`WriterCmd::InsertEdge`** new variant in `crates/loomweave-storage/src/commands.rs`: ```rust #[derive(Debug, Clone)] @@ -259,7 +259,7 @@ WHERE e.parent_id IS NOT NULL AND (ce.from_id IS NULL OR ce.from_id != e.parent_id); ``` -Any non-empty result rejects the run with `CLA-INFRA-PARENT-CONTAINS-MISMATCH` and rolls back the in-flight transaction. Inverse check (every contains edge has a matching parent_id): +Any non-empty result rejects the run with `LMWV-INFRA-PARENT-CONTAINS-MISMATCH` and rolls back the in-flight transaction. Inverse check (every contains edge has a matching parent_id): ```sql SELECT ce.from_id, ce.to_id, e.parent_id @@ -279,7 +279,7 @@ The writer-actor exposes a per-run `dropped_edges_total` field on `AnalyzeFileOu - `(kind, from_id, to_id)` UNIQUE conflicts (idempotent re-analyze). - Dangling FK references (edge whose `from_id` or `to_id` is not in the entities table). -- Per-kind source-range contract violations (`CLA-INFRA-EDGE-SOURCE-RANGE-CONTRACT`). +- Per-kind source-range contract violations (`LMWV-INFRA-EDGE-SOURCE-RANGE-CONTRACT`). The walking-skeleton e2e (`tests/e2e/sprint_1_walking_skeleton.sh`) asserts `dropped_edges_total == 0` post-analyze. Without this assertion, "VER-without-VAL" applies: schema is correct, tests pass, catalog is wrong because edges silently dropped. @@ -288,8 +288,8 @@ The walking-skeleton e2e (`tests/e2e/sprint_1_walking_skeleton.sh`) asserts `dro ### Task 1 — Storage schema migration (drop edges.id, add WITHOUT ROWID) Files: -- Modify: `crates/clarion-storage/migrations/0001_initial_schema.sql` (in-place edit per ADR-024 / ADR-026 retirement rule). -- Modify: `crates/clarion-storage/tests/schema_apply.rs` (any test asserting on `edges.id` column). +- Modify: `crates/loomweave-storage/migrations/0001_initial_schema.sql` (in-place edit per ADR-024 / ADR-026 retirement rule). +- Modify: `crates/loomweave-storage/tests/schema_apply.rs` (any test asserting on `edges.id` column). Steps: - Failing test: schema_apply test asserting edges has primary key `(kind, from_id, to_id)` and no `id` column. @@ -302,9 +302,9 @@ Steps: ### Task 2 — `WriterCmd::InsertEdge` + `EdgeRecord` + writer-actor integration Files: -- Modify: `crates/clarion-storage/src/commands.rs` (add `EdgeRecord`, add `WriterCmd::InsertEdge`). -- Modify: `crates/clarion-storage/src/writer.rs` (add INSERT logic; rename `inserts_in_batch` → `writes_in_batch`; per-kind source-range contract enforcement; dropped-edge counter). -- Modify: `crates/clarion-storage/tests/writer_actor.rs` (round-trip insert tests; per-kind contract violation tests; dropped-edge counter tests). +- Modify: `crates/loomweave-storage/src/commands.rs` (add `EdgeRecord`, add `WriterCmd::InsertEdge`). +- Modify: `crates/loomweave-storage/src/writer.rs` (add INSERT logic; rename `inserts_in_batch` → `writes_in_batch`; per-kind source-range contract enforcement; dropped-edge counter). +- Modify: `crates/loomweave-storage/tests/writer_actor.rs` (round-trip insert tests; per-kind contract violation tests; dropped-edge counter tests). Steps: - Failing test: writer-actor inserts a contains edge, asserts row visible to a reader. @@ -320,22 +320,22 @@ Steps: ### Task 3 — Host wire shape: `RawEdge` + `RawEntity.parent_id` + `AnalyzeFileResult.edges` Files: -- Modify: `crates/clarion-core/src/plugin/protocol.rs` (add `RawEdge` struct; add `edges: Vec` to `AnalyzeFileResult`; add `parent_id: Option` to `RawEntity` if/when it graduates from `Vec` — Sprint-1 `protocol.rs:328` notes this is Task 6 graduation work). -- Modify: `crates/clarion-core/src/plugin/host.rs` (deserialise `edges` from `AnalyzeFileResult`; pass through to writer; derive `source_file_id` from module entity). -- Modify: `crates/clarion-core/tests/host_subprocess.rs` (test edge round-trip). +- Modify: `crates/loomweave-core/src/plugin/protocol.rs` (add `RawEdge` struct; add `edges: Vec` to `AnalyzeFileResult`; add `parent_id: Option` to `RawEntity` if/when it graduates from `Vec` — Sprint-1 `protocol.rs:328` notes this is Task 6 graduation work). +- Modify: `crates/loomweave-core/src/plugin/host.rs` (deserialise `edges` from `AnalyzeFileResult`; pass through to writer; derive `source_file_id` from module entity). +- Modify: `crates/loomweave-core/tests/host_subprocess.rs` (test edge round-trip). Steps: - Failing test: a fixture plugin returns one entity and one contains edge; host deserialises both; storage persists both. - Implement minimal `RawEdge` deserialise + pass-through. -- Failing test: parent-id consistency check (entity declares parent_id but no matching contains edge → run rejected with `CLA-INFRA-PARENT-CONTAINS-MISMATCH`). +- Failing test: parent-id consistency check (entity declares parent_id but no matching contains edge → run rejected with `LMWV-INFRA-PARENT-CONTAINS-MISMATCH`). - Implement consistency check at commit time. - Commit: `feat(host): RawEdge wire shape + parent_id consistency check (B.3)`. ### Task 4 — Python plugin: emit `contains` edges + `parent_id` on entities Files: -- Modify: `plugins/python/src/clarion_plugin_python/extractor.py` (add `RawEdge` TypedDict; change `extract()` return to `tuple[list[RawEntity], list[RawEdge]]`; per-kind builders return `tuple[RawEntity, str]`; `_walk` accumulates dual lists; module entity `parent_id` is None — module is the root within-file). -- Modify: `plugins/python/src/clarion_plugin_python/server.py` (update `handle_analyze_file` to pass both entities and edges through to the response). +- Modify: `plugins/python/src/loomweave_plugin_python/extractor.py` (add `RawEdge` TypedDict; change `extract()` return to `tuple[list[RawEntity], list[RawEdge]]`; per-kind builders return `tuple[RawEntity, str]`; `_walk` accumulates dual lists; module entity `parent_id` is None — module is the root within-file). +- Modify: `plugins/python/src/loomweave_plugin_python/server.py` (update `handle_analyze_file` to pass both entities and edges through to the response). - Modify: `plugins/python/tests/test_extractor.py` (new tests for edge emission and parent_id). Steps: @@ -353,8 +353,8 @@ Steps: Files: - Modify: `plugins/python/plugin.toml` (`[ontology].edge_kinds = ["contains"]`; `[ontology].ontology_version = "0.3.0"`). -- Modify: `plugins/python/src/clarion_plugin_python/server.py` (`ONTOLOGY_VERSION = "0.3.0"`). -- Modify: `plugins/python/src/clarion_plugin_python/__init__.py` (`__version__ = "0.1.2"`). +- Modify: `plugins/python/src/loomweave_plugin_python/server.py` (`ONTOLOGY_VERSION = "0.3.0"`). +- Modify: `plugins/python/src/loomweave_plugin_python/__init__.py` (`__version__ = "0.1.2"`). - Modify: `plugins/python/tests/test_server.py` and `plugins/python/tests/test_package.py` (update version-string assertions). Steps: @@ -415,7 +415,7 @@ B.3 is done for Sprint 2 when ALL of: - Cross-language fixture parity passes on Rust + Python sides. - Walking-skeleton e2e PASSES with `dropped_edges_total == 0`. - All ADR-023 gates green on the closing commit. -- ADR-026 + ADR-027 in Accepted state, indexed in `docs/clarion/adr/README.md`. +- ADR-026 + ADR-027 in Accepted state, indexed in `docs/loomweave/adr/README.md`. ## 10. Open questions for the implementation phase (lower stakes) @@ -431,7 +431,7 @@ Six design questions taken to a five-reviewer panel (systems-thinker, solution-a | Q | Decision | Vote pattern | Reconciliation | |---|---|---|---| | Q1 | (a) top-level `edges` field on `AnalyzeFileResult` | 5×(a) high confidence | unanimous; cross-file resolution explicitly documented in ADR-026 | -| Q2 | (a) dual-encoding parent_id + contains edge WITH writer-actor enforcement | 3×(a), 2×(b) split | reconciled to (a)+enforcement per architecture-critic's `CLA-INFRA-PARENT-CONTAINS-MISMATCH` requirement | +| Q2 | (a) dual-encoding parent_id + contains edge WITH writer-actor enforcement | 3×(a), 2×(b) split | reconciled to (a)+enforcement per architecture-critic's `LMWV-INFRA-PARENT-CONTAINS-MISMATCH` requirement | | Q3 | (a) all immediate-parent containments | 5×(a) high confidence | unanimous; non-goal locked: "emitter is exhaustive; renderer-side filtering owns presentation" | | Q4 | (c) drop `edges.id` column | 2×(b), 2×(c), 1×(b)+ADR | reconciled to (c) per architecture-critic + Python/Rust engineer concrete byte-cost arguments + no-current-reader | | Q5 | (a) None — emit nothing for contains source range | 5×(a) high confidence | unanimous; per-kind contract documented in ADR-026 decision 3 | diff --git a/docs/implementation/sprint-2/b4-calls-edges.md b/docs/implementation/sprint-2/b4-calls-edges.md index 0c02657b..9db311b9 100644 --- a/docs/implementation/sprint-2/b4-calls-edges.md +++ b/docs/implementation/sprint-2/b4-calls-edges.md @@ -1,8 +1,8 @@ # B.4* — Python plugin: `calls` edges via pyright + confidence tiers (Sprint 2 amended / Tier B) **Status**: IMPLEMENTED — Sprint 2 amended Tier-B B.4* work-package design closed; see §9 exit criteria and [B.4* gate results](./b4-gate-results.md) -**Anchoring design**: [system-design.md §4 (Plugin host / analyze pipeline)](../../clarion/v0.1/system-design.md), [detailed-design.md §"Python plugin specifics — call graph precision"](../../clarion/v0.1/detailed-design.md), [scope-amendment-2026-05.md](./scope-amendment-2026-05.md) -**Accepted ADRs**: [ADR-002](../../clarion/adr/ADR-002-plugin-transport-json-rpc.md), [ADR-003](../../clarion/adr/ADR-003-entity-id-scheme.md), [ADR-007](../../clarion/adr/ADR-007-summary-cache-key.md), [ADR-022](../../clarion/adr/ADR-022-core-plugin-ontology.md), [ADR-023](../../clarion/adr/ADR-023-tooling-baseline.md), [ADR-024](../../clarion/adr/ADR-024-guidance-schema-vocabulary.md), [ADR-026](../../clarion/adr/ADR-026-containment-wire-and-edge-identity.md), [ADR-027](../../clarion/adr/ADR-027-ontology-version-semver.md), [ADR-028](../../clarion/adr/ADR-028-edge-confidence-tiers.md) +**Anchoring design**: [system-design.md §4 (Plugin host / analyze pipeline)](../../loomweave/v0.1/system-design.md), [detailed-design.md §"Python plugin specifics — call graph precision"](../../loomweave/v0.1/detailed-design.md), [scope-amendment-2026-05.md](./scope-amendment-2026-05.md) +**Accepted ADRs**: [ADR-002](../../loomweave/adr/ADR-002-plugin-transport-json-rpc.md), [ADR-003](../../loomweave/adr/ADR-003-entity-id-scheme.md), [ADR-007](../../loomweave/adr/ADR-007-summary-cache-key.md), [ADR-022](../../loomweave/adr/ADR-022-core-plugin-ontology.md), [ADR-023](../../loomweave/adr/ADR-023-tooling-baseline.md), [ADR-024](../../loomweave/adr/ADR-024-guidance-schema-vocabulary.md), [ADR-026](../../loomweave/adr/ADR-026-containment-wire-and-edge-identity.md), [ADR-027](../../loomweave/adr/ADR-027-ontology-version-semver.md), [ADR-028](../../loomweave/adr/ADR-028-edge-confidence-tiers.md) **Predecessor**: [B.3 — `contains` edges](./b3-contains-edges.md) **Successor**: B.5* (`references` edges), B.6 (WP8 MCP surface) **Filigree umbrella**: `clarion-2d2d1d27b5` @@ -65,7 +65,7 @@ Three viable strategies; each implies a different process model, JSON-RPC traffi - Pyright's `callHierarchy/incomingCalls` and `outgoingCalls` are the entry points pyright's maintainers shipped for exactly this consumer. (b) re-implements a piece of pyright (its call-site walker) inside the plugin; (c) takes on `pycg` whose last release was 2022 and whose Python-version coverage is shaky against elspeth's tooling envelope. - The week-2 gate (§5) measures *pyright's cost at elspeth scale*. That cost dominates regardless of (a)/(b) — pyright still types the project. (a) inherits pyright's incrementality and project caching; (b) re-pays project init cost in pyright on every type-resolution request unless we hold the LSP subprocess open anyway (at which point (b) is just (a) plus an extra AST walk in the plugin). -- (b) and (c) put *call-pattern recognition* — knowing what "Protocol dispatch" or "dict-of-callables" or "decorated function" looks like — into the plugin. Pyright already knows these. Reimplementing them in the plugin is the kind of "lightweight glue" the Loom federation axiom warns against (cross-product reasoning collapse, not federation). +- (b) and (c) put *call-pattern recognition* — knowing what "Protocol dispatch" or "dict-of-callables" or "decorated function" looks like — into the plugin. Pyright already knows these. Reimplementing them in the plugin is the kind of "lightweight glue" the Weft federation axiom warns against (cross-product reasoning collapse, not federation). - LSP serialization overhead is real but bounded: typical call-hierarchy responses are kilobytes; per-file query counts are bounded by call-site count, which is bounded by file size. At elspeth's ~425k LOC, this is millions of LSP messages, not billions. **Process model** (panel-revised): @@ -74,15 +74,15 @@ Three viable strategies; each implies a different process model, JSON-RPC traffi - The subprocess is `pyright-langserver --stdio`; the plugin speaks JSON-RPC 2.0 framed with `Content-Length` headers (same framing as the plugin's own protocol — convenient symmetry). - **Traffic shape** (panel correction from python-engineering): per `analyze_file`, the plugin: (1) sends `textDocument/didOpen` for the file, (2) walks the AST for *function entities* (not individual call sites), (3) for each function entity, issues `textDocument/prepareCallHierarchy` once + `callHierarchy/outgoingCalls` once — pyright returns *all* outgoing call sites from that function in a single response, including byte ranges, (4) maps response ranges back to AST call-site nodes for byte-offset population, tier-tags edges (resolved if exactly one in-project target; ambiguous if N>1; emitted-nothing if zero), (5) sends `textDocument/didClose`. The cost envelope is `functions_per_file × roundtrip_latency`, not `call_sites_per_file × roundtrip_latency` — material for the week-2 gate math (Q7). - **Per-session LSP subprocess lifecycle**: started lazily, kept alive until `shutdown`. Stderr is drained by a daemon thread started before the first LSP write; the drain thread is joined in `PyrightSession.close()` after `process.wait()`. -- **Init-hang deadline** (panel addition): the `initialize` handshake to pyright is bounded by `PYRIGHT_INIT_TIMEOUT_SECS` (default 30s). On timeout, the plugin emits `CLA-PY-PYRIGHT-INIT-TIMEOUT` and treats pyright as unavailable for the run (no `calls` edges; `unresolved_call_sites_total` += AST-counted call sites). Distinct from per-call-site timeout (5s, also bounded). -- **Crash + restart with per-run cap** (panel addition): if pyright crashes mid-session, restart count surfaces as `CLA-PY-PYRIGHT-RESTART`. Restarts capped at `MAX_PYRIGHT_RESTARTS_PER_RUN` (default 3). On cap exceeded, the *currently-analysing file* is skipped with `CLA-PY-PYRIGHT-POISON-FRAME`; subsequent files in the same run continue without pyright (treated as unavailable). This prevents a single deterministic-crash file from stalling the entire run indefinitely. -- **Init-failure (binary missing / bootstrap incomplete)** (panel addition): if pyright cannot be invoked at all (binary missing, node bootstrap failed, version negotiation failed), the plugin emits `CLA-PY-PYRIGHT-UNAVAILABLE` and continues with zero `calls` edges + `unresolved_call_sites_total` += AST-counted call sites. The run completes; the operator sees the finding. +- **Init-hang deadline** (panel addition): the `initialize` handshake to pyright is bounded by `PYRIGHT_INIT_TIMEOUT_SECS` (default 30s). On timeout, the plugin emits `LMWV-PY-PYRIGHT-INIT-TIMEOUT` and treats pyright as unavailable for the run (no `calls` edges; `unresolved_call_sites_total` += AST-counted call sites). Distinct from per-call-site timeout (5s, also bounded). +- **Crash + restart with per-run cap** (panel addition): if pyright crashes mid-session, restart count surfaces as `LMWV-PY-PYRIGHT-RESTART`. Restarts capped at `MAX_PYRIGHT_RESTARTS_PER_RUN` (default 3). On cap exceeded, the *currently-analysing file* is skipped with `LMWV-PY-PYRIGHT-POISON-FRAME`; subsequent files in the same run continue without pyright (treated as unavailable). This prevents a single deterministic-crash file from stalling the entire run indefinitely. +- **Init-failure (binary missing / bootstrap incomplete)** (panel addition): if pyright cannot be invoked at all (binary missing, node bootstrap failed, version negotiation failed), the plugin emits `LMWV-PY-PYRIGHT-UNAVAILABLE` and continues with zero `calls` edges + `unresolved_call_sites_total` += AST-counted call sites. The run completes; the operator sees the finding. ### Q2 — Pyright provisioning The pyright binary must be present in the plugin's runtime environment. Three options: -- **(a) Pinned `pyright` PyPI package as a `[project.dependencies]` entry**: `pip install clarion-plugin-python` brings pyright in; pyright's bootstrapper downloads node + the LSP on first run. +- **(a) Pinned `pyright` PyPI package as a `[project.dependencies]` entry**: `pip install loomweave-plugin-python` brings pyright in; pyright's bootstrapper downloads node + the LSP on first run. - **(b) Operator pre-installs pyright separately**: plugin assumes a working `pyright-langserver` on PATH or in a configurable location; refuses to start without it. - **(c) Bundle pyright with the plugin distribution**: bake into a wheel or supplementary archive; no first-run network access. @@ -100,17 +100,17 @@ The pyright binary must be present in the plugin's runtime environment. Three op ```toml [capabilities.runtime.pyright] -# Pyright version the plugin was tested against. Host surfaces a CLA-PY-PYRIGHT-VERSION-DRIFT +# Pyright version the plugin was tested against. Host surfaces a LMWV-PY-PYRIGHT-VERSION-DRIFT # finding (warning, not fatal) if `pyright --version` at runtime differs from this pin. pin = "1.1.X" # exact version set at B.4* impl start ``` **First-run hardening** (panel additions): -- After `pip install`, the `pyright` PyPI package downloads node + the LSP binary on *first invocation*, not at install time. The plugin checks `pyright-langserver --version` executability before the first LSP write. On failure (binary absent, node bootstrap failed, network egress restricted), the plugin emits `CLA-PY-PYRIGHT-INSTALL-FAILURE` and treats pyright as unavailable (Q1 init-failure path). +- After `pip install`, the `pyright` PyPI package downloads node + the LSP binary on *first invocation*, not at install time. The plugin checks `pyright-langserver --version` executability before the first LSP write. On failure (binary absent, node bootstrap failed, network egress restricted), the plugin emits `LMWV-PY-PYRIGHT-INSTALL-FAILURE` and treats pyright as unavailable (Q1 init-failure path). - The pyright subprocess is launched with an explicit `env` (passing through `PATH` and `HOME` only, plus the pyright wrapper's expected cache directory `~/.cache/pyright-python/`) so a system-installed `pyright` on `PATH` cannot shadow the managed binary. - CI gains a cache key for `~/.cache/pyright-python/` to avoid re-downloading node on every cold run. -- Dependabot is configured to ignore the `pyright` package (or, if dependabot is not configured, a TODO comment beside the `pyproject.toml` pin documents the manual-bump policy). This prevents auto-bumps from silently breaking the two-source pin parity (`pyproject.toml` + `plugin.toml`) that `CLA-PY-PYRIGHT-VERSION-DRIFT` is designed to catch. +- Dependabot is configured to ignore the `pyright` package (or, if dependabot is not configured, a TODO comment beside the `pyproject.toml` pin documents the manual-bump policy). This prevents auto-bumps from silently breaking the two-source pin parity (`pyproject.toml` + `plugin.toml`) that `LMWV-PY-PYRIGHT-VERSION-DRIFT` is designed to catch. ### Q3 — `properties.candidates` shape for ambiguous edges @@ -149,22 +149,22 @@ The writer must enforce that confidence tiers obey the per-kind invariant from A **Decision** (panel-revised — substantially simpler than the draft): -Extend the *existing* `enforce_edge_contract` function at `crates/clarion-storage/src/writer.rs:343` with a confidence check; do NOT add a parallel `enforce_edge_confidence_contract`. The existing function already dispatches by kind class and emits `CLA-INFRA-EDGE-UNKNOWN-KIND` for unknown kinds; the new confidence check folds into the same per-kind match arms: +Extend the *existing* `enforce_edge_contract` function at `crates/loomweave-storage/src/writer.rs:343` with a confidence check; do NOT add a parallel `enforce_edge_confidence_contract`. The existing function already dispatches by kind class and emits `LMWV-INFRA-EDGE-UNKNOWN-KIND` for unknown kinds; the new confidence check folds into the same per-kind match arms: ```rust // Inside the existing enforce_edge_contract — new arms folded in: match (kind_class, edge.confidence) { (EdgeKindClass::Structural, EdgeConfidence::Resolved) => { /* fall through to existing source-range check */ } (EdgeKindClass::Structural, _) => return Err(violation( - "CLA-INFRA-EDGE-CONFIDENCE-CONTRACT", + "LMWV-INFRA-EDGE-CONFIDENCE-CONTRACT", "structural edge must carry confidence=resolved", )), (EdgeKindClass::Anchored, EdgeConfidence::Inferred) => return Err(violation( - "CLA-INFRA-EDGE-CONFIDENCE-CONTRACT", + "LMWV-INFRA-EDGE-CONFIDENCE-CONTRACT", "inferred-tier edges are query-time-only at scan time", )), (EdgeKindClass::Anchored, _) => { /* fall through to existing source-range check */ } - (EdgeKindClass::Unknown, _) => { /* existing arm emits CLA-INFRA-EDGE-UNKNOWN-KIND */ } + (EdgeKindClass::Unknown, _) => { /* existing arm emits LMWV-INFRA-EDGE-UNKNOWN-KIND */ } } ``` @@ -182,16 +182,16 @@ The draft introduced a `WriteOrigin::{Scan, Query}` enum on `WriterCmd::InsertEd - Single-implementer rule. Don't ship an abstraction with one consumer. - The check is now strictly local to scan-time — no scan-vs-query coupling. -- Violations still increment `dropped_edges_total` AND surface as findings (matches B.3's `CLA-INFRA-EDGE-SOURCE-RANGE-CONTRACT` pattern). +- Violations still increment `dropped_edges_total` AND surface as findings (matches B.3's `LMWV-INFRA-EDGE-SOURCE-RANGE-CONTRACT` pattern). - The check runs per-edge, not at batch commit time — fail fast on the first malformed edge rather than aggregating to "20 edges dropped, here's a sample." **Test coverage required for the contract** (quality-reviewer additions — all of these are required in Task 2's RED tests): -- `(contains, Ambiguous)` → rejected with `CLA-INFRA-EDGE-CONFIDENCE-CONTRACT`; assert `dropped_edges_total == 1`; assert finding list contains the named code. +- `(contains, Ambiguous)` → rejected with `LMWV-INFRA-EDGE-CONFIDENCE-CONTRACT`; assert `dropped_edges_total == 1`; assert finding list contains the named code. - `(contains, Inferred)` → rejected; same assertions (force-traverses the structural-with-non-resolved arm a second way). - `(in_subsystem, Inferred)` → rejected (force-coverage of a *second* structural kind, not just `contains`). -- `(calls, Inferred)` → rejected with `CLA-INFRA-EDGE-CONFIDENCE-CONTRACT`; assert counter + finding. -- `(unknown_kind, Resolved)` → rejected with `CLA-INFRA-EDGE-UNKNOWN-KIND`; assert counter + finding (currently-untested arm of the existing function). +- `(calls, Inferred)` → rejected with `LMWV-INFRA-EDGE-CONFIDENCE-CONTRACT`; assert counter + finding. +- `(unknown_kind, Resolved)` → rejected with `LMWV-INFRA-EDGE-UNKNOWN-KIND`; assert counter + finding (currently-untested arm of the existing function). - `(calls, Ambiguous)` → ACCEPTED; assert row inserted; assert `ambiguous_edges_total == 1`. - `(calls, Resolved)` → ACCEPTED; assert row inserted; counters untouched. @@ -228,8 +228,8 @@ Mechanical, per ADR-027 precedent (mirrors B.3 Q6 exactly). | File | Change | |---|---| | `plugins/python/plugin.toml` | `[ontology].edge_kinds = ["contains", "calls"]`; `[ontology].ontology_version = "0.4.0"`; NEW `[capabilities.runtime.pyright]` sub-table w/ `pin` (Q2) | -| `plugins/python/src/clarion_plugin_python/server.py` | `ONTOLOGY_VERSION = "0.4.0"` | -| `plugins/python/src/clarion_plugin_python/__init__.py` | `__version__` PATCH bump (e.g., `0.1.2` → `0.1.3`) — additive edge kind, no breaking external API | +| `plugins/python/src/loomweave_plugin_python/server.py` | `ONTOLOGY_VERSION = "0.4.0"` | +| `plugins/python/src/loomweave_plugin_python/__init__.py` | `__version__` PATCH bump (e.g., `0.1.2` → `0.1.3`) — additive edge kind, no breaking external API | | `plugins/python/pyproject.toml` | Add `pyright == X.Y.Z` to `[project.dependencies]` (Q2's pinned version) | ADR-027 policy: MINOR ontology bump (additive `calls` edge kind), PATCH package bump (no API change to `extract()` signature — the return type is already `tuple[list[RawEntity], list[RawEdge]]` after B.3). @@ -255,7 +255,7 @@ The scope-amendment memo §5 names the gate but does not specify the test corpus Required fields per entry: - Date, calibration machine, `OPERATOR_HARDWARE_RATIO` (1.0 if reference machine). -- `pyright_pin` (verbatim from `plugin.toml`); `clarion_commit` (sha). +- `pyright_pin` (verbatim from `plugin.toml`); `loomweave_commit` (sha). - Total wall-clock; breakdown: pyright init, per-file resolution, parent-walk overhead, CLI overhead. - Median + p95 per-file resolution time, per-file roundtrip count (pyright LSP `outgoingCalls` requests), ambiguous-edge ratio. - Corpus stats: file-count, function-count (drives the per-function-entity LSP traffic, Q1), unresolved-call-site-count. @@ -357,7 +357,7 @@ pub struct EdgeRecord { **Writer-actor enforcement** (Q4, panel-revised): -- The existing `enforce_edge_contract` function at `crates/clarion-storage/src/writer.rs:343` is *extended in place* (not replaced or paralleled). New arms cover: (1) structural-with-non-Resolved → reject with `CLA-INFRA-EDGE-CONFIDENCE-CONTRACT`, (2) anchored-with-Inferred → reject with the same code. Existing arms for source-range contract + unknown-kind remain. +- The existing `enforce_edge_contract` function at `crates/loomweave-storage/src/writer.rs:343` is *extended in place* (not replaced or paralleled). New arms cover: (1) structural-with-non-Resolved → reject with `LMWV-INFRA-EDGE-CONFIDENCE-CONTRACT`, (2) anchored-with-Inferred → reject with the same code. Existing arms for source-range contract + unknown-kind remain. - Violations fail the edge (counter increments, finding emitted, run continues — single-edge failure does NOT fail the run, matching B.3's UNIQUE-conflict semantics). - The check runs per-edge (not batched at CommitRun) — failing fast on the first malformed edge surfaces the bug to the plugin author at exactly the failing site rather than aggregating to "20 edges dropped, here's a sample." - The `ix_edges_kind_confidence` index MUST be exercised by an `EXPLAIN QUERY PLAN` test asserting it's used for `WHERE kind=? AND confidence=?` predicates. Without this, B.6's `callers_of` default (`confidence='resolved'`) silently degrades to a full scan. @@ -366,17 +366,17 @@ pub struct EdgeRecord { | Code | Severity | Emitted when | |---|---|---| -| `CLA-INFRA-EDGE-CONFIDENCE-CONTRACT` | error | structural edge has confidence != resolved, OR scan-time anchored edge has confidence == inferred | -| `CLA-PY-PYRIGHT-RESTART` | warning | pyright subprocess died and was restarted; carries restart count + last-seen exit code | -| `CLA-PY-PYRIGHT-POISON-FRAME` | warning | per-run restart cap exceeded; file currently being analyzed is skipped; subsequent files in this run continue without pyright | -| `CLA-PY-PYRIGHT-INIT-TIMEOUT` | warning | pyright `initialize` handshake exceeded `PYRIGHT_INIT_TIMEOUT_SECS` (default 30s); pyright treated as unavailable for the run | -| `CLA-PY-PYRIGHT-UNAVAILABLE` | warning | pyright cannot be invoked (binary missing, node bootstrap failed); run continues with zero `calls` edges | -| `CLA-PY-PYRIGHT-INSTALL-FAILURE` | warning | pyright-langserver executability check failed before first LSP write (distinct from `-UNAVAILABLE` — fires at install-bootstrap-time only) | -| `CLA-PY-PYRIGHT-VERSION-DRIFT` | warning | `pyright --version` at runtime differs from `[capabilities.runtime.pyright].pin` | -| `CLA-PY-CALL-RESOLUTION-TIMEOUT` | warning | per-call-site LSP query exceeded configured deadline (default 5s); edge omitted | -| `CLA-PY-CALL-SITE-UNRESOLVED` | info | per-call-site unresolved (no in-project candidates); emitted optionally — see Q4-handoff in §10 / B.6 design (decision deferred for now: counter is required, finding-per-site is an open question for B.6) | - -The first one belongs to clarion-core (the writer-actor); the rest belong to the Python plugin (prefix `CLA-PY-` per ADR-022). +| `LMWV-INFRA-EDGE-CONFIDENCE-CONTRACT` | error | structural edge has confidence != resolved, OR scan-time anchored edge has confidence == inferred | +| `LMWV-PY-PYRIGHT-RESTART` | warning | pyright subprocess died and was restarted; carries restart count + last-seen exit code | +| `LMWV-PY-PYRIGHT-POISON-FRAME` | warning | per-run restart cap exceeded; file currently being analyzed is skipped; subsequent files in this run continue without pyright | +| `LMWV-PY-PYRIGHT-INIT-TIMEOUT` | warning | pyright `initialize` handshake exceeded `PYRIGHT_INIT_TIMEOUT_SECS` (default 30s); pyright treated as unavailable for the run | +| `LMWV-PY-PYRIGHT-UNAVAILABLE` | warning | pyright cannot be invoked (binary missing, node bootstrap failed); run continues with zero `calls` edges | +| `LMWV-PY-PYRIGHT-INSTALL-FAILURE` | warning | pyright-langserver executability check failed before first LSP write (distinct from `-UNAVAILABLE` — fires at install-bootstrap-time only) | +| `LMWV-PY-PYRIGHT-VERSION-DRIFT` | warning | `pyright --version` at runtime differs from `[capabilities.runtime.pyright].pin` | +| `LMWV-PY-CALL-RESOLUTION-TIMEOUT` | warning | per-call-site LSP query exceeded configured deadline (default 5s); edge omitted | +| `LMWV-PY-CALL-SITE-UNRESOLVED` | info | per-call-site unresolved (no in-project candidates); emitted optionally — see Q4-handoff in §10 / B.6 design (decision deferred for now: counter is required, finding-per-site is an open question for B.6) | + +The first one belongs to loomweave-core (the writer-actor); the rest belong to the Python plugin (prefix `LMWV-PY-` per ADR-022). ## 6. Observability additions @@ -394,7 +394,7 @@ The first one belongs to clarion-core (the writer-actor); the rest belong to the **Counter-vs-finding-per-site decision for B.6 (panel-flagged handoff)**: the counter is a run-level signal. For B.6's `callers_of(entity_id)` tool to know *which* entities have unresolved sites worth querying at `confidence>=Inferred`, an entity-scoped signal is needed. Three options for B.6's design: -- (a) Per-site finding (`CLA-PY-CALL-SITE-UNRESOLVED`, info severity) — entity-anchored, queryable via `findings.entity_id`. Risk: at elspeth-full scale tens of thousands of findings flood Filigree. +- (a) Per-site finding (`LMWV-PY-CALL-SITE-UNRESOLVED`, info severity) — entity-anchored, queryable via `findings.entity_id`. Risk: at elspeth-full scale tens of thousands of findings flood Filigree. - (b) Per-entity counter row in a sibling table (`entity_unresolved_call_sites`) — one row per entity that has any unresolved sites, with a count. - (c) Synthesized "edge-with-zero-candidates" rows in the `edges` table (kind=`calls`, confidence=`inferred`, properties.unresolved=true) — fits the existing schema, queryable via existing indexes, but stretches the semantic of "edge" (it's the absence of an edge). @@ -407,8 +407,8 @@ B.4* makes the per-run counter required; B.6's design pass MUST resolve this que ### Task 1 — Storage schema: add `confidence` column + index Files: -- Modify: `crates/clarion-storage/migrations/0001_initial_schema.sql` (in-place edit per ADR-024 + ADR-028 last-edit clause). -- Modify: `crates/clarion-storage/tests/schema_apply.rs` (assert `confidence` column exists, CHECK constraint enforced, index present). +- Modify: `crates/loomweave-storage/migrations/0001_initial_schema.sql` (in-place edit per ADR-024 + ADR-028 last-edit clause). +- Modify: `crates/loomweave-storage/tests/schema_apply.rs` (assert `confidence` column exists, CHECK constraint enforced, index present). Steps: - RED: schema_apply test asserts a row with `confidence='garbage'` is rejected. @@ -420,18 +420,18 @@ Steps: ### Task 2 — `EdgeRecord.confidence` + extend existing `enforce_edge_contract` (panel-revised) Files: -- Modify: `crates/clarion-storage/src/commands.rs` (add `confidence: EdgeConfidence` to `EdgeRecord`). NOTE: `WriteOrigin` enum is DEFERRED to B.6 per Q4 panel reversal — `WriterCmd::InsertEdge` shape unchanged. -- Modify: `crates/clarion-storage/src/writer.rs` (extend the EXISTING `enforce_edge_contract` function at writer.rs:343 — do not add a parallel function; fold confidence arms into the existing per-kind match; extend `dropped_edges_total` increment to cover the new rejection cause; emit `CLA-INFRA-EDGE-CONFIDENCE-CONTRACT` finding on violation; ensure `ix_edges_kind_confidence` index hint surfaces via `EXPLAIN QUERY PLAN`). -- Modify: `crates/clarion-storage/tests/writer_actor.rs` (full contract-test matrix; see RED steps). -- Modify: `crates/clarion-storage/tests/writer_actor.rs::make_contains_edge` helper (add `confidence: EdgeConfidence` arg, default `Resolved`). -- Add: `crates/clarion-storage/tests/schema_apply.rs` EXPLAIN QUERY PLAN assertion for `WHERE kind=? AND confidence=?` using `ix_edges_kind_confidence`. +- Modify: `crates/loomweave-storage/src/commands.rs` (add `confidence: EdgeConfidence` to `EdgeRecord`). NOTE: `WriteOrigin` enum is DEFERRED to B.6 per Q4 panel reversal — `WriterCmd::InsertEdge` shape unchanged. +- Modify: `crates/loomweave-storage/src/writer.rs` (extend the EXISTING `enforce_edge_contract` function at writer.rs:343 — do not add a parallel function; fold confidence arms into the existing per-kind match; extend `dropped_edges_total` increment to cover the new rejection cause; emit `LMWV-INFRA-EDGE-CONFIDENCE-CONTRACT` finding on violation; ensure `ix_edges_kind_confidence` index hint surfaces via `EXPLAIN QUERY PLAN`). +- Modify: `crates/loomweave-storage/tests/writer_actor.rs` (full contract-test matrix; see RED steps). +- Modify: `crates/loomweave-storage/tests/writer_actor.rs::make_contains_edge` helper (add `confidence: EdgeConfidence` arg, default `Resolved`). +- Add: `crates/loomweave-storage/tests/schema_apply.rs` EXPLAIN QUERY PLAN assertion for `WHERE kind=? AND confidence=?` using `ix_edges_kind_confidence`. Steps (each RED requires the assertion to fail for the right reason, per TDD discipline): -- RED-1: `(contains, Ambiguous)` rejected — assert: edge rejected, `dropped_edges_total == 1`, finding list contains `CLA-INFRA-EDGE-CONFIDENCE-CONTRACT`. +- RED-1: `(contains, Ambiguous)` rejected — assert: edge rejected, `dropped_edges_total == 1`, finding list contains `LMWV-INFRA-EDGE-CONFIDENCE-CONTRACT`. - RED-2: `(contains, Inferred)` rejected — same assertions (covers the structural arm a second way). - RED-3: `(in_subsystem, Inferred)` rejected — covers a second *structural kind* beyond `contains`. - RED-4: `(calls, Inferred)` rejected with same assertions. -- RED-5: `(unknown_kind, Resolved)` rejected with `CLA-INFRA-EDGE-UNKNOWN-KIND`; counter + finding asserted (this arm exists in the current function but is untested). +- RED-5: `(unknown_kind, Resolved)` rejected with `LMWV-INFRA-EDGE-UNKNOWN-KIND`; counter + finding asserted (this arm exists in the current function but is untested). - RED-6: `(calls, Ambiguous)` ACCEPTED; row inserted; assert `ambiguous_edges_total == 1`. - RED-7: `(calls, Resolved)` ACCEPTED; row inserted; counters untouched. - RED-8: EXPLAIN QUERY PLAN on `SELECT * FROM edges WHERE kind=? AND confidence=?` returns a plan that uses `ix_edges_kind_confidence` (not a full scan). @@ -441,9 +441,9 @@ Steps (each RED requires the assertion to fail for the right reason, per TDD dis ### Task 3 — Host wire shape: `confidence` on `RawEdge` + `AcceptedEdge` Files: -- Modify: `crates/clarion-core/src/plugin/protocol.rs` (add `confidence: EdgeConfidence` to `RawEdge` with `#[serde(default)]`). -- Modify: `crates/clarion-core/src/plugin/host.rs` (`AcceptedEdge` carries `confidence`; `process_edges` threads it through to `EdgeRecord`). -- Modify: `crates/clarion-core/tests/` (extend edge round-trip test to assert confidence survives the round trip). +- Modify: `crates/loomweave-core/src/plugin/protocol.rs` (add `confidence: EdgeConfidence` to `RawEdge` with `#[serde(default)]`). +- Modify: `crates/loomweave-core/src/plugin/host.rs` (`AcceptedEdge` carries `confidence`; `process_edges` threads it through to `EdgeRecord`). +- Modify: `crates/loomweave-core/tests/` (extend edge round-trip test to assert confidence survives the round trip). Steps: - RED: host test sends a `calls`-kind RawEdge with `confidence="ambiguous"`; expects `AcceptedEdge.confidence == Ambiguous`. @@ -455,7 +455,7 @@ Steps: ### Task 4 — CLI: thread `confidence` through `map_edge_to_record` (panel-revised — no `WriteOrigin`) Files: -- Modify: `crates/clarion-cli/src/analyze.rs` (extend `map_edge_to_record` to set `confidence` on the `EdgeRecord` it produces). `WriteOrigin` plumbing is DROPPED per Q4 panel reversal. +- Modify: `crates/loomweave-cli/src/analyze.rs` (extend `map_edge_to_record` to set `confidence` on the `EdgeRecord` it produces). `WriteOrigin` plumbing is DROPPED per Q4 panel reversal. Steps: - RED: cli integration test asserts `runs.stats` reports `ambiguous_edges_total > 0` after a run that emits at least one ambiguous edge. @@ -467,9 +467,9 @@ Steps: ### Task 5 — Observability: `ambiguous_edges_total`, `unresolved_call_sites_total`, `pyright_query_latency_p95_ms` Files: -- Modify: `crates/clarion-storage/src/writer.rs` (new `Arc` counters on `Writer`; increments in the right places). -- Modify: `crates/clarion-cli/src/analyze.rs` (snapshot counters at CommitRun; fold into `runs.stats` JSON alongside `dropped_edges_total` from B.3). -- Modify: `crates/clarion-storage/tests/writer_actor.rs` (counter-assertion tests). +- Modify: `crates/loomweave-storage/src/writer.rs` (new `Arc` counters on `Writer`; increments in the right places). +- Modify: `crates/loomweave-cli/src/analyze.rs` (snapshot counters at CommitRun; fold into `runs.stats` JSON alongside `dropped_edges_total` from B.3). +- Modify: `crates/loomweave-storage/tests/writer_actor.rs` (counter-assertion tests). - Add: a small p95 accumulator module + deterministic unit test. Steps: @@ -485,9 +485,9 @@ Steps: ### Task 6 — Pyright integration: `pyright_session.py` module (panel-revised — substantially expanded) Files: -- NEW: `plugins/python/src/clarion_plugin_python/pyright_session.py` — owns the pyright LSP subprocess lifecycle, the JSON-RPC framing, the per-function-entity `prepareCallHierarchy` + `outgoingCalls` flow, restart-on-crash logic with per-run cap, stderr drain via daemon thread, init-hang deadline, explicit `subprocess.Popen` `env` handling. -- NEW: `plugins/python/src/clarion_plugin_python/call_resolver.py` (or co-located in `pyright_session.py`) — defines the `CallResolver` Protocol and `NoOpCallResolver` default. `extract()` accepts a `CallResolver` (no Optional) — `NoOpCallResolver` for tests that don't need pyright; `PyrightSession` for the real path. -- Modify: `plugins/python/src/clarion_plugin_python/server.py` (`ServerState.pyright: PyrightSession | None`; lazy-init on first `analyze_file`; explicit close at `shutdown` that joins the stderr drain thread). +- NEW: `plugins/python/src/loomweave_plugin_python/pyright_session.py` — owns the pyright LSP subprocess lifecycle, the JSON-RPC framing, the per-function-entity `prepareCallHierarchy` + `outgoingCalls` flow, restart-on-crash logic with per-run cap, stderr drain via daemon thread, init-hang deadline, explicit `subprocess.Popen` `env` handling. +- NEW: `plugins/python/src/loomweave_plugin_python/call_resolver.py` (or co-located in `pyright_session.py`) — defines the `CallResolver` Protocol and `NoOpCallResolver` default. `extract()` accepts a `CallResolver` (no Optional) — `NoOpCallResolver` for tests that don't need pyright; `PyrightSession` for the real path. +- Modify: `plugins/python/src/loomweave_plugin_python/server.py` (`ServerState.pyright: PyrightSession | None`; lazy-init on first `analyze_file`; explicit close at `shutdown` that joins the stderr drain thread). - NEW: `plugins/python/tests/test_pyright_session.py` — integration tests against a real pyright (pytest marker `@pytest.mark.pyright`; auto-skipped via a session-scoped fixture when `pyright-langserver` isn't on PATH). - Modify: `plugins/python/pyproject.toml` (register the two new markers — `pyright` and `slow` — under `pytest.ini_options.markers`). @@ -497,12 +497,12 @@ Steps: - RED: `test_pyright_session_ambiguous_dict_dispatch` — `handlers[k]()` over a dict-of-callables; assert ambiguous-tier edge with `properties.candidates` non-empty. - Verify GREEN. - RED: `test_pyright_session_ambiguous_determinism` — run analysis twice on the same input; assert byte-identical `to_id` and `candidates` ordering (proves `sorted(candidates)[0]` deterministic across invocations). -- RED: `test_pyright_session_restart_on_crash` — kill pyright mid-session, assert next query restarts it and emits `CLA-PY-PYRIGHT-RESTART`. -- RED: `test_pyright_session_restart_cap` — force `MAX_PYRIGHT_RESTARTS_PER_RUN + 1` restarts; assert the currently-analysing file is skipped with `CLA-PY-PYRIGHT-POISON-FRAME`; assert subsequent files in the run continue with `unresolved_call_sites_total` increment. -- RED: `test_pyright_session_init_timeout` — monkeypatch the pyright handshake to hang past `PYRIGHT_INIT_TIMEOUT_SECS`; assert `CLA-PY-PYRIGHT-INIT-TIMEOUT` emitted; assert run completes with zero `calls` edges. -- RED: `test_pyright_session_unavailable_binary_missing` — run with `pyright-langserver` not on PATH; assert `CLA-PY-PYRIGHT-UNAVAILABLE` emitted; run completes with zero `calls` edges; `unresolved_call_sites_total` reflects AST call-site count. -- RED: `test_pyright_session_install_failure` — simulate node-bootstrapper failure (mock the executability check); assert `CLA-PY-PYRIGHT-INSTALL-FAILURE` emitted; behavior matches `-UNAVAILABLE`. -- RED: `test_pyright_session_call_resolution_timeout` — mock per-call-site LSP query to exceed 5s; assert `CLA-PY-PYRIGHT-CALL-RESOLUTION-TIMEOUT` emitted; edge omitted (NOT silently dropped, NOT a crashed run). +- RED: `test_pyright_session_restart_on_crash` — kill pyright mid-session, assert next query restarts it and emits `LMWV-PY-PYRIGHT-RESTART`. +- RED: `test_pyright_session_restart_cap` — force `MAX_PYRIGHT_RESTARTS_PER_RUN + 1` restarts; assert the currently-analysing file is skipped with `LMWV-PY-PYRIGHT-POISON-FRAME`; assert subsequent files in the run continue with `unresolved_call_sites_total` increment. +- RED: `test_pyright_session_init_timeout` — monkeypatch the pyright handshake to hang past `PYRIGHT_INIT_TIMEOUT_SECS`; assert `LMWV-PY-PYRIGHT-INIT-TIMEOUT` emitted; assert run completes with zero `calls` edges. +- RED: `test_pyright_session_unavailable_binary_missing` — run with `pyright-langserver` not on PATH; assert `LMWV-PY-PYRIGHT-UNAVAILABLE` emitted; run completes with zero `calls` edges; `unresolved_call_sites_total` reflects AST call-site count. +- RED: `test_pyright_session_install_failure` — simulate node-bootstrapper failure (mock the executability check); assert `LMWV-PY-PYRIGHT-INSTALL-FAILURE` emitted; behavior matches `-UNAVAILABLE`. +- RED: `test_pyright_session_call_resolution_timeout` — mock per-call-site LSP query to exceed 5s; assert `LMWV-PY-PYRIGHT-CALL-RESOLUTION-TIMEOUT` emitted; edge omitted (NOT silently dropped, NOT a crashed run). - RED: `test_pyright_session_stderr_drain` — feed pyright a working input but ensure stderr fills past the OS pipe buffer (~64 KiB); assert the run does NOT deadlock and the stderr drain thread is joined cleanly in `close()`. - Implement each, verify GREEN. - Commit: `feat(wp3): PyrightSession — LSP subprocess + per-function callHierarchy + crash/timeout/unavailable handling (B.4* Q1)`. @@ -510,11 +510,11 @@ Steps: ### Task 7 — Extractor: emit `calls` edges via `CallResolver` (panel-revised — Protocol shape, not Optional) Files: -- Modify: `plugins/python/src/clarion_plugin_python/extractor.py`: +- Modify: `plugins/python/src/loomweave_plugin_python/extractor.py`: - `extract()` signature gains `call_resolver: CallResolver = NoOpCallResolver()` (panel correction — no `Optional[PyrightSession]`; the Protocol with a no-op default eliminates dual code paths and mypy-strict null-check noise). - `RawEdge` TypedDict gains `confidence: NotRequired[Literal["resolved", "ambiguous", "inferred"]]` (panel-flagged: not in the original draft's task ledger; load-bearing for the wire shape). - Extractor's `_walk` accumulates function entities into a side list; after the walk, `call_resolver.resolve_calls(file_path, function_ids)` returns the `calls` RawEdges en masse (matches the per-function LSP traffic shape, Q1). -- Modify: `plugins/python/src/clarion_plugin_python/server.py` (`handle_analyze_file` passes `state.pyright or NoOpCallResolver()` into `extract`). +- Modify: `plugins/python/src/loomweave_plugin_python/server.py` (`handle_analyze_file` passes `state.pyright or NoOpCallResolver()` into `extract`). - Modify: `plugins/python/tests/test_extractor.py` (calls-emission tests; some marked `@pytest.mark.pyright`, others use `NoOpCallResolver` and need no marker). Steps: @@ -539,9 +539,9 @@ Files: [capabilities.runtime.pyright] pin = "1.1.X" # exact version at impl start ``` -- Modify: `crates/clarion-core/src/plugin/manifest.rs` (extend manifest parser to recognise the new `pyright` sub-table under `[capabilities.runtime]`; surface `pin` for `CLA-PY-PYRIGHT-VERSION-DRIFT` detection). -- Modify: `plugins/python/src/clarion_plugin_python/server.py` (`ONTOLOGY_VERSION = "0.4.0"`). -- Modify: `plugins/python/src/clarion_plugin_python/__init__.py` (PATCH `__version__` bump). +- Modify: `crates/loomweave-core/src/plugin/manifest.rs` (extend manifest parser to recognise the new `pyright` sub-table under `[capabilities.runtime]`; surface `pin` for `LMWV-PY-PYRIGHT-VERSION-DRIFT` detection). +- Modify: `plugins/python/src/loomweave_plugin_python/server.py` (`ONTOLOGY_VERSION = "0.4.0"`). +- Modify: `plugins/python/src/loomweave_plugin_python/__init__.py` (PATCH `__version__` bump). - Modify: `plugins/python/pyproject.toml`: - Add `pyright == X.Y.Z` to `[project.dependencies]`. - Add `[tool.pytest.ini_options].markers`: register `pyright` and `slow`. @@ -631,7 +631,7 @@ Steps: Files: - Modify: `docs/implementation/sprint-2/scope-amendment-2026-05.md` (status line: "B.4* complete; see exit criteria §9 of b4-calls-edges.md"). -- Modify: `docs/clarion/adr/ADR-028-edge-confidence-tiers.md` (Open-Questions section: amend "Storage location for inferred edges" with "Resolved by B.4* design Q5 — same `edges` table; sketch of additive migration if reversed inline in b4-calls-edges.md"). +- Modify: `docs/loomweave/adr/ADR-028-edge-confidence-tiers.md` (Open-Questions section: amend "Storage location for inferred edges" with "Resolved by B.4* design Q5 — same `edges` table; sketch of additive migration if reversed inline in b4-calls-edges.md"). - NEW: `docs/implementation/sprint-2/b8-scale-test.md` (or update an existing B.8 design stub) — pre-write the yellow/red rollback options for B.8 *before B.5* begins* (panel-required handoff from Q7/Systems). - Verify all ADR-023 gates green on the closing commit. - Commit: `docs(wp3): close B.4* design + ADR-028 amendment + B.8 rollback pre-write (Q5/Q7 panel-required)`. @@ -649,7 +649,7 @@ B.4* is done for Sprint 2 when ALL of: - Plugin emits `calls` edges with `confidence` ∈ {`resolved`, `ambiguous`} for every static call site pyright resolves to ≥1 in-project candidate. - Plugin emits zero `calls` edges for call sites pyright cannot resolve (unresolved → `unresolved_call_sites_total` increment, NOT a synthetic edge — inferred is lazy per ADR-028 §"Decision 3"). - Plugin gracefully handles pyright init-failure, crash, timeout, version-drift; all emit the named findings without crashing the run; restart cap (`MAX_PYRIGHT_RESTARTS_PER_RUN`) prevents infinite loops. -- Writer-actor extends `enforce_edge_contract` to enforce per-kind confidence contract; structural-with-non-resolved → rejected; anchored-with-inferred-at-scan-time → rejected; both cases increment `dropped_edges_total` AND emit `CLA-INFRA-EDGE-CONFIDENCE-CONTRACT` finding. ALL contract-test matrix permutations from Task 2 (RED-1 through RED-8) pass. +- Writer-actor extends `enforce_edge_contract` to enforce per-kind confidence contract; structural-with-non-resolved → rejected; anchored-with-inferred-at-scan-time → rejected; both cases increment `dropped_edges_total` AND emit `LMWV-INFRA-EDGE-CONFIDENCE-CONTRACT` finding. ALL contract-test matrix permutations from Task 2 (RED-1 through RED-8) pass. - `EXPLAIN QUERY PLAN` on `WHERE kind=? AND confidence=?` uses `ix_edges_kind_confidence`. - `plugin.toml::edge_kinds == ["contains", "calls"]`; `ontology_version == "0.4.0"`; `[capabilities.runtime.pyright].pin == X.Y.Z`. - Pyright is a pinned `[project.dependencies]` entry in `pyproject.toml`; dependabot ignores it (or TODO comment explains manual-bump policy); CI cache key includes the pin. @@ -665,17 +665,17 @@ B.4* is done for Sprint 2 when ALL of: ## 10. Open questions for the implementation phase (lower stakes — panel-expanded) - **Pyright LSP subprocess re-init policy on `project_root` change**: the plugin currently captures `project_root` at `initialize` once. If a future host sends multiple `initialize` calls with different roots (currently not the case), pyright would need a re-init. **Recommendation**: defer — single-init is the contract, document as such, raise if changed. -- **Per-call-site timeout default**: 5s is a guess. Tunable via `clarion.yaml`? **Recommendation**: ship hard-coded 5s; add YAML config in a follow-up if observed. +- **Per-call-site timeout default**: 5s is a guess. Tunable via `loomweave.yaml`? **Recommendation**: ship hard-coded 5s; add YAML config in a follow-up if observed. - **Pyright init-hang deadline default**: 30s. Same recommendation — hard-coded for now, configurable when observed. - **Pyright restart cap default**: `MAX_PYRIGHT_RESTARTS_PER_RUN = 3`. Recommendation: hard-coded; tune via YAML when observed. -- **Counter-vs-finding-per-site for unresolved call sites** (panel-flagged): B.4* ships only the run-level counter. B.6's `callers_of` design MUST pick between (a) per-site finding (`CLA-PY-CALL-SITE-UNRESOLVED`), (b) sibling `entity_unresolved_call_sites` table, or (c) synthesized "edge-with-zero-candidates" rows. Documented in §6 as a B.6 handoff. **Decision deferred to B.6, not B.4*.** -- **Multi-pin upgrade ordering** (panel-flagged, systems): the plugin now carries TWO independent version pins — `[capabilities.runtime.pyright]` and `[integrations.wardline]`. When both need bumping (e.g., pyright minor break coincides with Wardline release), the operator sees independent findings with no guidance on resolve order. B.5* may add a third pin (references resolution). **Decision needed**: do we author a "Loom suite pin discipline" memo before B.5* ships? **Recommendation**: defer to immediately-pre-B.5*; document the interaction in a one-line tracking note in the scope amendment. -- **Plugin coupling-accumulation policy** (panel-flagged, architecture): the Python plugin now has three optional heavy dependencies (Wardline probe at Sprint 1, pyright at B.4*, and references-resolution-engine at B.5* — TBD). "Plugin fails gracefully when dependency absent" is implicit but not stated as policy. **Recommendation**: write a brief plugin-resilience policy memo before B.5* begins, citing both `CLA-PY-PYRIGHT-UNAVAILABLE` and the Wardline-probe absence-handling as the templates. +- **Counter-vs-finding-per-site for unresolved call sites** (panel-flagged): B.4* ships only the run-level counter. B.6's `callers_of` design MUST pick between (a) per-site finding (`LMWV-PY-CALL-SITE-UNRESOLVED`), (b) sibling `entity_unresolved_call_sites` table, or (c) synthesized "edge-with-zero-candidates" rows. Documented in §6 as a B.6 handoff. **Decision deferred to B.6, not B.4*.** +- **Multi-pin upgrade ordering** (panel-flagged, systems): the plugin now carries TWO independent version pins — `[capabilities.runtime.pyright]` and `[integrations.wardline]`. When both need bumping (e.g., pyright minor break coincides with Wardline release), the operator sees independent findings with no guidance on resolve order. B.5* may add a third pin (references resolution). **Decision needed**: do we author a "Weft suite pin discipline" memo before B.5* ships? **Recommendation**: defer to immediately-pre-B.5*; document the interaction in a one-line tracking note in the scope amendment. +- **Plugin coupling-accumulation policy** (panel-flagged, architecture): the Python plugin now has three optional heavy dependencies (Wardline probe at Sprint 1, pyright at B.4*, and references-resolution-engine at B.5* — TBD). "Plugin fails gracefully when dependency absent" is implicit but not stated as policy. **Recommendation**: write a brief plugin-resilience policy memo before B.5* begins, citing both `LMWV-PY-PYRIGHT-UNAVAILABLE` and the Wardline-probe absence-handling as the templates. - **B.4 → B.4*/B.5* rescoping pattern frequency** (panel-flagged, systems, LOW): governance is fine at n=2 sequential re-scopes. If B.5* or B.6 spawns a *-variant mid-implementation, the WP boundary assumptions in `v0.1-plan.md` warrant structural reassessment before Sprint 3. **Recommendation**: monitor; flag if a third re-scope appears. - **Cache-miss storm on concurrent inferred queries** (panel-flagged, systems, for B.6 not B.4*): a single `callers_of(id, confidence>=inferred)` cold-cache call triggers an LLM dispatch. Multiple concurrent MCP queries on the same cold caller fire N parallel LLM calls without coalescing. **Recommendation**: B.6's design pass MUST decide on a per-caller in-flight coalescing guard. - **Async call resolution**: `await foo()` — pyright resolves this through coroutine types correctly; no special-case logic needed for B.4*. Implementation should verify with a test case (added in Task 7). - **Method call vs. function call distinction**: B.4* treats both as `calls` (no kind split — `method_calls` is YAGNI per ADR-027 / ADR-028 spirit). Detailed-design.md mentions "method calls" as approximate; this is what the ambiguous tier captures. -- **Pyright stderr handling**: drained in a daemon thread, joined in `PyrightSession.close()`. Fatal errors surfaced as `CLA-PY-PYRIGHT-DIAGNOSTIC` warnings (optional finding, not in the required set). +- **Pyright stderr handling**: drained in a daemon thread, joined in `PyrightSession.close()`. Fatal errors surfaced as `LMWV-PY-PYRIGHT-DIAGNOSTIC` warnings (optional finding, not in the required set). - **Decorated-function calls**: covered by fixture in Task 7 (`test_extractor_decorated_callable_resolves_when_possible`). - **`__call__` dispatch**: covered by fixture in Task 7 (`test_extractor_dunder_call_dispatch`). @@ -693,8 +693,8 @@ Reviewer verdicts and the doc changes each forced: | Q | Draft proposal | Panel verdict | Reconciliation in this doc | |---|---|---|---| -| Q1 | Pyright-as-LSP; per-call-site `prepareCallHierarchy` + `outgoingCalls`; restart on crash | architecture ACCEPT; reality verified LSP method names; python CRITICAL — **traffic shape wrong**; systems BLOCKING — **poison-frame restart unbounded** | adopted python's per-function-entity traffic shape (Q1 process model rewritten); added `MAX_PYRIGHT_RESTARTS_PER_RUN` cap + `CLA-PY-PYRIGHT-POISON-FRAME`; added `PYRIGHT_INIT_TIMEOUT_SECS` deadline + `CLA-PY-PYRIGHT-INIT-TIMEOUT`; added `CLA-PY-PYRIGHT-UNAVAILABLE` for binary-missing; stderr drain promoted from open-question to Task 6 spec | -| Q2 | Pinned PyPI pyright; new `[runtime.pyright]` manifest block | reality BLOCKING — **manifest path wrong** (existing structure is `[capabilities.runtime]`); architecture ACCEPT-WITH-REVISION — first-run executability check; python WARN — CI cache + dependabot-ignore + explicit subprocess `env` | corrected manifest section to `[capabilities.runtime.pyright]`; added `CLA-PY-PYRIGHT-INSTALL-FAILURE` first-run check; added CI cache key + dependabot-ignore to Task 8; documented explicit `env` for the subprocess; added pyright-vs-Wardline multi-pin drift open-question | +| Q1 | Pyright-as-LSP; per-call-site `prepareCallHierarchy` + `outgoingCalls`; restart on crash | architecture ACCEPT; reality verified LSP method names; python CRITICAL — **traffic shape wrong**; systems BLOCKING — **poison-frame restart unbounded** | adopted python's per-function-entity traffic shape (Q1 process model rewritten); added `MAX_PYRIGHT_RESTARTS_PER_RUN` cap + `LMWV-PY-PYRIGHT-POISON-FRAME`; added `PYRIGHT_INIT_TIMEOUT_SECS` deadline + `LMWV-PY-PYRIGHT-INIT-TIMEOUT`; added `LMWV-PY-PYRIGHT-UNAVAILABLE` for binary-missing; stderr drain promoted from open-question to Task 6 spec | +| Q2 | Pinned PyPI pyright; new `[runtime.pyright]` manifest block | reality BLOCKING — **manifest path wrong** (existing structure is `[capabilities.runtime]`); architecture ACCEPT-WITH-REVISION — first-run executability check; python WARN — CI cache + dependabot-ignore + explicit subprocess `env` | corrected manifest section to `[capabilities.runtime.pyright]`; added `LMWV-PY-PYRIGHT-INSTALL-FAILURE` first-run check; added CI cache key + dependabot-ignore to Task 8; documented explicit `env` for the subprocess; added pyright-vs-Wardline multi-pin drift open-question | | Q3 | `list[str]` candidates; `total=False` TypedDict | architecture ACCEPT (with B.6 consumer caveat); python WARN — **`total=False` diverges from project convention**; quality WARN — no determinism test | switched `CallsEdgeProperties` to `NotRequired` per project convention; added explicit B.6 consumer constraint (don't treat `to_id` as "best guess"); added determinism RED test to Task 6 | | Q4 | `enforce_edge_confidence_contract` new function + `WriteOrigin::{Scan, Query}` enum | architecture BLOCKING — **drop `WriteOrigin` (single-implementer premature abstraction)**; systems WARN — under-specified; reality BLOCKING — **name collides with existing `enforce_edge_contract`**; quality BLOCKING — **VER-without-VAL** on counter+finding emission | dropped `WriteOrigin` entirely; extend the existing `enforce_edge_contract` in place; expanded Task 2 contract-test matrix to 8 cases (RED-1..8) with explicit counter + finding assertions; B.6 introduces scan-vs-query discriminator with the real query write path in hand | | Q5 | Same `edges` table; "reversible" framing | architecture ACCEPT-WITH-REVISION — **"reversible" overstated**; quality ACCEPT — add `EXPLAIN QUERY PLAN` assertion; systems WARN — migration plan doesn't exist | replaced "reversible" with explicit two-step migration sketch inline; added `EXPLAIN QUERY PLAN` assertion to Task 1 and Task 2 verifying `ix_edges_kind_confidence` is used | diff --git a/docs/implementation/sprint-2/b4-gate-results.md b/docs/implementation/sprint-2/b4-gate-results.md index ea31c7c9..73ac3b47 100644 --- a/docs/implementation/sprint-2/b4-gate-results.md +++ b/docs/implementation/sprint-2/b4-gate-results.md @@ -9,7 +9,7 @@ outcome: GREEN calibration_machine: Linux 6.8.0-110-generic #110-Ubuntu SMP PREEMPT_DYNAMIC Thu Mar 19 15:09:20 UTC 2026 x86_64 GNU/Linux; Python 3.12.3 operator_hardware_ratio: 1.0 pyright_pin: 1.1.409 -clarion_commit: 7337506142e12a1fa924867d117c49b054dfce56 +loomweave_commit: 7337506142e12a1fa924867d117c49b054dfce56 ### Corpus Results - elspeth_mini: diff --git a/docs/implementation/sprint-2/b5-gate-results.md b/docs/implementation/sprint-2/b5-gate-results.md index 810d1cd3..d7cebac3 100644 --- a/docs/implementation/sprint-2/b5-gate-results.md +++ b/docs/implementation/sprint-2/b5-gate-results.md @@ -18,7 +18,7 @@ Implementation mitigation included in this run: ### Corpus Results - corpus: `elspeth_mini` -- corpus_root: `/home/john/clarion/tests/perf/elspeth_mini` +- corpus_root: `/home/john/loomweave/tests/perf/elspeth_mini` - file_count: 80 - function_count: 828 - reference_sites_total: 6186 @@ -55,7 +55,7 @@ Implementation mitigation included in this run: - outcome: GREEN - calibration_machine: Linux 6.8.0-110-generic x86_64; Python 3.12.3 - pyright_pin: 1.1.409 -- clarion_commit_at_measurement: `098d61829eb59462596809205dc068b0282a8852` plus the B.5 review-follow-up working-tree patch +- loomweave_commit_at_measurement: `098d61829eb59462596809205dc068b0282a8852` plus the B.5 review-follow-up working-tree patch ### Decision diff --git a/docs/implementation/sprint-2/b5-references-edges.md b/docs/implementation/sprint-2/b5-references-edges.md index 3f293dad..c0e29946 100644 --- a/docs/implementation/sprint-2/b5-references-edges.md +++ b/docs/implementation/sprint-2/b5-references-edges.md @@ -2,7 +2,7 @@ **Status**: READY-FOR-IMPLEMENTATION after five-reviewer panel revisions **Anchoring design**: inherits [B.4* calls edges](./b4-calls-edges.md) except where this memo says otherwise. -**Accepted ADRs**: [ADR-026](../../clarion/adr/ADR-026-containment-wire-and-edge-identity.md), [ADR-027](../../clarion/adr/ADR-027-ontology-version-semver.md), [ADR-028](../../clarion/adr/ADR-028-edge-confidence-tiers.md) +**Accepted ADRs**: [ADR-026](../../loomweave/adr/ADR-026-containment-wire-and-edge-identity.md), [ADR-027](../../loomweave/adr/ADR-027-ontology-version-semver.md), [ADR-028](../../loomweave/adr/ADR-028-edge-confidence-tiers.md) **Predecessor**: [B.4* - `calls` edges](./b4-calls-edges.md) **Filigree umbrella**: `clarion-b0cedfd2bb` @@ -30,7 +30,7 @@ Changes in scope: Out of scope: - Virtual entities for builtins, stdlib, site-packages, typeshed, or - third-party packages. Pyright can resolve `int`, but Clarion has no persisted + third-party packages. Pyright can resolve `int`, but Loomweave has no persisted target entity for it today. - Subclassing: `class B(A)` is future `inherits_from`, not `references`. - Decorators: future `decorates`. @@ -55,7 +55,7 @@ Inherited unchanged: Required B.5* correction to the live tree: -- `crates/clarion-storage/src/writer.rs` currently lists anchored edge kinds as +- `crates/loomweave-storage/src/writer.rs` currently lists anchored edge kinds as `calls`, `imports`, `decorates`, `inherits_from`. Add `references`, and tighten the anchored source-range contract so both endpoints are required, not merely one endpoint. @@ -157,7 +157,7 @@ Traffic envelope: Target filter and mapping: - Persist only targets whose resolved URI is under `project_root` and maps to a - known Clarion module, class, or function entity. + known Loomweave module, class, or function entity. - Pyright definition locations are mapped through a shared entity declaration index, not B.4*'s function-only index. The index includes: - module entity by source file path; @@ -212,9 +212,9 @@ Lockstep updates: - `plugins/python/plugin.toml`: `edge_kinds = ["contains", "calls", "references"]`; `ontology_version = "0.5.0"`. -- `plugins/python/src/clarion_plugin_python/server.py`: +- `plugins/python/src/loomweave_plugin_python/server.py`: `ONTOLOGY_VERSION = "0.5.0"`. -- `plugins/python/src/clarion_plugin_python/__init__.py` and +- `plugins/python/src/loomweave_plugin_python/__init__.py` and `plugins/python/pyproject.toml`: package `0.1.3 -> 0.1.4`. ### D6 - Pyright Provisioning And Failure Modes @@ -225,12 +225,12 @@ Failure behavior: | Case | Finding / observable | Behavior | |---|---|---| -| pyright unavailable | `CLA-PY-PYRIGHT-UNAVAILABLE` | no calls or references from pyright; site counters record skipped/unresolved sites | -| pyright install failure | `CLA-PY-PYRIGHT-INSTALL-FAILURE` | same | -| init timeout | `CLA-PY-PYRIGHT-INIT-TIMEOUT` | same | -| subprocess crash, restart under cap | `CLA-PY-PYRIGHT-RESTART` | retry future work through restarted session | -| restart cap exceeded | `CLA-PY-PYRIGHT-POISON-FRAME` | session disabled; both calls and references suppressed for remaining files | -| per-reference lookup timeout | `CLA-PY-REFERENCE-RESOLUTION-TIMEOUT` | current site skipped; run continues | +| pyright unavailable | `LMWV-PY-PYRIGHT-UNAVAILABLE` | no calls or references from pyright; site counters record skipped/unresolved sites | +| pyright install failure | `LMWV-PY-PYRIGHT-INSTALL-FAILURE` | same | +| init timeout | `LMWV-PY-PYRIGHT-INIT-TIMEOUT` | same | +| subprocess crash, restart under cap | `LMWV-PY-PYRIGHT-RESTART` | retry future work through restarted session | +| restart cap exceeded | `LMWV-PY-PYRIGHT-POISON-FRAME` | session disabled; both calls and references suppressed for remaining files | +| per-reference lookup timeout | `LMWV-PY-REFERENCE-RESOLUTION-TIMEOUT` | current site skipped; run continues | The same `PyrightSession` disabled state suppresses both call and reference resolution. Plugin findings remain resolver/extractor test observables in B.5*; @@ -274,7 +274,7 @@ Rust `AnalyzeFileStats` grows serde-defaulted fields: - existing `pyright_query_latency_ms`, now documented as all pyright LSP query latency samples, not only call-hierarchy latency. -`clarion analyze` aggregates these into `runs.stats` with the same names plus +`loomweave analyze` aggregates these into `runs.stats` with the same names plus the existing `pyright_query_latency_p95_ms`. ## 5. Storage, Host, And Consumer Contracts @@ -292,7 +292,7 @@ Writer requirements: Host requirements: - Before the manifest bump, a `references` edge is dropped with - `CLA-INFRA-PLUGIN-UNDECLARED-EDGE-KIND`. + `LMWV-INFRA-PLUGIN-UNDECLARED-EDGE-KIND`. - After the manifest bump, `process_edges` accepts `references` exactly as it accepts other declared edge kinds. @@ -307,7 +307,7 @@ B.6 consumer handoff: B.5* adds `MAX_REFERENCE_SITES_PER_FILE` (default 2000). If a file exceeds the cap, reference resolution for that file is skipped, `references_skipped_cap_total` -increments by the skipped site count, and a `CLA-PY-REFERENCE-SITE-CAP` warning +increments by the skipped site count, and a `LMWV-PY-REFERENCE-SITE-CAP` warning is emitted. Calls still run. B.5 reference scale smoke: @@ -345,11 +345,11 @@ RED matrix: | Case | Observable | |---|---| -| `references` before manifest declaration | host finding `CLA-INFRA-PLUGIN-UNDECLARED-EDGE-KIND` | -| no source range | writer error `CLA-INFRA-EDGE-SOURCE-RANGE-CONTRACT` + `dropped_edges_total += 1` | +| `references` before manifest declaration | host finding `LMWV-INFRA-PLUGIN-UNDECLARED-EDGE-KIND` | +| no source range | writer error `LMWV-INFRA-EDGE-SOURCE-RANGE-CONTRACT` + `dropped_edges_total += 1` | | start-only range | same | | end-only range | same | -| `confidence=inferred` | writer error `CLA-INFRA-EDGE-CONFIDENCE-CONTRACT` + `dropped_edges_total += 1` | +| `confidence=inferred` | writer error `LMWV-INFRA-EDGE-CONFIDENCE-CONTRACT` + `dropped_edges_total += 1` | | resolved with both endpoints | row persisted, no counters | | ambiguous with both endpoints | row persisted, `ambiguous_edges_total += 1` | | duplicate logical edge | plugin-level dedup test; writer dedupe behavior remains unchanged | @@ -394,10 +394,10 @@ RED tests: no edge and increment skipped/unresolved stats. - duplicate references keep earliest byte range and merge candidates. - ambiguous result is deterministic and has full sorted `properties.candidates`. -- `CLA-PY-PYRIGHT-UNAVAILABLE`, `CLA-PY-PYRIGHT-INSTALL-FAILURE`, - `CLA-PY-PYRIGHT-INIT-TIMEOUT`, `CLA-PY-PYRIGHT-RESTART`, - `CLA-PY-PYRIGHT-POISON-FRAME`, `CLA-PY-REFERENCE-RESOLUTION-TIMEOUT`, - and `CLA-PY-REFERENCE-SITE-CAP` each have a targeted negative test. +- `LMWV-PY-PYRIGHT-UNAVAILABLE`, `LMWV-PY-PYRIGHT-INSTALL-FAILURE`, + `LMWV-PY-PYRIGHT-INIT-TIMEOUT`, `LMWV-PY-PYRIGHT-RESTART`, + `LMWV-PY-PYRIGHT-POISON-FRAME`, `LMWV-PY-REFERENCE-RESOLUTION-TIMEOUT`, + and `LMWV-PY-REFERENCE-SITE-CAP` each have a targeted negative test. GREEN: extend `PyrightSession` with `resolve_references(file_path, sites)` and the shared entity index. Reuse lifecycle, stderr drain, and restart behavior. diff --git a/docs/implementation/sprint-2/b6-mcp-surface.md b/docs/implementation/sprint-2/b6-mcp-surface.md index 5f88dae3..b42da308 100644 --- a/docs/implementation/sprint-2/b6-mcp-surface.md +++ b/docs/implementation/sprint-2/b6-mcp-surface.md @@ -1,4 +1,4 @@ -# B.6 - WP8 MCP surface: `clarion-mcp` crate with 7 navigation tools +# B.6 - WP8 MCP surface: `loomweave-mcp` crate with 7 navigation tools **Status**: PANEL-REVISED STAGE 0 DESIGN - split required; no implementation starts before this document is committed. @@ -6,13 +6,13 @@ starts before this document is committed. [B.4* calls edges](./b4-calls-edges.md), [B.5* references edges](./b5-references-edges.md) **Accepted ADRs**: -[ADR-007](../../clarion/adr/ADR-007-summary-cache-key.md), -[ADR-011](../../clarion/adr/ADR-011-writer-actor-concurrency.md), -[ADR-022](../../clarion/adr/ADR-022-core-plugin-ontology.md), -[ADR-023](../../clarion/adr/ADR-023-tooling-baseline.md), -[ADR-028](../../clarion/adr/ADR-028-edge-confidence-tiers.md), -[ADR-029](../../clarion/adr/ADR-029-entity-associations-binding.md), -[ADR-030](../../clarion/adr/ADR-030-on-demand-summary-scope.md) +[ADR-007](../../loomweave/adr/ADR-007-summary-cache-key.md), +[ADR-011](../../loomweave/adr/ADR-011-writer-actor-concurrency.md), +[ADR-022](../../loomweave/adr/ADR-022-core-plugin-ontology.md), +[ADR-023](../../loomweave/adr/ADR-023-tooling-baseline.md), +[ADR-028](../../loomweave/adr/ADR-028-edge-confidence-tiers.md), +[ADR-029](../../loomweave/adr/ADR-029-entity-associations-binding.md), +[ADR-030](../../loomweave/adr/ADR-030-on-demand-summary-scope.md) **Predecessor**: B.5* (`references` edges) **Successor**: B.8 (elspeth scale-test) **Filigree umbrella**: `clarion-e2a3672cc9` @@ -21,8 +21,8 @@ starts before this document is committed. ## 1. Scope -B.6 introduces Clarion's MVP consult surface: a new Rust library crate, -`clarion-mcp`, and a `clarion serve` CLI subcommand exposing seven MCP tools +B.6 introduces Loomweave's MVP consult surface: a new Rust library crate, +`loomweave-mcp`, and a `loomweave serve` CLI subcommand exposing seven MCP tools over JSON-RPC stdio. The seven tools are: @@ -49,34 +49,34 @@ Out of scope: function/class summaries. - Findings emission to Filigree. ADR-029 split WP9-A binding from WP9-B finding export. -- Editing Filigree main branches from this Clarion checkout. Filigree changes +- Editing Filigree main branches from this Loomweave checkout. Filigree changes happen in the Filigree repo and PR. Live prerequisite check at this revision: -- Clarion branch is `sprint-2/b6-impl` from `sprint-2/b5-impl`. +- Loomweave branch is `sprint-2/b6-impl` from `sprint-2/b5-impl`. - B.5* commits are present locally. - Filigree PR 42 is OPEN, base `main`, head - `clarion/b7-entity-associations`, and contains the reverse + `loomweave/b7-entity-associations`, and contains the reverse `GET /api/entity-associations?entity_id=...` route plus `list_associations_by_entity` MCP/data-layer support. Some PR checks were still in progress at this design pass; Phase E depends on that PR being merged or otherwise available to the integration environment. -- The global `filigree` binary is older than Clarion's tracker DB schema. - Clarion tracker commands must use: +- The global `filigree` binary is older than Loomweave's tracker DB schema. + Loomweave tracker commands must use: `uv run --project /home/john/filigree filigree ...` ## 2. Locked Surfaces B.6 reads and writes against these existing surfaces: -- Workspace crates today are `clarion-core`, `clarion-storage`, - `clarion-cli`, and `clarion-plugin-fixture`. `clarion-mcp` becomes the +- Workspace crates today are `loomweave-core`, `loomweave-storage`, + `loomweave-cli`, and `loomweave-plugin-fixture`. `loomweave-mcp` becomes the fifth workspace member. - JSON-RPC Content-Length framing already exists in - `crates/clarion-core/src/plugin/transport.rs`. B.6 reuses that framing but - defines MCP-specific protocol types inside `clarion-mcp`. -- `crates/clarion-cli/src/cli.rs` currently has `install` and `analyze`. + `crates/loomweave-core/src/plugin/transport.rs`. B.6 reuses that framing but + defines MCP-specific protocol types inside `loomweave-mcp`. +- `crates/loomweave-cli/src/cli.rs` currently has `install` and `analyze`. `serve` is a new subcommand on the same binary. - `ReaderPool` is the read path. MCP tool handlers borrow readers with `ReaderPool::with_reader`, do short queries, and drop the connection. @@ -101,7 +101,7 @@ B.6 reads and writes against these existing surfaces: ### D1 - MCP SDK choice and process model -Decision: implement a small Rust MCP stdio server in `clarion-mcp`, using the +Decision: implement a small Rust MCP stdio server in `loomweave-mcp`, using the existing Content-Length framing style rather than adopting a third-party SDK for B.6. @@ -120,9 +120,9 @@ tools/list, and tools/call. Process model: -- `clarion serve` runs one foreground MCP stdio process. This is not the +- `loomweave serve` runs one foreground MCP stdio process. This is not the ADR-012 HTTP/UDS read API. -- It opens a `ReaderPool` to `.clarion/clarion.db`. +- It opens a `ReaderPool` to `.loomweave/loomweave.db`. - It starts the storage writer actor when summary, inferred-edge, or query-time stats writes are enabled. - It owns `ServerState`: config, readers, optional writer, provider factory, @@ -130,13 +130,13 @@ Process model: ### D2 - Crate structure -Decision: create `crates/clarion-mcp/` as a library crate. +Decision: create `crates/loomweave-mcp/` as a library crate. -`crates/clarion-mcp/Cargo.toml` must use the workspace floor: +`crates/loomweave-mcp/Cargo.toml` must use the workspace floor: ```toml [package] -name = "clarion-mcp" +name = "loomweave-mcp" edition.workspace = true rust-version.workspace = true license.workspace = true @@ -148,9 +148,9 @@ workspace = true Dependencies: -- `clarion-core`: entity IDs, edge confidence, provider traits, prompt +- `loomweave-core`: entity IDs, edge confidence, provider traits, prompt metadata, and reused framing. -- `clarion-storage`: `ReaderPool`, writer actor handle, schema helpers, query +- `loomweave-storage`: `ReaderPool`, writer actor handle, schema helpers, query helpers. - `serde` / `serde_json`: MCP protocol and tool schemas. - `tokio`: stdio loop, writer actor integration, in-flight coalescing. @@ -158,8 +158,8 @@ Dependencies: - `reqwest` with rustls only when Phase E starts and the Filigree client is actually implemented. -`clarion-cli` owns CLI parsing and calls `clarion_mcp::serve(options)`. -`clarion-mcp` is a library so tests can call dispatch functions without +`loomweave-cli` owns CLI parsing and calls `loomweave_mcp::serve(options)`. +`loomweave-mcp` is a library so tests can call dispatch functions without spawning a process for every unit case. ### D3 - Query-time writer commands @@ -329,8 +329,8 @@ use a 60-second timeout; timeout increments ### D6 - Filigree HTTP client and auth -Decision: `clarion-mcp` owns a small Filigree HTTP client, configured from -`clarion.yaml`. +Decision: `loomweave-mcp` owns a small Filigree HTTP client, configured from +`loomweave.yaml`. Config: @@ -339,14 +339,14 @@ integrations: filigree: enabled: true base_url: "http://127.0.0.1:8766" - actor: "clarion-mcp" + actor: "loomweave-mcp" token_env: FILIGREE_API_TOKEN timeout_seconds: 5 ``` Auth follows Filigree's actor-identity pattern from PR 42. If the route later -requires Bearer auth, Clarion reads `token_env`; otherwise it still sends -`actor` where Filigree accepts it. Filigree absence never breaks Clarion solo +requires Bearer auth, Loomweave reads `token_env`; otherwise it still sends +`actor` where Filigree accepts it. Filigree absence never breaks Loomweave solo mode: `issues_for` returns an unavailable envelope with reason `filigree-unreachable`. @@ -366,7 +366,7 @@ Required response shape from PR 42: "associations": [ { "issue_id": "filigree-...", - "clarion_entity_id": "python:function:demo.hello", + "loomweave_entity_id": "python:function:demo.hello", "content_hash_at_attach": "...", "attached_at": "...", "attached_by": "..." @@ -378,9 +378,9 @@ Required response shape from PR 42: Current PR 42 includes this route, its data-layer helper, HTTP tests, and MCP tool tests. B.6 Phase E still treats it as a live external dependency: -- If PR 42 merges before Phase E, Clarion integration tests use the merged +- If PR 42 merges before Phase E, Loomweave integration tests use the merged Filigree route. -- If PR 42 remains open, Phase E does not close B.6; it may prepare Clarion +- If PR 42 remains open, Phase E does not close B.6; it may prepare Loomweave code against the contract, but the seven-tool MVP requires a real route-backed integration test before close. @@ -388,7 +388,7 @@ Why not client-side scan: - Fetching all issues and then every issue's associations is O(issue_count) HTTP calls per `issues_for`. -- PR 42 already adds the entity association index and reverse route; Clarion +- PR 42 already adds the entity association index and reverse route; Loomweave should use that one-hop lookup. ### D8 - `include_contained` traversal cap @@ -438,13 +438,13 @@ inferred-dispatch state. ### D10 - Schema additions and migration policy Decision: edit `0001_initial_schema.sql` in place for B.6 unless a published -Clarion build has already produced databases with the Sprint 2 branch schema. +Loomweave build has already produced databases with the Sprint 2 branch schema. Reasoning: - `main` / `origin/main` do not contain B.4* or B.5* edge rows. - B.4* and B.5* are branch-local Sprint 2 work. -- More importantly, no external operator has produced a `.clarion/clarion.db` +- More importantly, no external operator has produced a `.loomweave/loomweave.db` from a published build containing these branch schemas. That external published-DB boundary is ADR-024's real retirement trigger for in-place edits. @@ -506,7 +506,7 @@ When the ceiling is reached: - `summary()` returns `available=false`, reason `cost-ceiling-exceeded`. - Inferred-edge dispatch returns the same reason. - Server stats increment `cost_ceiling_exceeded_total`. -- Response diagnostics include `CLA-LLM-COST-CEILING-EXCEEDED`. +- Response diagnostics include `LMWV-LLM-COST-CEILING-EXCEEDED`. The ceiling resets on process restart. @@ -515,10 +515,10 @@ The ceiling resets on process restart. Decision: the exact MCP tool descriptions are: `entity_at`: -"Return the innermost Clarion entity whose source range contains a file and line. Paths are normalized relative to the project root. Returns no match rather than guessing when ranges are absent." +"Return the innermost Loomweave entity whose source range contains a file and line. Paths are normalized relative to the project root. Returns no match rather than guessing when ranges are absent." `find_entity`: -"Search Clarion entities by id, name, short name, and summary text stored on entity rows. Results are paginated and ranked by FTS match where possible. This does not traverse the graph and does not search on-demand summary_cache entries." +"Search Loomweave entities by id, name, short name, and summary text stored on entity rows. Results are paginated and ranked by FTS match where possible. This does not traverse the graph and does not search on-demand summary_cache entries." `callers_of`: "Return entities that call the given entity. Default confidence is resolved, so ambiguous static candidates and LLM-inferred edges are excluded unless explicitly requested. Ambiguous edges expand all candidates; inferred edges may trigger bounded LLM dispatch." @@ -530,10 +530,10 @@ Decision: the exact MCP tool descriptions are: "Return an on-demand cached summary for one entity. In v0.1 this is leaf scope only: module summaries describe the module docstring and top-level members, not an aggregation of contained function/class summaries." `issues_for`: -"Return Filigree issues attached to this Clarion entity, optionally including issues attached to contained entities. Filigree is an enrichment source; if unavailable, the tool returns an unavailable envelope instead of failing Clarion." +"Return Filigree issues attached to this Loomweave entity, optionally including issues attached to contained entities. Filigree is an enrichment source; if unavailable, the tool returns an unavailable envelope instead of failing Loomweave." `neighborhood`: -"Return the one-hop Clarion neighborhood around an entity: callers, callees, container, contained entities, and references. Default confidence is resolved; ambiguous and inferred calls are opt-in. References are not execution flow." +"Return the one-hop Loomweave neighborhood around an entity: callers, callees, container, contained entities, and references. Default confidence is resolved; ambiguous and inferred calls are opt-in. References are not execution flow." ### D13 - WP6 narrowing matrix @@ -653,7 +653,7 @@ Config rules: - `RecordingProvider` is selected by tests and fixture configs. - `AnthropicProvider` is selected only when `llm.enabled=true` and either the config has `allow_live_provider=true` or the process has - `CLARION_LLM_LIVE=1`. + `LOOMWEAVE_LLM_LIVE=1`. - Missing API key with live provider selected is a startup/config error. - API key present while live provider is not explicitly allowed has no effect. @@ -874,7 +874,7 @@ Semantics: - Uses the Filigree reverse route from D7. - Missing entity returns `available=false`, reason `entity-not-found`. - Entity without content hash can still query Filigree but cannot classify - drift; response includes diagnostic `CLA-ENTITY-CONTENT-HASH-MISSING`. + drift; response includes diagnostic `LMWV-ENTITY-CONTENT-HASH-MISSING`. - Compares `content_hash_at_attach` to current `entities.content_hash`. - Includes contained traversal per D8. - Filigree unavailable is a normal unavailable envelope. @@ -949,7 +949,7 @@ B.6a is a checkpoint PR/commit series. It does not close Task 2 - Crate scaffold and protocol skeleton -- Create `crates/clarion-mcp/` with workspace package metadata and +- Create `crates/loomweave-mcp/` with workspace package metadata and `[lints] workspace = true`. - Add workspace member. - Implement MCP `initialize`, `tools/list`, `tools/call`, and D15 error @@ -957,16 +957,16 @@ Task 2 - Crate scaffold and protocol skeleton - Add schema/golden protocol tests. - Commit: `feat(mcp): scaffold stdio MCP server`. -Task 3 - `clarion serve` +Task 3 - `loomweave serve` - Add CLI subcommand and options for project path/config path. -- Open `.clarion/clarion.db`, build `ReaderPool`, instantiate `ServerState`. -- Smoke test `clarion serve --help` and stdio initialize. -- Commit: `feat(cli): add clarion serve subcommand`. +- Open `.loomweave/loomweave.db`, build `ReaderPool`, instantiate `ServerState`. +- Smoke test `loomweave serve --help` and stdio initialize. +- Commit: `feat(cli): add loomweave serve subcommand`. Task 4 - Config and provider factory shell -- Add `clarion.yaml` config parsing for MCP, LLM disabled defaults, and +- Add `loomweave.yaml` config parsing for MCP, LLM disabled defaults, and Filigree config. - Add D16 provider factory tests before live provider implementation. - Commit: `feat(config): add MCP and LLM config defaults`. @@ -999,7 +999,7 @@ Task 7 - Five storage-backed tools Task 8 - B.6a end-to-end MCP test - Add `tests/e2e/sprint_2_mcp_surface.sh`. -- Start `clarion serve` against a demo DB. +- Start `loomweave serve` against a demo DB. - Send real Content-Length framed `initialize`, `tools/list`, and the five storage-backed tool calls. - Assert docstrings, response envelopes, confidence defaults, empty paths, and @@ -1012,7 +1012,7 @@ Task 9 - Summary and inferred cache schema - Extend `summary_cache` per D10. - Add `inferred_edge_cache` per D5. -- Add cache read/write/touch helpers in `clarion-storage`. +- Add cache read/write/touch helpers in `loomweave-storage`. - Commit: `feat(storage): add MCP LLM cache tables`. Task 10 - LLM providers and prompt templates @@ -1052,7 +1052,7 @@ Task 14 - Filigree contract verification environment. - Add a contract test hitting `GET /api/entity-associations?entity_id=...`. - File a Filigree follow-up only if the committed route diverges from D7. -- Commit Clarion-side contract fixture updates if needed. +- Commit Loomweave-side contract fixture updates if needed. Task 15 - `issues_for` @@ -1085,7 +1085,7 @@ Task 18 - ADR-compatible resolution notes and close write a new ADR or a supersession note. - Move Filigree `clarion-e2a3672cc9` through verifying/done only after all gates pass. -- Close `clarion-73ab0da435` only after the Clarion-side `issues_for` stitch +- Close `clarion-73ab0da435` only after the Loomweave-side `issues_for` stitch is verified. - Commit: `docs(wp8): close B.6 ADR resolution notes`. @@ -1094,8 +1094,8 @@ Task 18 - ADR-compatible resolution notes and close B.6a checkpoint is done when: - Stage 0 design doc and panel record are committed. -- `clarion-mcp` is a workspace member with ADR-023 crate metadata/lints. -- `clarion serve` starts an MCP stdio server. +- `loomweave-mcp` is a workspace member with ADR-023 crate metadata/lints. +- `loomweave serve` starts an MCP stdio server. - `tools/list` exposes exact D12 docstrings. - Five storage-backed tools respond with documented shapes: `entity_at`, `find_entity`, `callers_of`, `execution_paths_from`, @@ -1118,7 +1118,7 @@ Full B.6 is done when: - ADR-028 and ADR-030 resolution notes land without silently rewriting accepted ADR decisions. - Filigree `clarion-e2a3672cc9` reaches done. -- `clarion-73ab0da435` closes only after the Clarion-side stitch is in place. +- `clarion-73ab0da435` closes only after the Loomweave-side stitch is in place. Exact local gates before any B.6 close commit: diff --git a/docs/implementation/sprint-2/b8-elspeth-scale-test.md b/docs/implementation/sprint-2/b8-elspeth-scale-test.md index dc698f60..c5db6b01 100644 --- a/docs/implementation/sprint-2/b8-elspeth-scale-test.md +++ b/docs/implementation/sprint-2/b8-elspeth-scale-test.md @@ -4,8 +4,8 @@ **Date opened**: 2026-05-18 **Filigree umbrella**: `clarion-6222134e0d` **Branch**: `sprint-2/b8-scale-test` -**Purpose**: close Sprint 2 with measured evidence from `clarion analyze` and -`clarion serve` against an elspeth-scale corpus. +**Purpose**: close Sprint 2 with measured evidence from `loomweave analyze` and +`loomweave serve` against an elspeth-scale corpus. **Read with**: [scope amendment](./scope-amendment-2026-05.md), [B.4* gate results](./b4-gate-results.md), [B.8 rollback playbook](./b8-scale-test.md), and @@ -35,9 +35,9 @@ is larger: 1,519 Python files and 608,930 LOC. The full checkout is useful as a future stress target but is not the named B.8 slice. Representativeness checks before analyze: confirm path, file count, LOC, pinned -commit, and dirty state; record any Python files Clarion excludes. B.8 measures -what Clarion actually analyzes, so the memo must distinguish "corpus LOC" from -"files accepted by Clarion". +commit, and dirty state; record any Python files Loomweave excludes. B.8 measures +what Loomweave actually analyzes, so the memo must distinguish "corpus LOC" from +"files accepted by Loomweave". Fallback corpus: @@ -58,15 +58,15 @@ records the fields below, even when a field is unavailable. Unknown values use ### Analyze-Time Fields -Run identity: run_id, timestamps, Clarion commit, elspeth commit, corpus path, +Run identity: run_id, timestamps, Loomweave commit, elspeth commit, corpus path, Python file count, Python LOC, dirty state, command line, pyright pin, calibration machine, and operator hardware ratio. Wall-clock: total analyze time plus phase timings for discovery, plugin initialization, per-file analysis, and commit batches. -Resources: peak RSS for the `clarion analyze` process group, peak -`.clarion/clarion.db` size, and final `.clarion/` directory size. +Resources: peak RSS for the `loomweave analyze` process group, peak +`.loomweave/loomweave.db` size, and final `.loomweave/` directory size. Graph and B.4* comparability: corpus function count, accepted function count, entities by kind, edges by kind and by `(kind, confidence)`, calls-edge count by @@ -75,7 +75,7 @@ confidence, ambiguous ratio, unresolved-per-function, `dropped_edges_total`, observed/projected ratio, and whether assumptions changed. Pyright and findings: pyright per-file p50/p95 latency, restart count, -`CLA-PY-PYRIGHT-*` findings by code, `CLA-INFRA-*` findings by code, and any +`LMWV-PY-PYRIGHT-*` findings by code, `LMWV-INFRA-*` findings by code, and any other material finding count that changes gate interpretation. ### MCP-Serve-Time Fields @@ -247,7 +247,7 @@ Rollback Action, and Raw Artifacts. The driver lives at `tests/perf/b8_scale_test/driver.py`. -Responsibilities: spawn `clarion serve --path `, send +Responsibilities: spawn `loomweave serve --path `, send Content-Length framed JSON-RPC, exercise `initialize`, `tools/list`, and all seven `tools/call` requests, run manifest `B8-MCP-001`, record per-call latency/size/token/envelope stats, summarize by tool/pattern/phase/cache state, @@ -281,10 +281,10 @@ response parsing before driver implementation, then smoke it against a small analyzed project before elspeth scale. Stage 2 - analyze: source `.env` only when live LLM calls are needed, run -`clarion analyze` against the pinned corpus, and stop before Stage 3 if analyze +`loomweave analyze` against the pinned corpus, and stop before Stage 3 if analyze is Red. -Stage 3 - serve: start `clarion serve`, run all patterns, and use the second +Stage 3 - serve: start `loomweave serve`, run all patterns, and use the second medium pass as the cache-hit gate. Stage 4 - decision memo: append one entry to `b8-results.md`, state the verdict diff --git a/docs/implementation/sprint-2/b8-results.md b/docs/implementation/sprint-2/b8-results.md index 357db880..bbad924f 100644 --- a/docs/implementation/sprint-2/b8-results.md +++ b/docs/implementation/sprint-2/b8-results.md @@ -34,32 +34,32 @@ N stubs + 1 impl. surfaced_then_dismissed: **`clarion-0cd961dbbc`** — filed and closed as `not_a_bug` during this gate's investigation. The "cross-file module collision" was actually stale state from the prior RED run; the corpus's -`.clarion/clarion.db` from `2026-05-18T00:17Z` persisted 30,950 entities +`.loomweave/loomweave.db` from `2026-05-18T00:17Z` persisted 30,950 entities (via batched commits before the failing batch's rollback) including `python:module:examples.chroma_rag.seed_collection`. Subsequent analyze runs against the same project root appended to that DB and collided. The -stale `.clarion` was moved aside (preserved at -`/tmp/clarion-b8-elspeth-full-20260518T0016Z/.clarion.from-2026-05-18T0017Z-RED`) -and a fresh `clarion install` was run against the corpus root. No +stale `.loomweave` was moved aside (preserved at +`/tmp/loomweave-b8-elspeth-full-20260518T0016Z/.loomweave.from-2026-05-18T0017Z-RED`) +and a fresh `loomweave install` was run against the corpus root. No production code change shipped for this issue. ### Reproducibility | Field | Value | |---|---| -| Clarion branch at run | `sprint-2/b8-scale-test` | -| Clarion commit at run | `29f0426` (`fix(wp3): skip @overload stubs to prevent UNIQUE(entities.id) collision`) | -| Clarion working-tree changes | same untracked / unrelated changes carried from the prior RED entry (see Reproducibility there); none material to this rerun | +| Loomweave branch at run | `sprint-2/b8-scale-test` | +| Loomweave commit at run | `29f0426` (`fix(wp3): skip @overload stubs to prevent UNIQUE(entities.id) collision`) | +| Loomweave working-tree changes | same untracked / unrelated changes carried from the prior RED entry (see Reproducibility there); none material to this rerun | | Corpus source | `/home/john/elspeth/` (full checkout) | | Corpus commit | `9d3fd55d63bac764c88af04330af2c3f4f651346` | -| Scratch corpus path | `/tmp/clarion-b8-elspeth-full-20260518T0016Z` (same as the prior RED, reused verbatim) | +| Scratch corpus path | `/tmp/loomweave-b8-elspeth-full-20260518T0016Z` (same as the prior RED, reused verbatim) | | Python files in scratch | 1,532 (1,526 reach the plugin after `SKIP_DIRS` filtering) | | Raw artifacts | `tests/perf/b8_scale_test/results/2026-05-18T0114Z/` | -| Install command | `clarion install --path /tmp/clarion-b8-elspeth-full-20260518T0016Z` | -| Analyze command | `target/release/clarion analyze /tmp/clarion-b8-elspeth-full-20260518T0016Z` (with python plugin venv bin on PATH) | +| Install command | `loomweave install --path /tmp/loomweave-b8-elspeth-full-20260518T0016Z` | +| Analyze command | `target/release/loomweave analyze /tmp/loomweave-b8-elspeth-full-20260518T0016Z` (with python plugin venv bin on PATH) | | RSS sampler | `tests/perf/b8_scale_test/results/2026-05-18T0114Z/analyze-with-rss.py` (250 ms poll over proc + 2 generations of descendants; carried verbatim from the prior RED) | | Serve driver | `tests/perf/b8_scale_test/driver.py --timeout-seconds 300 --skip-inferred` | -| Serve config | `/tmp/clarion-b8-elspeth-full-20260518T0016Z/clarion-b8-live.yaml` (with `integrations.filigree.base_url` updated to `http://127.0.0.1:8885` for this run's dashboard binding) | +| Serve config | `/tmp/loomweave-b8-elspeth-full-20260518T0016Z/loomweave-b8-live.yaml` (with `integrations.filigree.base_url` updated to `http://127.0.0.1:8885` for this run's dashboard binding) | | Filigree route | enabled (`http://127.0.0.1:8885`), HTTP reachable, no live entity associations attached to corpus entities | ### Analyze-Time Measurements @@ -74,7 +74,7 @@ production code change shipped for this issue. | NFR-PERF-01 limit | 60m (≤ 13.46% of envelope) | | Peak RSS (sampled) | 197,865,472 bytes / 188.699 MiB | | Peak RSS caveat | sampler swept process + 2 generations of descendants at 250 ms; like the prior RED, this RSS number is a lower bound vs. the named-slice 2026-05-17T21:56Z GREEN's 1,939 MiB which used a different harness — treat as not directly comparable | -| `.clarion/clarion.db` size at completion | 234.66 MiB | +| `.loomweave/loomweave.db` size at completion | 234.66 MiB | | Discovery/source walk | ~0.013s from log start to `source tree walk complete` | | Plugin processing | 484.42s (`processing plugin` 01:51:06.169Z → `plugin complete` 01:59:10.586Z); the plugin-driven path dominates the wall-clock since `analyze` is single-plugin | | Pyright per-file p95 | 1,194 ms | @@ -131,7 +131,7 @@ Plugin-host findings observed in stdout (informational; not persisted): | Subcode | Count | Materiality | |---|---:|---| -| `CLA-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE` | 21 | Same `callee_expr > 512 bytes` signal as prior runs (3 in the named slice, 6 in the prior supplementary RED, 21 here at full-corpus scale). Increases with corpus size; harmless to analyze. | +| `LMWV-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE` | 21 | Same `callee_expr > 512 bytes` signal as prior runs (3 in the named slice, 6 in the prior supplementary RED, 21 here at full-corpus scale). Increases with corpus size; harmless to analyze. | ### MCP Serve-Time Measurements (Storage-Backed Slice) @@ -253,7 +253,7 @@ checkout**, with two explicit narrowings: This run does **prove**: -- The `@overload` fix unblocks `clarion analyze` against any real-world +- The `@overload` fix unblocks `loomweave analyze` against any real-world `src/` tree that uses `@overload`, `@typing.overload`, or `@typing_extensions.overload`. - The full elspeth corpus (1.48× the named slice's file count) fits @@ -274,11 +274,11 @@ the full elspeth corpus. The amendment exercises the inferred-edge LLM dispatch and cold-cache `summary` path that the [2026-05-18T01:14Z entry](#2026-05-18t0114z--green-full-elspeth-supplementary-storage-backed-slice) deliberately skipped. 99 of 100 tool calls returned ok; the one failure -is a Clarion-side defect (`clarion-df58379de4`, see below) not an +is a Loomweave-side defect (`clarion-df58379de4`, see below) not an external-provider failure. LLM cost was $0.897 USD on 219,006 tokens. scope: Same corpus, same analyze DB -(`/tmp/clarion-b8-elspeth-full-20260518T0016Z/.clarion/clarion.db`, +(`/tmp/loomweave-b8-elspeth-full-20260518T0016Z/.loomweave/loomweave.db`, run id `3461ded9-…`) as the 01:14Z entry. Before invoking the driver, `summary_cache` and `inferred_edge_cache` were both wiped so the run measures the true cold-LLM picture rather than warm-cache reads of @@ -288,7 +288,7 @@ prior calls. | Field | Value | |---|---| -| Clarion commit at run | `29f0426` (same as 01:14Z entry) | +| Loomweave commit at run | `29f0426` (same as 01:14Z entry) | | Driver command | `tests/perf/b8_scale_test/driver.py … --timeout-seconds 300` (no `--skip-inferred`) | | Driver output | `tests/perf/b8_scale_test/results/2026-05-18T0114Z/mcp-driver-output-live.json` | | Driver stderr | `tests/perf/b8_scale_test/results/2026-05-18T0114Z/mcp-driver-live.stderr` | @@ -398,7 +398,7 @@ future stress target on operator request. It does **not** reopen the [2026-05-17T22:43Z GREEN](#2026-05-17t2243z--green-rerun-superseding-red) verdict on the named slice. -reason: `clarion analyze` failed mid-run with a UNIQUE constraint violation on +reason: `loomweave analyze` failed mid-run with a UNIQUE constraint violation on `entities.id`. The Python plugin emits one entity per `def`, ignoring `@typing.overload` stub signatures that legitimately share a qualname with their implementation. The named B.8 slice did not surface this because elspeth @@ -415,20 +415,20 @@ to surface. | Field | Value | |---|---| -| Clarion branch at run | `sprint-2/b8-scale-test` | -| Clarion base commit | `a80c31a` | -| Clarion working-tree changes | 3 src files modified (`crates/clarion-core/src/plugin/manifest.rs`, `crates/clarion-storage/migrations/0001_initial_schema.sql`, `crates/clarion-storage/tests/schema_apply.rs`); untracked `docs/clarion/adr/ADR-031-schema-validation-policy.md`. Binary rebuilt against this state. | +| Loomweave branch at run | `sprint-2/b8-scale-test` | +| Loomweave base commit | `a80c31a` | +| Loomweave working-tree changes | 3 src files modified (`crates/loomweave-core/src/plugin/manifest.rs`, `crates/loomweave-storage/migrations/0001_initial_schema.sql`, `crates/loomweave-storage/tests/schema_apply.rs`); untracked `docs/loomweave/adr/ADR-031-schema-validation-policy.md`. Binary rebuilt against this state. | | Corpus source | `/home/john/elspeth/` (full checkout, not just `tests/`) | | Corpus commit | `9d3fd55d63bac764c88af04330af2c3f4f651346` | -| Corpus dirty state | 11 modified files (composer ux-redesign docs and two orchestrator src files); recorded at `/tmp/clarion-b8-full-elspeth-status.txt` | -| Scratch corpus path | `/tmp/clarion-b8-elspeth-full-20260518T0016Z` | +| Corpus dirty state | 11 modified files (composer ux-redesign docs and two orchestrator src files); recorded at `/tmp/loomweave-b8-full-elspeth-status.txt` | +| Scratch corpus path | `/tmp/loomweave-b8-elspeth-full-20260518T0016Z` | | Scratch corpus selection | rsync of `*.py` outside `.venv`, `.uv-cache`, `.worktrees`, `node_modules`, `.git`, `__pycache__`, `build`, `dist` | | Python files in scratch | 1,532 | | Python LOC in scratch | 611,220 | | Raw artifacts | `tests/perf/b8_scale_test/results/2026-05-18T0017Z/` | -| Install command | `clarion install --path ` (run with python plugin venv bin on PATH) | -| Analyze command | `target/release/clarion analyze /tmp/clarion-b8-elspeth-full-20260518T0016Z` | -| RSS sampler | `/tmp/clarion-b8-analyze-with-rss.py` (250 ms poll over proc + 2 levels of descendants) | +| Install command | `loomweave install --path ` (run with python plugin venv bin on PATH) | +| Analyze command | `target/release/loomweave analyze /tmp/loomweave-b8-elspeth-full-20260518T0016Z` | +| RSS sampler | `/tmp/loomweave-b8-analyze-with-rss.py` (250 ms poll over proc + 2 levels of descendants) | | MCP driver | not run (no usable DB) | | Filigree route | not exercised | @@ -449,7 +449,7 @@ file count (1,532 vs 1,037), 1.42× the LOC (611k vs 430k), and includes | Wall-clock vs 60m envelope | well inside (12.99% of envelope), but irrelevant — run did not complete | | Peak RSS (sampled) | 185,541,632 bytes / 176.94 MiB | | Peak RSS caveat | sampler swept process + 2 generations of descendants at 250 ms; prior tests-slice run reported 1.94 GiB peak via a different harness. The 11.0× discrepancy is likely sampler under-coverage of pyright subprocess RSS rather than a real memory reduction — treat this RSS number as a lower bound, not a measurement comparable to the prior run. | -| `.clarion/clarion.db` size at FailRun | 47.25 MiB | +| `.loomweave/loomweave.db` size at FailRun | 47.25 MiB | | Discovery/source walk | ~0.013s from log start to `source tree walk complete` | | Plugin processing | ~464.467s from `processing plugin` to `plugin host collected findings` (Python plugin completed all 1,526 files it was handed) | | Commit/close phase | did not reach successful completion; writer-actor aborted with UNIQUE constraint failure 3.563s after host findings | @@ -495,8 +495,8 @@ Plugin-host findings observed in stderr (informational; not persisted): | Subcode | Count | Materiality | |---|---:|---| -| `CLA-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE` | 6 | Same malformed `callee_expr > 512 bytes` signal observed on the named slice (8 there); harmless | -| `CLA-PY-PYRIGHT-*` | 0 | No pyright lifecycle finding surfaced | +| `LMWV-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE` | 6 | Same malformed `callee_expr > 512 bytes` signal observed on the named slice (8 there); harmless | +| `LMWV-PY-PYRIGHT-*` | 0 | No pyright lifecycle finding surfaced | ### Failure Detail @@ -523,7 +523,7 @@ Blast radius at this scale: 2 files use `@overload` (both in `src/elspeth/core/l No files in elspeth `tests/` use `@overload`, which is why the named B.8 slice did not surface this. The pattern is standard in typed Python libraries; the -defect will recur whenever Clarion analyzes any real `src/` tree that uses +defect will recur whenever Loomweave analyzes any real `src/` tree that uses `@overload`, `@typing.overload`, or — by the same reasoning — any decorator pattern that produces multiple `def`s sharing a qualname. @@ -538,7 +538,7 @@ characterise the supplementary corpus either (the run did not complete). A sanitised rerun against the full corpus minus the 2 overload-using files was considered and rejected as scope escalation: the methodology's job here is to -faithfully record what `clarion analyze` does against the full elspeth tree, +faithfully record what `loomweave analyze` does against the full elspeth tree, not to engineer a workaround so the harness can claim "all 7 tools exercised." ### NFR And Gate Outcome @@ -591,7 +591,7 @@ rollback_action: - Follow-up filed: `clarion-ac5f9bf35b` — OpenRouter-backed summary and inferred MCP paths return invalid JSON. -reason: `clarion analyze` completed within the v0.1 scale envelope and the +reason: `loomweave analyze` completed within the v0.1 scale envelope and the storage-backed MCP tools returned useful bounded responses, but every live OpenRouter-backed `summary()` call and every inferred-confidence dispatch failed with `llm-invalid-json`. The B.8 "all 7 tools" proof is therefore not true, and @@ -601,18 +601,18 @@ the NFR-COST-02 summary-cache hit-rate target is unmeasurable rather than green. | Field | Value | |---|---| -| Clarion branch at run | `sprint-2/b8-scale-test` | -| Clarion commit at run | `80a6af9` | +| Loomweave branch at run | `sprint-2/b8-scale-test` | +| Loomweave commit at run | `80a6af9` | | Corpus source | `/home/john/elspeth/tests` | | Corpus commit | `deab8f5b21335f37e72ed70fb494a30e2c237b21` | | Corpus dirty state | one unrelated untracked doc: `docs/superpowers/plans/2026-05-18-report-assemble-aggregation.md` | -| Scratch corpus path | `/tmp/clarion-b8-elspeth-tests-20260517T2156Z` | +| Scratch corpus path | `/tmp/loomweave-b8-elspeth-tests-20260517T2156Z` | | Python files | 1,037 | | Python LOC | 429,870 | | Raw artifacts | `tests/perf/b8_scale_test/results/2026-05-17T2156Z/` | -| Analyze command | `target/release/clarion analyze /tmp/clarion-b8-elspeth-tests-20260517T2156Z` | +| Analyze command | `target/release/loomweave analyze /tmp/loomweave-b8-elspeth-tests-20260517T2156Z` | | Serve driver | `tests/perf/b8_scale_test/driver.py` | -| Serve config | `/tmp/clarion-b8-elspeth-tests-20260517T2156Z/clarion-b8-live.yaml` | +| Serve config | `/tmp/loomweave-b8-elspeth-tests-20260517T2156Z/loomweave-b8-live.yaml` | | Filigree route | real dashboard at `http://127.0.0.1:9388/api/entity-associations` | This was the representative elspeth-slice requested by B.8, not the fallback @@ -628,8 +628,8 @@ excluded non-Python files. | Total wall-clock | 447.154s / 7m27s | | NFR-PERF-01 limit | 60m | | Peak RSS | 2,033,442,816 bytes / 1,939.242 MiB | -| `.clarion/clarion.db` size | 173 MiB | -| `.clarion/` size at close | 173 MiB | +| `.loomweave/loomweave.db` size | 173 MiB | +| `.loomweave/` size at close | 173 MiB | | Discovery/source walk | ~0.002s from first log to `source tree walk complete` | | Plugin processing | ~441.346s from `processing plugin` to host findings | | Commit/close flush | ~5.699s from host findings to `plugin complete` | @@ -677,8 +677,8 @@ Analyze findings emitted by code: | Code | Count | Materiality | |---|---:|---| -| `CLA-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE` | 8 | Material follow-up signal; overlong `callee_expr` entries are dropped from the unresolved-site side table | -| `CLA-PY-PYRIGHT-*` | 0 | No pyright lifecycle finding surfaced | +| `LMWV-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE` | 8 | Material follow-up signal; overlong `callee_expr` entries are dropped from the unresolved-site side table | +| `LMWV-PY-PYRIGHT-*` | 0 | No pyright lifecycle finding surfaced | ### B.4* Extrapolation Check @@ -799,13 +799,13 @@ cap pressure appeared in this run. | Successful LLM-backed responses | 0 | | `summary_cache` rows after run | 0 | | `inferred_edge_cache` rows after run | 0 | -| Clarion-reported prompt tokens | 0 | -| Clarion-reported completion tokens | 0 | -| Clarion-reported total tokens | 0 | -| Estimated dollar cost | not computable from Clarion artifacts | +| Loomweave-reported prompt tokens | 0 | +| Loomweave-reported completion tokens | 0 | +| Loomweave-reported total tokens | 0 | +| Estimated dollar cost | not computable from Loomweave artifacts | The token/cost ceiling could not be validated. This does not mean the run was -free; it means the provider path returned text that failed Clarion's JSON +free; it means the provider path returned text that failed Loomweave's JSON contract before the MCP envelope surfaced usage accounting. The v0.2 follow-up must either enforce JSON-mode/provider constraints or preserve usage tokens even when the semantic JSON parse fails. @@ -861,8 +861,8 @@ supersedes: **2026-05-17T21:56Z — RED** for the live LLM JSON/cost/cache blocker tracked as `clarion-ac5f9bf35b`. This rerun used the same analyzed elspeth-slice database from the RED entry: -`/tmp/clarion-b8-elspeth-tests-20260517T2156Z/.clarion/clarion.db`. No analyze -rerun was performed; the proof is scoped to the missing `clarion serve` live +`/tmp/loomweave-b8-elspeth-tests-20260517T2156Z/.loomweave/loomweave.db`. No analyze +rerun was performed; the proof is scoped to the missing `loomweave serve` live OpenRouter/cache evidence. Raw artifacts: @@ -870,7 +870,7 @@ Raw artifacts: - Cold-cache repair run: `tests/perf/b8_scale_test/results/2026-05-17T2243Z/mcp-driver-output.json` - Warm-cache steady-state rerun: `tests/perf/b8_scale_test/results/2026-05-17T2243Z/mcp-driver-output-warm-cache.json` -Clarion source at run time: branch `sprint-2/b8-scale-test`, base commit +Loomweave source at run time: branch `sprint-2/b8-scale-test`, base commit `e6bba0f`, with the B.8 repair patch in the working tree. Filigree HTTP was the real dashboard route at `http://127.0.0.1:9388/api/entity-associations`. diff --git a/docs/implementation/sprint-2/b8-scale-test.md b/docs/implementation/sprint-2/b8-scale-test.md index afa6fc04..9bde8441 100644 --- a/docs/implementation/sprint-2/b8-scale-test.md +++ b/docs/implementation/sprint-2/b8-scale-test.md @@ -9,7 +9,7 @@ B.8 is the Sprint 2 full-system validation pass: -- Run `clarion analyze` against the elspeth validation corpus. +- Run `loomweave analyze` against the elspeth validation corpus. - Start the MCP surface from B.6. - Verify an agent can navigate real code with `entity_at`, `find_entity`, `callers_of`, `execution_paths_from`, `summary`, `issues_for`, and `neighborhood`. - Record scale evidence for wall-clock time, memory, store size, edge counts, confidence distribution, and MCP query latency. diff --git a/docs/implementation/sprint-2/openrouter-provider-swap.md b/docs/implementation/sprint-2/openrouter-provider-swap.md index 64d11241..f7bc9b2a 100644 --- a/docs/implementation/sprint-2/openrouter-provider-swap.md +++ b/docs/implementation/sprint-2/openrouter-provider-swap.md @@ -8,7 +8,7 @@ Branch: `sprint-2/openrouter-swap` ## Purpose This memo scopes the focused provider replacement that must land before B.8. -B.8 should measure the provider Clarion actually ships with in v0.1, so the +B.8 should measure the provider Loomweave actually ships with in v0.1, so the just-shipped `AnthropicProvider` is replaced by an `OpenRouterProvider`. The `LlmProvider` trait remains. OpenRouter is the canonical v0.1 provider; @@ -17,7 +17,7 @@ added in v0.2+ without changing MCP call sites. ## S1. Wire Format -Clarion speaks OpenAI-compatible Chat Completions HTTP to OpenRouter. +Loomweave speaks OpenAI-compatible Chat Completions HTTP to OpenRouter. - Base URL default: `https://openrouter.ai/api/v1` - Chat endpoint: `POST {endpoint_url}/chat/completions` @@ -26,12 +26,12 @@ Clarion speaks OpenAI-compatible Chat Completions HTTP to OpenRouter. - Request body: `model`, `messages`, `temperature`, and a completion limit (`max_completion_tokens` preferred; `max_tokens` accepted/deprecated by OpenRouter), plus future OpenAI-compatible fields as needed. -- v0.1 message shape: one user message containing the rendered Clarion prompt. +- v0.1 message shape: one user message containing the rendered Loomweave prompt. - Response body: `choices[]` with a non-streaming `message`, plus `usage`. The response parser consumes the first choice whose `message.content` is non-empty. `usage.prompt_tokens`, `usage.completion_tokens`, and -`usage.total_tokens` are copied into Clarion's response/accounting path. +`usage.total_tokens` are copied into Loomweave's response/accounting path. Streaming is out of scope for this swap. Sources: OpenRouter quickstart and API reference describe `/api/v1/chat/completions`, @@ -39,16 +39,16 @@ Bearer auth, OpenAI-compatible messages, non-streaming choices, and usage. ## S2. OpenRouter Attribution Headers -Clarion sends attribution headers on live OpenRouter calls: +Loomweave sends attribution headers on live OpenRouter calls: -- `HTTP-Referer`: default `https://github.com/qacona/clarion` -- `X-OpenRouter-Title`: default `Clarion` +- `HTTP-Referer`: default `https://github.com/qacona/loomweave` +- `X-OpenRouter-Title`: default `Loomweave` OpenRouter documents `X-OpenRouter-Title` as the canonical app-title header and -still accepts `X-Title` for backwards compatibility. Clarion should emit the +still accepts `X-Title` for backwards compatibility. Loomweave should emit the canonical header. -Both values are configurable in `clarion.yaml`. They are not required for auth. +Both values are configurable in `loomweave.yaml`. They are not required for auth. `HTTP-Referer` is required for app attribution/rankings; `X-OpenRouter-Title` sets the display name when paired with `HTTP-Referer`, and `X-Title` is accepted for backwards compatibility. @@ -65,7 +65,7 @@ API and normally include an organization/provider prefix, such as: Examples are illustrative; defaults should be refreshed from `/api/v1/models` before release. -Clarion does not implement tier-name to model-ID resolution in v0.1. Operators +Loomweave does not implement tier-name to model-ID resolution in v0.1. Operators choose the concrete `model_id` in config. ADR-007's `model_tier` cache-key component stores that concrete string verbatim. The cache-key shape is unchanged; only values move from native Anthropic model IDs to OpenRouter `vendor/model` @@ -76,19 +76,19 @@ strings. B.6 added Anthropic-specific pricing tables and dollar-budget estimates. Those tables are removed. -Clarion records token counts from OpenRouter usage: +Loomweave records token counts from OpenRouter usage: - prompt tokens - completion tokens - total tokens -Cost-per-token math stays outside Clarion. Operators already have OpenRouter's -billing dashboard and can choose models based on their own budget. Clarion's +Cost-per-token math stays outside Loomweave. Operators already have OpenRouter's +billing dashboard and can choose models based on their own budget. Loomweave's enforcement becomes a token ceiling: - Config: `llm_policy.session_token_ceiling` - Default: `1000000` -- Scope: one `clarion serve` process/session +- Scope: one `loomweave serve` process/session - Cache hits do not spend tokens Existing cache storage columns named `cost_usd` can remain for compatibility and @@ -110,20 +110,20 @@ Classification: - 4xx auth, insufficient credits, forbidden, moderation, model/request errors: non-retryable unless OpenRouter explicitly returns a retryable code. -- 429 and 503: OpenRouter may return a standard `Retry-After` header. Clarion +- 429 and 503: OpenRouter may return a standard `Retry-After` header. Loomweave v0.1 may choose not to sleep-and-retry inside a single MCP call, but the provider/MCP envelope must preserve retryability and retry-after context so callers can retry later. - 5xx and connection/transport failures: retryable. -- Malformed JSON or schema mismatch is a Clarion parse/transport error; - retryability is Clarion policy, not an OpenRouter-documented cause. +- Malformed JSON or schema mismatch is a Loomweave parse/transport error; + retryability is Loomweave policy, not an OpenRouter-documented cause. MCP envelopes should preserve retryability instead of always marking provider errors retryable. ## S6. Config Surface -`clarion.yaml`: +`loomweave.yaml`: ```yaml llm_policy: @@ -134,8 +134,8 @@ llm_policy: endpoint_url: https://openrouter.ai/api/v1 api_key_env: OPENROUTER_API_KEY attribution: - referer: https://github.com/qacona/clarion - title: Clarion + referer: https://github.com/qacona/loomweave + title: Loomweave model_id: anthropic/claude-sonnet-4.6 session_token_ceiling: 1000000 max_inferred_edges_per_caller: 8 @@ -148,11 +148,11 @@ OpenRouter direct. The existing implementation also accepts the earlier `llm:` key as a compatibility alias. The old `anthropic_api_key_env` or `provider: anthropic` -shape should produce a clear `CLA-CONFIG-DEPRECATED-PROVIDER` configuration +shape should produce a clear `LMWV-CONFIG-DEPRECATED-PROVIDER` configuration error/finding that points operators to `provider: openrouter` and `llm_policy.openrouter.api_key_env`. -`clarion serve` must not panic at startup with no API key. If live provider use +`loomweave serve` must not panic at startup with no API key. If live provider use is not explicitly enabled, no provider is constructed. If live use is enabled and the configured env var is missing, config validation reports the missing OpenRouter key. @@ -200,7 +200,7 @@ Corrections folded from review: - Remove unsupported free-tier attribution wording and undocumented parse-cause wording. - Treat OpenRouter usage as token accounting and do not recompute provider - pricing inside Clarion. + pricing inside Loomweave. ## Task Ledger @@ -208,8 +208,8 @@ Corrections folded from review: 2. TDD provider replacement: happy path, error envelope, choice-level error, retryability, headers, and usage tokens. 3. Remove Anthropic pricing math and switch MCP ledger to token ceiling. -4. Update `clarion.yaml` parsing and deprecation diagnostics. -5. Wire `clarion serve` to construct `OpenRouterProvider`. +4. Update `loomweave.yaml` parsing and deprecation diagnostics. +5. Wire `loomweave serve` to construct `OpenRouterProvider`. 6. Keep `RecordingProvider` trait compatibility. 7. Amend ADR-030, ADR-007, and operator docs. 8. Extend the Sprint 2 MCP e2e with a RecordingProvider `summary()` smoke. diff --git a/docs/implementation/sprint-2/scope-amendment-2026-05.md b/docs/implementation/sprint-2/scope-amendment-2026-05.md index 1ae57498..605e192f 100644 --- a/docs/implementation/sprint-2/scope-amendment-2026-05.md +++ b/docs/implementation/sprint-2/scope-amendment-2026-05.md @@ -37,7 +37,7 @@ The kickoff handoff named seven Tier B boxes. Two warmup-bug fixes were also in | Filigree issue | Status | |---|---| | `clarion-5e03cfdd21` — `read_applied_versions` swallows DB errors | **fixed** in PR #3 (merge `da45823`, fix `ad2936b`) | -| `clarion-ed5017139f` — `clarion install` partial `.clarion/` on failure | **still open** (P2, ready) | +| `clarion-ed5017139f` — `loomweave install` partial `.loomweave/` on failure | **still open** (P2, ready) | | `clarion-b5b1029f5a` — `reader_pool` flaky 100ms sleep | **still open** (P2, ready) | | `clarion-4cd11905e2` — `entities.priority` TEXT affinity wrong-order | **resolved** by ADR-024 vocab rename (priority→scope_rank with INTEGER affinity) | @@ -49,7 +49,7 @@ These were not in the kickoff but shipped during the same window: - **ADR-025** — Minor shared standards registry; first entry MSS-1 locks `tier:*` filigree label namespace. - **ADR-026** — Containment wire shape and edge identity (load-bearing for B.3 design). - **ADR-027** — Ontology version semver policy (clarifies ADR-022). -- **Loom vocabulary glossary** — `docs/suite/loom.md` glossary clause + ADR-acceptance rule. +- **Weft vocabulary glossary** — `docs/suite/weft.md` glossary clause + ADR-acceptance rule. - **Skeleton audit doc** — `docs/implementation/sprint-2/...` audit pass with 5 findings (F-13 through F-17, all open in filigree). The unplanned ADR cluster (024–027) was net positive: each was a real surface lock-in that downstream sprints need. The skeleton audit findings (F-13 through F-17) are not Sprint 2 blockers but should be triaged before WP9 (B.7 new scope, below) touches any of the affected surfaces. @@ -97,9 +97,9 @@ Rationale: each of these is a step toward briefings and clustered subsystem view |---|---|---|---| | **B.4*** — `calls` edges via pyright + confidence tiers | WP3 + ADR-028 | Python plugin emits `calls` edges with `confidence` ∈ {`resolved`, `ambiguous`, `inferred`}; pyright integration; **week-2 go/no-go gate**: can pyright extract elspeth's calls in <5 min? | ADR-026, **ADR-028** | | **B.5*** — `references` edges | WP3 + ADR-028 | Python plugin emits `references` edges; same confidence-tier discipline as B.4* | ADR-026, **ADR-028** | -| **B.6** — WP8 MCP surface (7 tools) | WP8 | New `clarion-mcp` crate exposes: `entity_at(file, line)`, `find_entity(pattern)`, `callers_of(id, confidence)`, `execution_paths_from(id, max_depth, confidence)`, `summary(id)`, `issues_for(id, include_contained)`, `neighborhood(id, confidence)` | ADR-012, **ADR-028**, **ADR-029**, **ADR-030** | -| **B.7** — WP9-A entity_associations binding | WP9-A (split from WP9) | Filigree-side `entity_associations` migration; Filigree MCP gains `add_entity_association` / `remove_entity_association` / `list_entity_associations`; Clarion MCP gains `issues_for` | **ADR-029** | -| **B.8** — elspeth scale-test (was original B.6) | original B.6 / WP11 spike | `clarion analyze` + `clarion serve` against elspeth-slice; agent can navigate via the 7 MCP tools; cost ceiling sanity-check on `summary(id)` | — | +| **B.6** — WP8 MCP surface (7 tools) | WP8 | New `loomweave-mcp` crate exposes: `entity_at(file, line)`, `find_entity(pattern)`, `callers_of(id, confidence)`, `execution_paths_from(id, max_depth, confidence)`, `summary(id)`, `issues_for(id, include_contained)`, `neighborhood(id, confidence)` | ADR-012, **ADR-028**, **ADR-029**, **ADR-030** | +| **B.7** — WP9-A entity_associations binding | WP9-A (split from WP9) | Filigree-side `entity_associations` migration; Filigree MCP gains `add_entity_association` / `remove_entity_association` / `list_entity_associations`; Loomweave MCP gains `issues_for` | **ADR-029** | +| **B.8** — elspeth scale-test (was original B.6) | original B.6 / WP11 spike | `loomweave analyze` + `loomweave serve` against elspeth-slice; agent can navigate via the 7 MCP tools; cost ceiling sanity-check on `summary(id)` | — | Status drift requirements: @@ -112,7 +112,7 @@ Status drift requirements: | ADR | What it locks | |---|---| | **ADR-028** — Edge confidence tiers | resolved / ambiguous / inferred; default MCP queries to `>=resolved`; lazy LLM compute at query time for inferred | -| **ADR-029** — Entity associations binding | Filigree-side `entity_associations` table; `add_entity_association` MCP tool on Filigree; `issues_for` MCP tool on Clarion; content-hash drift detection; federation §5 audit; WP9 split A/B | +| **ADR-029** — Entity associations binding | Filigree-side `entity_associations` table; `add_entity_association` MCP tool on Filigree; `issues_for` MCP tool on Loomweave; content-hash drift detection; federation §5 audit; WP9 split A/B | | **ADR-030** — On-demand summary scope | Narrow WP6 from batched Phases 4–6 to MCP-driven `summary(id)`; 5-tuple cache key (ADR-007) unchanged; module/subsystem aggregation deferred to v0.2 | All three are Accepted at the same time as this memo lands. @@ -134,19 +134,19 @@ This memo is the authoritative resequence record. `v0.1-plan.md` gets a forward- | WP | Original scope | Narrowed scope | Defers to v0.2 | |---|---|---|---| -| WP3 — Python plugin v0.1 | Functions only at Sprint-1 close → full ontology + edges (calls, imports, decorated_by, inherits_from) + full `CLA-PY-*` rules | + class + module (B.2 ✅) + contains (B.3) + **calls with confidence** (B.4*) + **references** (B.5*) | imports, decorated_by, inherits_from, `CLA-PY-*` finding rules | +| WP3 — Python plugin v0.1 | Functions only at Sprint-1 close → full ontology + edges (calls, imports, decorated_by, inherits_from) + full `LMWV-PY-*` rules | + class + module (B.2 ✅) + contains (B.3) + **calls with confidence** (B.4*) + **references** (B.5*) | imports, decorated_by, inherits_from, `LMWV-PY-*` finding rules | | WP6 — LLM dispatch + cache (Phases 4–6) | Batched-pipeline summarisation across leaf / module / subsystem tiers | **On-demand `summary(id)` MCP tool only**, leaf tier only (ADR-030) | Phases 4–6 batched pipeline; module/subsystem aggregation; `--prewarm-summaries` | -| WP9 — Loom integrations (Clarion-side) | Findings emission + entity binding + Wardline config ingest + suite-compat probe | **WP9-A only — `entity_associations` binding** (B.7) | WP9-B: findings emission to Filigree, Wardline config ingest, suite-compat probe, observation MCP-spawn | +| WP9 — Weft integrations (Loomweave-side) | Findings emission + entity binding + Wardline config ingest + suite-compat probe | **WP9-A only — `entity_associations` binding** (B.7) | WP9-B: findings emission to Filigree, Wardline config ingest, suite-compat probe, observation MCP-spawn | ### WPs deferred to v0.2 (out of Sprint 2 entirely) | WP | Original Sprint-2 portion deferred | Rationale | |---|---|---| -| **WP4** — Core-only pipeline | Phases 0–3 multi-file orchestration, Phase 3 clustering (ADR-006 Leiden/Louvain), Phase 7 cross-cutting `CLA-*` rules, Phase 8 entity-set diff | The MVP MCP surface queries the SQLite store directly; the pipeline orchestrator and clustering layer aren't on the critical path until briefings / subsystem rendering land | +| **WP4** — Core-only pipeline | Phases 0–3 multi-file orchestration, Phase 3 clustering (ADR-006 Leiden/Louvain), Phase 7 cross-cutting `LMWV-*` rules, Phase 8 entity-set diff | The MVP MCP surface queries the SQLite store directly; the pipeline orchestrator and clustering layer aren't on the critical path until briefings / subsystem rendering land | | **WP5** — Pre-ingest secret scanner | All | Defensible to defer because Sprint 2's MVP runs on a known-safe corpus (elspeth-slice); production deployment against unknown corpora gates on this returning | -| **WP7** — Guidance system | All | The guidance composition algorithm and `CLA-FACT-GUIDANCE-*` findings have no MCP-tool consumer until briefings ship | +| **WP7** — Guidance system | All | The guidance composition algorithm and `LMWV-FACT-GUIDANCE-*` findings have no MCP-tool consumer until briefings ship | | **WP10** — Cross-product (Filigree-side `registry_backend`, SARIF translator) | All | Independent of MCP surface delivery; will land in v0.2 | -| **WP11** — Cost validation spike (originally scheduled) | The originally-scheduled batched-pipeline cost measurement | **Re-scoped to B.8** as the elspeth scale-test gate; measures `summary(id)` per-query cost rather than `clarion analyze` per-run cost, which is the more honest metric for an on-demand tool | +| **WP11** — Cost validation spike (originally scheduled) | The originally-scheduled batched-pipeline cost measurement | **Re-scoped to B.8** as the elspeth scale-test gate; measures `summary(id)` per-query cost rather than `loomweave analyze` per-run cost, which is the more honest metric for an on-demand tool | ### Critical path under the amendment @@ -205,7 +205,7 @@ Existing open Sprint-2 issues that stay open: ## 7. What this memo does NOT change - Sprint 1 lock-ins (L1–L9) — all unchanged. Walking-skeleton CI stays green. -- Loom federation axiom (`loom.md` §5) — ADR-029 explicitly audits against it; no relaxation. +- Weft federation axiom (`weft.md` §5) — ADR-029 explicitly audits against it; no relaxation. - ADRs 001–027 — all Accepted, all unchanged. The three new ADRs (028/029/030) are additive. - Sprint-1 signoff ladder format — Sprint 2's eventual close (when B.8 ships) will follow the same shape, but is not pre-written here. - The original WP6 design (Phases 4–6 batched pipeline) — preserved as the v0.2 target architecture; ADR-030 narrows what ships in v0.1, not what the system aims at long-term. @@ -219,7 +219,7 @@ Existing open Sprint-2 issues that stay open: - [Sprint 2 kickoff handoff](../handoffs/2026-04-30-sprint-2-kickoff.md) — the original 7-box scope - [B.2 design](./b2-class-module-entities.md) — shipped - [B.3 design](./b3-contains-edges.md) — implementation pending -- [ADR-028](../../clarion/adr/ADR-028-edge-confidence-tiers.md) — new -- [ADR-029](../../clarion/adr/ADR-029-entity-associations-binding.md) — new -- [ADR-030](../../clarion/adr/ADR-030-on-demand-summary-scope.md) — new -- [`loom.md` §5](../../suite/loom.md) — federation failure modes; ADR-029 audits against this +- [ADR-028](../../loomweave/adr/ADR-028-edge-confidence-tiers.md) — new +- [ADR-029](../../loomweave/adr/ADR-029-entity-associations-binding.md) — new +- [ADR-030](../../loomweave/adr/ADR-030-on-demand-summary-scope.md) — new +- [`weft.md` §5](../../suite/weft.md) — federation failure modes; ADR-029 audits against this diff --git a/docs/implementation/sprint-2/signoffs.md b/docs/implementation/sprint-2/signoffs.md index bc169ac1..7e947cdd 100644 --- a/docs/implementation/sprint-2/signoffs.md +++ b/docs/implementation/sprint-2/signoffs.md @@ -1,4 +1,4 @@ -# Clarion Sprint 2 — Sign-off Ladder +# Loomweave Sprint 2 — Sign-off Ladder **Status**: CLOSED — GREEN after B.8 repair rerun **Scope**: B.3, B.4*, B.5*, B.6, B.7, B.8 from @@ -49,14 +49,14 @@ remain part of the audit trail. ### A.4 B.6 — Seven-Tool MCP Surface - [x] **Design doc commit**: `6a9a7b2` (`docs(wp8): design B.6 MCP surface`). -- [x] **Implementation range**: `b0a12a6` → `a53d2e4` (MCP stdio server, `clarion serve`, storage-backed tools, summary cache, inferred dispatch, `issues_for`, e2e observability, OpenRouter provider swap). +- [x] **Implementation range**: `b0a12a6` → `a53d2e4` (MCP stdio server, `loomweave serve`, storage-backed tools, summary cache, inferred dispatch, `issues_for`, e2e observability, OpenRouter provider swap). - [x] **Panel record**: [`b6-mcp-surface.md`](./b6-mcp-surface.md) Stage 0 panel record and reconciliation. - [x] **Exit criteria attestation**: Filigree `clarion-e2a3672cc9` closed `done`; B.6 local gates passed with RecordingProvider coverage, not live-provider proof. ### A.5 B.7 — Entity Associations Binding -- [x] **Design source**: [ADR-029](../../clarion/adr/ADR-029-entity-associations-binding.md) and B.6 `issues_for` integration design. -- [x] **Implementation artifacts**: Filigree PR 42 merged; Clarion-side `issues_for` integration commits include `16634ae` and `29d3865`. +- [x] **Design source**: [ADR-029](../../loomweave/adr/ADR-029-entity-associations-binding.md) and B.6 `issues_for` integration design. +- [x] **Implementation artifacts**: Filigree PR 42 merged; Loomweave-side `issues_for` integration commits include `16634ae` and `29d3865`. - [x] **Panel record**: ADR-029 federation audit and B.6 Filigree reverse-route review. - [x] **Exit criteria attestation**: Filigree `clarion-73ab0da435` closed `done`; B.8 measured the real reverse route at p95 3.262 ms. diff --git a/docs/implementation/sprint-3/2026-05-19-loom-federation-hardening-tasking.md b/docs/implementation/sprint-3/2026-05-19-loom-federation-hardening-tasking.md index a17712e1..38f933ba 100644 --- a/docs/implementation/sprint-3/2026-05-19-loom-federation-hardening-tasking.md +++ b/docs/implementation/sprint-3/2026-05-19-loom-federation-hardening-tasking.md @@ -1,14 +1,14 @@ -# Loom Federation Hardening Tasking +# Weft Federation Hardening Tasking > **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:subagent-driven-development` or `superpowers:executing-plans` to implement this tasking task-by-task. Steps use checkbox syntax for tracking. -**Goal:** Bring Clarion's HTTP read API and ADR-014 federation contract up to the shared Loom standard required for Clarion 1.0 and Filigree 2.1 registry-backend mode. +**Goal:** Bring Loomweave's HTTP read API and ADR-014 federation contract up to the shared Weft standard required for Loomweave 1.0 and Filigree 2.1 registry-backend mode. -**Release stance:** This is release-blocking unless ADR-014 Clarion registry mode is explicitly de-scoped from the Clarion 1.0 and Filigree 2.1 tag notes. The current RC1 tests are green, but they validate the old contract shape rather than the hardened federation contract. +**Release stance:** This is release-blocking unless ADR-014 Loomweave registry mode is explicitly de-scoped from the Loomweave 1.0 and Filigree 2.1 tag notes. The current RC1 tests are green, but they validate the old contract shape rather than the hardened federation contract. -**Architecture:** Clarion owns the publisher side of the file-registry read contract. Filigree's side should implement against Clarion's documented decisions, so Clarion must first pin the wire semantics in ADR-014, then make storage and HTTP behavior match the documented contract. +**Architecture:** Loomweave owns the publisher side of the file-registry read contract. Filigree's side should implement against Loomweave's documented decisions, so Loomweave must first pin the wire semantics in ADR-014, then make storage and HTTP behavior match the documented contract. -**Tech Stack:** Rust workspace, `axum`, `tower`/`tower-http`, `rusqlite`, `deadpool`, `serde`, `serde_json`, `uuid`, Clarion's local Filigree tracker. +**Tech Stack:** Rust workspace, `axum`, `tower`/`tower-http`, `rusqlite`, `deadpool`, `serde`, `serde_json`, `uuid`, Loomweave's local Filigree tracker. --- @@ -16,15 +16,15 @@ Filigree reported, and RC1 source confirms, that the contract hardening prompt is largely not landed: -- C1: `resolve_file` storage errors still map to HTTP 400 with raw `err.to_string()` at `crates/clarion-cli/src/http_read.rs`. -- C2/C5/C11: `resolve_file` still requires disk existence via canonicalization, still synthesizes `core:file:{content_hash}@{canonical_path}`, and still converts unreadable hash failures to empty strings in `crates/clarion-storage/src/query.rs`. +- C1: `resolve_file` storage errors still map to HTTP 400 with raw `err.to_string()` at `crates/loomweave-cli/src/http_read.rs`. +- C2/C5/C11: `resolve_file` still requires disk existence via canonicalization, still synthesizes `core:file:{content_hash}@{canonical_path}`, and still converts unreadable hash failures to empty strings in `crates/loomweave-storage/src/query.rs`. - C3: federation fixtures are still bare request/response snippets, and the file fixture still documents an invalid `@` entity ID. - C4/C7/C8/C9: HTTP read config has only `enabled` and `bind`; there is no non-loopback guard, no `allow_non_loopback`, no middleware stack, separate reader pools remain, and HTTP thread supervision only happens on shutdown. - C6/C10/C12/C13: joint contract decisions are not pinned; capabilities still return `version: "0.1"`, there is no `instance_id`, and errors are still `{ "error": String }`. -## Contract Decisions Clarion Must Own First +## Contract Decisions Loomweave Must Own First -Clarion owns these publisher-side choices. Land them in `docs/clarion/adr/ADR-014-filigree-registry-backend.md` before implementation so the Filigree agent has a stable reference: +Loomweave owns these publisher-side choices. Land them in `docs/loomweave/adr/ADR-014-filigree-registry-backend.md` before implementation so the Filigree agent has a stable reference: 1. **Capabilities version field:** use `api_version: 1`. `api_version` increments only when the HTTP read API wire contract changes incompatibly for existing Filigree clients. Do not use product semver here. @@ -32,8 +32,8 @@ Clarion owns these publisher-side choices. Land them in `docs/clarion/adr/ADR-01 Delete the synthetic `core:file:{content_hash}@{canonical_path}` branch. It violates ADR-003 and creates shadow IDs that will not match future file-discovery rows. 3. **`canonical_path` semantics:** project-relative POSIX path. No leading `/`, no leading `./`, no trailing `/`, separator `/`. This survives project relocation and matches current response intent. -4. **Instance fingerprint:** stable per-project UUID in `.clarion/instance_id`, surfaced as `instance_id` in capabilities. - First creation persists with mode `0600` on Unix. Deleting `.clarion/` may create a new instance ID; that is acceptable and should be detectable by Filigree. +4. **Instance fingerprint:** stable per-project UUID in `.loomweave/instance_id`, surfaced as `instance_id` in capabilities. + First creation persists with mode `0600` on Unix. Deleting `.loomweave/` may create a new instance ID; that is acceptable and should be detectable by Filigree. 5. **Error envelope:** closed shape `{ "error": String, "code": ErrorCode }`. Initial code enum: `INVALID_PATH`, `PATH_OUTSIDE_PROJECT`, `NOT_FOUND`, `STORAGE_ERROR`, `INTERNAL`. 6. **HTTP trust model:** unauthenticated loopback-only by default. @@ -53,10 +53,10 @@ T0 contract docs If the team needs parallelism after T0 lands: -- Worker A owns storage: `crates/clarion-storage/src/query.rs`, `crates/clarion-storage/tests/query_helpers.rs`. -- Worker B owns HTTP contract: `crates/clarion-cli/src/http_read.rs`, `crates/clarion-cli/tests/serve.rs`, fixtures. -- Worker C owns runtime guard/supervision: `crates/clarion-mcp/src/config.rs`, `crates/clarion-cli/src/serve.rs`, `crates/clarion-cli/src/http_read.rs`. -- Worker D owns docs: ADR-014, ADR-003, `docs/federation/contracts.md`, `docs/operator/clarion-http-read-api.md`, `docs/suite/loom.md`. +- Worker A owns storage: `crates/loomweave-storage/src/query.rs`, `crates/loomweave-storage/tests/query_helpers.rs`. +- Worker B owns HTTP contract: `crates/loomweave-cli/src/http_read.rs`, `crates/loomweave-cli/tests/serve.rs`, fixtures. +- Worker C owns runtime guard/supervision: `crates/loomweave-mcp/src/config.rs`, `crates/loomweave-cli/src/serve.rs`, `crates/loomweave-cli/src/http_read.rs`. +- Worker D owns docs: ADR-014, ADR-003, `docs/federation/contracts.md`, `docs/operator/loomweave-http-read-api.md`, `docs/suite/weft.md`. Workers are not alone in the codebase. Do not revert or overwrite unrelated RC1 work; keep each task's write set narrow. @@ -65,7 +65,7 @@ Workers are not alone in the codebase. Do not revert or overwrite unrelated RC1 Create one P0 epic: ```text -Sprint 3 RC1 — Loom federation HTTP read API hardening +Sprint 3 RC1 — Weft federation HTTP read API hardening ``` Create these child phases/tasks: @@ -84,9 +84,9 @@ Create these child phases/tasks: Use: ```bash -filigree --actor clarion-agent start-work --assignee clarion-agent -filigree --actor clarion-agent add-comment "summary, tests, commits" -filigree --actor clarion-agent close --reason="implemented and tested ..." +filigree --actor loomweave-agent start-work --assignee loomweave-agent +filigree --actor loomweave-agent add-comment "summary, tests, commits" +filigree --actor loomweave-agent close --reason="implemented and tested ..." ``` Do not close any tracker item until the code, tests, docs, and commit for that item exist. @@ -95,11 +95,11 @@ Do not close any tracker item until the code, tests, docs, and commit for that i **Files:** -- Modify: `docs/clarion/adr/ADR-014-filigree-registry-backend.md` -- Modify: `docs/clarion/adr/ADR-003-entity-id-scheme.md` +- Modify: `docs/loomweave/adr/ADR-014-filigree-registry-backend.md` +- Modify: `docs/loomweave/adr/ADR-003-entity-id-scheme.md` - Modify: `docs/federation/contracts.md` -- Create: `docs/operator/clarion-http-read-api.md` -- Modify: `docs/suite/loom.md` +- Create: `docs/operator/loomweave-http-read-api.md` +- Modify: `docs/suite/weft.md` **Acceptance criteria:** @@ -113,10 +113,10 @@ Do not close any tracker item until the code, tests, docs, and commit for that i - [ ] Patch ADR-014 with the six contract decisions above. - [ ] Patch ADR-003 with a file-kind ID grammar note and reject the former `content_hash@path` pattern as non-conforming. - [ ] Patch `docs/federation/contracts.md` to show the new request/response and error shapes. -- [ ] Create `docs/operator/clarion-http-read-api.md` with a `Trust model` section. -- [ ] Patch `docs/suite/loom.md` to link the operator trust model. -- [ ] Run `cargo test -p clarion-cli --test serve` to ensure docs-only edits did not disturb tests. -- [ ] Commit as `C0 docs: pin Loom federation read contract decisions`. +- [ ] Create `docs/operator/loomweave-http-read-api.md` with a `Trust model` section. +- [ ] Patch `docs/suite/weft.md` to link the operator trust model. +- [ ] Run `cargo test -p loomweave-cli --test serve` to ensure docs-only edits did not disturb tests. +- [ ] Commit as `C0 docs: pin Weft federation read contract decisions`. ## T1: Storage Resolution Correctness @@ -124,8 +124,8 @@ Do not close any tracker item until the code, tests, docs, and commit for that i **Files:** -- Modify: `crates/clarion-storage/src/query.rs` -- Modify: `crates/clarion-storage/tests/query_helpers.rs` +- Modify: `crates/loomweave-storage/src/query.rs` +- Modify: `crates/loomweave-storage/tests/query_helpers.rs` **Implementation requirements:** @@ -136,7 +136,7 @@ Do not close any tracker item until the code, tests, docs, and commit for that i - `file_content_hash` must return `Result` if still used as a fallback. - Never return empty `content_hash` because disk read failed. If no catalog hash exists and a hash fallback fails, propagate the error. - `canonical_path` returned by `resolve_file` must be project-relative POSIX and must not start with `/`, `./`, or `../`. -- Caller-supplied `language` must not poison the catalog response when Clarion can infer a better value. +- Caller-supplied `language` must not poison the catalog response when Loomweave can infer a better value. **Tests to add first:** @@ -149,8 +149,8 @@ Do not close any tracker item until the code, tests, docs, and commit for that i **Commands:** ```bash -cargo test -p clarion-storage resolve_file -- --nocapture -cargo test -p clarion-storage +cargo test -p loomweave-storage resolve_file -- --nocapture +cargo test -p loomweave-storage ``` **Commit guidance:** @@ -165,9 +165,9 @@ cargo test -p clarion-storage **Files:** -- Modify: `crates/clarion-cli/src/http_read.rs` -- Modify: `crates/clarion-cli/tests/serve.rs` -- May modify: `crates/clarion-storage/src/error.rs` only if a small helper is needed. +- Modify: `crates/loomweave-cli/src/http_read.rs` +- Modify: `crates/loomweave-cli/tests/serve.rs` +- May modify: `crates/loomweave-storage/src/error.rs` only if a small helper is needed. **Implementation requirements:** @@ -194,8 +194,8 @@ cargo test -p clarion-storage **Commands:** ```bash -cargo test -p clarion-cli --test serve -cargo test -p clarion-storage +cargo test -p loomweave-cli --test serve +cargo test -p loomweave-storage ``` **Commit guidance:** @@ -209,9 +209,9 @@ cargo test -p clarion-storage **Files:** -- Modify: `crates/clarion-cli/src/http_read.rs` -- Modify: `crates/clarion-cli/tests/serve.rs` -- Modify or create helper in: `crates/clarion-cli/src/serve.rs` or a small `instance.rs` module if that keeps `http_read.rs` focused. +- Modify: `crates/loomweave-cli/src/http_read.rs` +- Modify: `crates/loomweave-cli/tests/serve.rs` +- Modify or create helper in: `crates/loomweave-cli/src/serve.rs` or a small `instance.rs` module if that keeps `http_read.rs` focused. - Modify: `docs/federation/fixtures/get-api-v1-capabilities.json` **Implementation requirements:** @@ -227,14 +227,14 @@ cargo test -p clarion-storage } ``` -- On startup, read `.clarion/instance_id`; if absent, create a UUID v4 and persist it. +- On startup, read `.loomweave/instance_id`; if absent, create a UUID v4 and persist it. - On Unix, persist with mode `0600`. - The ID must survive process restarts. - The capability fixture must document the shape with `_meta`, `shape_decl`, and `examples`. **Tests to add first:** -- first startup creates `.clarion/instance_id` +- first startup creates `.loomweave/instance_id` - second startup reuses the same ID - capabilities returns `api_version: 1` - capabilities includes the stored `instance_id` @@ -243,13 +243,13 @@ cargo test -p clarion-storage **Commands:** ```bash -cargo test -p clarion-cli --test serve capabilities -- --nocapture +cargo test -p loomweave-cli --test serve capabilities -- --nocapture ``` **Commit guidance:** - `C6 fix: expose api_version in HTTP capabilities` -- `C12 fix: add stable Clarion instance fingerprint` +- `C12 fix: add stable Loomweave instance fingerprint` ## T4: Contract Fixtures and Shape Gate @@ -259,7 +259,7 @@ cargo test -p clarion-cli --test serve capabilities -- --nocapture - Modify: `docs/federation/fixtures/get-api-v1-files.demo-python.json` - Modify: `docs/federation/fixtures/get-api-v1-capabilities.json` -- Modify: `crates/clarion-cli/tests/serve.rs` or create `crates/clarion-cli/tests/http_contract.rs` +- Modify: `crates/loomweave-cli/tests/serve.rs` or create `crates/loomweave-cli/tests/http_contract.rs` **Implementation requirements:** @@ -281,7 +281,7 @@ cargo test -p clarion-cli --test serve capabilities -- --nocapture **Commands:** ```bash -cargo test -p clarion-cli --test serve +cargo test -p loomweave-cli --test serve ``` **Commit guidance:** @@ -294,12 +294,12 @@ cargo test -p clarion-cli --test serve **Files:** -- Modify: `crates/clarion-mcp/src/config.rs` -- Modify: `crates/clarion-cli/src/http_read.rs` -- Modify: `crates/clarion-cli/src/serve.rs` -- Modify: `crates/clarion-cli/Cargo.toml` +- Modify: `crates/loomweave-mcp/src/config.rs` +- Modify: `crates/loomweave-cli/src/http_read.rs` +- Modify: `crates/loomweave-cli/src/serve.rs` +- Modify: `crates/loomweave-cli/Cargo.toml` - Modify: workspace `Cargo.toml` and `Cargo.lock` if adding dependencies -- Modify: `crates/clarion-cli/tests/serve.rs` +- Modify: `crates/loomweave-cli/tests/serve.rs` **Implementation requirements:** @@ -315,7 +315,7 @@ cargo test -p clarion-cli --test serve - `LoadShedLayer::new()` - Share the existing `ReaderPool` between MCP and HTTP; do not open a second pool. - Add supervision so a failed HTTP server aborts or fails the serve process visibly before MCP stdio continues pretending healthy. -- Name threads if a dedicated thread remains: OS thread `clarion-http-read`, Tokio workers `clarion-http-worker`. +- Name threads if a dedicated thread remains: OS thread `loomweave-http-read`, Tokio workers `loomweave-http-worker`. **Tests to add first:** @@ -329,7 +329,7 @@ cargo test -p clarion-cli --test serve **Commands:** ```bash -cargo test -p clarion-cli --test serve +cargo test -p loomweave-cli --test serve cargo test --workspace cargo clippy --workspace --all-targets -- -D warnings cargo fmt --all --check @@ -352,23 +352,23 @@ These do not have to block the tag if the release notes explicitly identify them - C15: release reader-pool slots before slow disk hash fallback. - C16: support `ETag` and `If-None-Match` on `/api/v1/files`. - C17: structured thread/request naming, if not fully covered by T5. -- C18: per-request structured access log fields including optional `X-Loom-Component` and `X-Filigree-Actor`. +- C18: per-request structured access log fields including optional `X-Weft-Component` and `X-Filigree-Actor`. - C19: `#[serde(deny_unknown_fields)]` on `FileQuery`. -If deferred, add a `Known limitations for 1.0` section to the release notes and create Clarion tracker issues for each item retained beyond the tag. +If deferred, add a `Known limitations for 1.0` section to the release notes and create Loomweave tracker issues for each item retained beyond the tag. ## End-to-End Acceptance -Clarion side is in spec when all of the following are true: +Loomweave side is in spec when all of the following are true: - ADR-014 documents `api_version`, `canonical_path`, `instance_id`, error envelope, no synthetic IDs, and trust model. - ADR-003 no longer permits or implies `@` file IDs. -- `cargo test -p clarion-cli --test serve` passes. -- `cargo test -p clarion-storage` passes. +- `cargo test -p loomweave-cli --test serve` passes. +- `cargo test -p loomweave-storage` passes. - `cargo test --workspace` passes. - `cargo clippy --workspace --all-targets -- -D warnings` passes. - `cargo fmt --all --check` passes. -- A local HTTP smoke against `clarion serve` proves: +- A local HTTP smoke against `loomweave serve` proves: - `/api/v1/_capabilities` returns `api_version: 1` and `instance_id`. - `/api/v1/files` returns a real `core:file:*` entity ID for a cataloged file. - a module-only catalog row does not synthesize a file ID and returns 404. @@ -387,4 +387,4 @@ When finished or blocked, report: - Joint decisions made and exact doc locations. - Any reviewer finding that proved wrong, with source evidence. -Do not mutate `/home/john/filigree`; it is read-only for this Clarion hardening pass. +Do not mutate `/home/john/filigree`; it is read-only for this Loomweave hardening pass. diff --git a/docs/implementation/sprint-3/scope-amendment-2026-05.md b/docs/implementation/sprint-3/scope-amendment-2026-05.md index a60bbb65..29897d3b 100644 --- a/docs/implementation/sprint-3/scope-amendment-2026-05.md +++ b/docs/implementation/sprint-3/scope-amendment-2026-05.md @@ -1,6 +1,6 @@ # Sprint 3 — WP10 Scope Amendment -**Status**: CLOSED — Clarion-side WP10 implementation complete. +**Status**: CLOSED — Loomweave-side WP10 implementation complete. **Date opened**: 2026-05-19 **Author**: John Morrissey **Predecessor**: [`../sprint-2/signoffs.md`](../sprint-2/signoffs.md) @@ -8,11 +8,11 @@ This memo serves three roles in one artifact: -1. It lifts WP10 back into active Clarion scope as the Sprint 3 anchor. -2. It records the Clarion-side half of ADR-014's federation displacement now that +1. It lifts WP10 back into active Loomweave scope as the Sprint 3 anchor. +2. It records the Loomweave-side half of ADR-014's federation displacement now that the Filigree-side counterpart and sequencing memo exist. -3. It turns the 2026-05-19 cross-project sequencing memo into Clarion tracker - work and records the implementation closeout for Clarion's side. +3. It turns the 2026-05-19 cross-project sequencing memo into Loomweave tracker + work and records the implementation closeout for Loomweave's side. --- @@ -36,8 +36,8 @@ No Sprint 2 umbrella issue is reopened by this amendment. ## 2. Why WP10 Is the Sprint 3 Anchor -Clarion ADR-014, accepted on 2026-04-18, made `registry_backend: clarion` the -design of record for displacing Filigree's native file IDs with Clarion file +Loomweave ADR-014, accepted on 2026-04-18, made `registry_backend: loomweave` the +design of record for displacing Filigree's native file IDs with Loomweave file entity IDs when the products are federated. Sprint 2 deliberately deferred WP10 while it shipped the MCP consult surface and the ADR-029 entity-associations binding. @@ -48,21 +48,21 @@ ADR and a cross-project sequencing memo: - `/home/john/filigree/docs/architecture/decisions/ADR-014-registry-backend-and-file-identity-displacement.md` - `/home/john/filigree/docs/plans/2026-05-19-registry-backend-cross-project-sequencing.md` -Mirrored read-only copies are in `docs/federation/filigree-side/` so Clarion's +Mirrored read-only copies are in `docs/federation/filigree-side/` so Loomweave's repo carries the federation context. These are counterparts, not replacements: -Clarion ADR-014 remains Accepted as-is, and ADR-029's `entity_associations` +Loomweave ADR-014 remains Accepted as-is, and ADR-029's `entity_associations` primitive stays intact. -The live Clarion tree also confirms the missing implementation surface: +The live Loomweave tree also confirms the missing implementation surface: -- `crates/clarion-cli/src/serve.rs` serves MCP over stdio only. +- `crates/loomweave-cli/src/serve.rs` serves MCP over stdio only. - No `axum`, `warp`, or `hyper` server imports exist under `crates/`. - No `resolve_file` implementation exists under `crates/`. -- `crates/clarion-mcp/src/filigree.rs` already has the blocking `reqwest` +- `crates/loomweave-mcp/src/filigree.rs` already has the blocking `reqwest` client pattern used for Filigree's `entity_associations` reverse lookup. Sprint 3 therefore anchors on the HTTP read API that Filigree's -`ClarionRegistry` needs. +`LoomweaveRegistry` needs. --- @@ -72,25 +72,25 @@ Sprint 3 therefore anchors on the HTTP read API that Filigree's | Box | Owning WP | Deliverable | Anchoring decisions | |---|---|---|---| -| **C-WP10** | WP10 | Clarion HTTP read API for Filigree's `ClarionRegistry` | Clarion ADR-014; Filigree ADR-014; system-design §9, §11; detailed-design §7 | +| **C-WP10** | WP10 | Loomweave HTTP read API for Filigree's `LoomweaveRegistry` | Loomweave ADR-014; Filigree ADR-014; system-design §9, §11; detailed-design §7 | -### Clarion-side breakdown +### Loomweave-side breakdown | Step | Title | Scope | |---|---|---| -| **C-WP10.1** | `axum` HTTP read server in `clarion-cli` (new module) | Add the server shell and configurable bind/auth posture without changing MCP tool behavior. | -| **C-WP10.2** | `GET /api/v1/files?path=&language=` endpoint | Resolve path/language to `{entity_id, content_hash, canonical_path, language}` from Clarion storage. | -| **C-WP10.3** | Contracts directory + JSON fixture for `GET /api/v1/files` | Publish the Clarion-side contract Filigree can dry-run against. | -| **C-WP10.4** | `GET /api/v1/_capabilities` endpoint | Expose `registry_backend: true` so Filigree's `ClarionRegistry` can fail fast. | -| **C-WP10.5** | Wire HTTP server into `clarion serve` alongside MCP stdio | Start the HTTP read server without regressing existing MCP-over-stdio operation. | +| **C-WP10.1** | `axum` HTTP read server in `loomweave-cli` (new module) | Add the server shell and configurable bind/auth posture without changing MCP tool behavior. | +| **C-WP10.2** | `GET /api/v1/files?path=&language=` endpoint | Resolve path/language to `{entity_id, content_hash, canonical_path, language}` from Loomweave storage. | +| **C-WP10.3** | Contracts directory + JSON fixture for `GET /api/v1/files` | Publish the Loomweave-side contract Filigree can dry-run against. | +| **C-WP10.4** | `GET /api/v1/_capabilities` endpoint | Expose `registry_backend: true` so Filigree's `LoomweaveRegistry` can fail fast. | +| **C-WP10.5** | Wire HTTP server into `loomweave serve` alongside MCP stdio | Start the HTTP read server without regressing existing MCP-over-stdio operation. | The Filigree sequencing memo used C-WP10.5 for this scope-amendment memo itself. -This document satisfies that planning item; Clarion's local tracker uses +This document satisfies that planning item; Loomweave's local tracker uses C-WP10.5 for the serve-wiring implementation step requested for Sprint 3. ### Out of scope -- Changes to Clarion ADR-014, ADR-029, or any other existing ADR. +- Changes to Loomweave ADR-014, ADR-029, or any other existing ADR. - Any unwind of ADR-029 or the `entity_associations` table. - Filigree repository mutations; Filigree-side files are read-only inputs here. - Wardline-native emitter work. @@ -101,14 +101,14 @@ C-WP10.5 for the serve-wiring implementation step requested for Sprint 3. The dependency is one-way and should stay explicit: -| Filigree phase | Relationship to Clarion Sprint 3 | +| Filigree phase | Relationship to Loomweave Sprint 3 | |---|---| -| **Phase B** — `RegistryProtocol` interface, `LocalRegistry`, behavior-preserving call-site refactor, additive schema columns | Independent. It can land before Clarion exposes HTTP because it has no observable `clarion` mode yet. | -| **Phase C** — `registry_backend` flag, `ClarionRegistry`, capability probe, displaced-registration error, fail-closed behavior, migration verb | Blocks on **C-WP10.2** because the Clarion file-resolution endpoint is the first useful read contract. | -| **Phase D** — cross-process integration tests | Blocks on both Clarion C-WP10.2/C-WP10.4 and Filigree Phase C. | +| **Phase B** — `RegistryProtocol` interface, `LocalRegistry`, behavior-preserving call-site refactor, additive schema columns | Independent. It can land before Loomweave exposes HTTP because it has no observable `loomweave` mode yet. | +| **Phase C** — `registry_backend` flag, `LoomweaveRegistry`, capability probe, displaced-registration error, fail-closed behavior, migration verb | Blocks on **C-WP10.2** because the Loomweave file-resolution endpoint is the first useful read contract. | +| **Phase D** — cross-process integration tests | Blocks on both Loomweave C-WP10.2/C-WP10.4 and Filigree Phase C. | | **Phase E** — docs and launch runbook | Lands after the contract and integration tests are stable. | -The practical sequencing: Filigree Phase B may proceed in parallel; Clarion +The practical sequencing: Filigree Phase B may proceed in parallel; Loomweave should ship C-WP10.1 -> C-WP10.2 -> C-WP10.4 -> C-WP10.5, with C-WP10.3 hanging off C-WP10.2 once the response shape is concrete. @@ -119,11 +119,11 @@ off C-WP10.2 once the response shape is concrete. `v0.1-plan.md` originally framed WP10 as Filigree-side plus Wardline-side cross-product work and assumed the Filigree side would land from Filigree's own roadmap. That assumption is obsolete as of 2026-05-19: the Filigree-side ADR and -sequencing memo now exist, and they require a Clarion HTTP read surface that is +sequencing memo now exist, and they require a Loomweave HTTP read surface that is not implemented today. -This amendment narrows Sprint 3 to the Clarion HTTP read API needed by -Filigree's `ClarionRegistry`. The original WP10 Wardline/SARIF translator work +This amendment narrows Sprint 3 to the Loomweave HTTP read API needed by +Filigree's `LoomweaveRegistry`. The original WP10 Wardline/SARIF translator work remains valuable but is not the Sprint 3 anchor unless explicitly added later. --- @@ -136,7 +136,7 @@ Create one planning milestone: Under it, create one phase: -- `Clarion HTTP read API for Filigree's ClarionRegistry` +- `Loomweave HTTP read API for Filigree's LoomweaveRegistry` Create the five C-WP10 steps in the order listed in §3, with dependencies: @@ -159,19 +159,19 @@ See /home/john/filigree's filigree tracker, milestone titled - Sprint 2 remains closed GREEN after repair. - ADR-029 remains the shipped peer primitive for issue-to-entity binding. -- Clarion ADR-014 remains Accepted and is not edited by this amendment. -- Clarion's MCP-over-stdio serve path remains live alongside the new opt-in +- Loomweave ADR-014 remains Accepted and is not edited by this amendment. +- Loomweave's MCP-over-stdio serve path remains live alongside the new opt-in HTTP read API. -- Filigree Phase B remains safe to land before Clarion; Filigree Phase C waits - for Clarion's file-resolution endpoint. +- Filigree Phase B remains safe to land before Loomweave; Filigree Phase C waits + for Loomweave's file-resolution endpoint. --- ## 8. References -- [Clarion ADR-014](../../clarion/adr/ADR-014-filigree-registry-backend.md) -- [Clarion ADR-029](../../clarion/adr/ADR-029-entity-associations-binding.md) -- [Clarion v0.1 plan §WP10](../v0.1-plan.md#wp10--cross-product-filigree--and-wardline-side-changes) +- [Loomweave ADR-014](../../loomweave/adr/ADR-014-filigree-registry-backend.md) +- [Loomweave ADR-029](../../loomweave/adr/ADR-029-entity-associations-binding.md) +- [Loomweave v0.1 plan §WP10](../v0.1-plan.md#wp10--cross-product-filigree--and-wardline-side-changes) - [Sprint 2 scope amendment](../sprint-2/scope-amendment-2026-05.md) - [Sprint 2 signoffs](../sprint-2/signoffs.md) - [Filigree ADR-014 mirror](../../federation/filigree-side/ADR-014-registry-backend-and-file-identity-displacement.md) @@ -181,26 +181,26 @@ See /home/john/filigree's filigree tracker, milestone titled ## 9. Implementation Closeout -The Clarion-side WP10 implementation is complete in the local tracker: +The Loomweave-side WP10 implementation is complete in the local tracker: | Issue | Scope | Status | |---|---|---| | `clarion-44fbe093ca` | Sprint 3 WP10 milestone | `completed` | -| `clarion-f082fb6a49` | Clarion HTTP read API phase | `completed` | +| `clarion-f082fb6a49` | Loomweave HTTP read API phase | `completed` | | `clarion-e904d7a7ea` | C-WP10.1 HTTP read server module | `completed` | | `clarion-aea2d917f9` | C-WP10.2 `GET /api/v1/files` | `completed` | | `clarion-9d1379172f` | C-WP10.3 contract docs and fixtures | `completed` | | `clarion-95643a7d5e` | C-WP10.4 `GET /api/v1/_capabilities` | `completed` | -| `clarion-2526c76071` | C-WP10.5 `clarion serve` HTTP wiring | `completed` | +| `clarion-2526c76071` | C-WP10.5 `loomweave serve` HTTP wiring | `completed` | Implementation artifacts: -- `crates/clarion-cli/src/http_read.rs` adds the `axum` HTTP read server. -- `crates/clarion-cli/src/serve.rs` starts the HTTP read server when +- `crates/loomweave-cli/src/http_read.rs` adds the `axum` HTTP read server. +- `crates/loomweave-cli/src/serve.rs` starts the HTTP read server when `serve.http.enabled` is true and shuts it down after MCP stdio exits. -- `crates/clarion-storage/src/query.rs` adds `resolve_file` for read-only file +- `crates/loomweave-storage/src/query.rs` adds `resolve_file` for read-only file identity resolution. -- `crates/clarion-mcp/src/config.rs` adds `serve.http.enabled` and +- `crates/loomweave-mcp/src/config.rs` adds `serve.http.enabled` and `serve.http.bind` config. - `docs/federation/contracts.md` and `docs/federation/fixtures/*.json` pin the Filigree-facing response shapes. @@ -216,4 +216,4 @@ Verification evidence: - `bash tests/e2e/sprint_2_mcp_surface.sh` The Filigree Phase B/C dependency direction remains unchanged: Phase B can land -independently, while Phase C needs Clarion's `/api/v1/files` contract. +independently, while Phase C needs Loomweave's `/api/v1/files` contract. diff --git a/docs/implementation/v0.1-plan.md b/docs/implementation/v0.1-plan.md index 92dcf131..22008e17 100644 --- a/docs/implementation/v0.1-plan.md +++ b/docs/implementation/v0.1-plan.md @@ -1,13 +1,13 @@ -# Clarion v0.1 — Implementation Plan (High-Level Overview) +# Loomweave v0.1 — Implementation Plan (High-Level Overview) -**Status**: AMENDED — Sprint 1 closed (`v0.1-sprint-1`); Sprint 2 resequenced under [`sprint-2/scope-amendment-2026-05.md`](./sprint-2/scope-amendment-2026-05.md) and closed (`v0.1-sprint-2`); Sprint 3 (Loom federation hardening, ADR-014 → ADR-034) closed 2026-05-19 — see [`sprint-3/2026-05-19-loom-federation-hardening-tasking.md`](./sprint-3/2026-05-19-loom-federation-hardening-tasking.md). The WP body below is the original framing and remains the architectural reference; the Sprint 2 resequence memo and Sprint 3 tasking doc are the authoritative sources for what shipped in v1.0 vs. what slips to v1.1+. **Read all three.** +**Status**: AMENDED — Sprint 1 closed (`v0.1-sprint-1`); Sprint 2 resequenced under [`sprint-2/scope-amendment-2026-05.md`](./sprint-2/scope-amendment-2026-05.md) and closed (`v0.1-sprint-2`); Sprint 3 (Weft federation hardening, ADR-014 → ADR-034) closed 2026-05-19 — see [`sprint-3/2026-05-19-weft-federation-hardening-tasking.md`](./sprint-3/2026-05-19-weft-federation-hardening-tasking.md). The WP body below is the original framing and remains the architectural reference; the Sprint 2 resequence memo and Sprint 3 tasking doc are the authoritative sources for what shipped in v1.0 vs. what slips to v1.1+. **Read all three.** **Date opened**: 2026-04-18 **Last amended**: 2026-05-19 -**Plan owner**: John Morrissey (primary dev of Clarion, Filigree, Wardline) -**Predecessor**: [`../clarion/v0.1/plans/v0.1-scope-commitments.md`](./v0.1-scope-plans/v0.1-scope-commitments.md) (scope locked 2026-04-18) +**Plan owner**: John Morrissey (primary dev of Loomweave, Filigree, Wardline) +**Predecessor**: [`../loomweave/v0.1/plans/v0.1-scope-commitments.md`](./v0.1-scope-plans/v0.1-scope-commitments.md) (scope locked 2026-04-18) **Amendments**: - [`sprint-2/scope-amendment-2026-05.md`](./sprint-2/scope-amendment-2026-05.md) (2026-05-16 — narrows WP6, splits WP9 into A/B, defers WP4/5/7/10/11-as-scheduled, pulls WP8 + WP9-A into Sprint 2 for MVP MCP surface). -- [`sprint-3/2026-05-19-loom-federation-hardening-tasking.md`](./sprint-3/2026-05-19-loom-federation-hardening-tasking.md) (2026-05-19 — Loom federation hardening sprint: bearer auth, batch resolution, `BRIEFING_BLOCKED` propagation, per-project `instance_id`; pinned in ADR-034). Closed alongside the v1.0 release. +- [`sprint-3/2026-05-19-weft-federation-hardening-tasking.md`](./sprint-3/2026-05-19-weft-federation-hardening-tasking.md) (2026-05-19 — Weft federation hardening sprint: bearer auth, batch resolution, `BRIEFING_BLOCKED` propagation, per-project `instance_id`; pinned in ADR-034). Closed alongside the v1.0 release. **Audience**: implementation engineer (the author + future contributors), Filigree-side issue-tracker seeder, design reviewers checking that every system-design section has an owning work package. @@ -54,7 +54,7 @@ flowchart LR WP6["WP6
LLM dispatch
+ summary cache"] WP7["WP7
Guidance
system"] WP8["WP8
Serve surfaces
(MCP + HTTP)"] - WP9["WP9
Loom integrations
(Filigree + Wardline)"] + WP9["WP9
Weft integrations
(Filigree + Wardline)"] WP10["WP10
Cross-product
(Filigree+Wardline-side)"] WP11["WP11
Cost-model
validation"] @@ -96,7 +96,7 @@ flowchart LR | WP6 | LLM dispatch + policy + summary cache (Phases 4–6) | sys §5 + sys §6 + det §4 | ADR-007 | — | | WP7 | Guidance system | sys §7 + det §2 | — | ADR-009 | | WP8 | MCP consult + HTTP read API | sys §8 + sys §9 + det §6 + det §7 | ADR-012 | ADR-010 | -| WP9 | Loom integrations | sys §9 + sys §11 + det §7 | ADR-004, ADR-015, ADR-016, ADR-017, ADR-018 | ADR-020 | +| WP9 | Weft integrations | sys §9 + sys §11 + det §7 | ADR-004, ADR-015, ADR-016, ADR-017, ADR-018 | ADR-020 | | WP10 | Cross-product (Filigree + Wardline side) | sys §11 prereqs + det §7 | ADR-014, ADR-015 | ADR-019 | | WP11 | Post-impl cost/perf validation | scope memo §"Validation" | — | — | @@ -108,12 +108,12 @@ per package. ### 5.1 RecordingProvider (testing primitive) The determinism requirements in `system-design.md` §6 (analyser determinism) and the -summary-cache contract in ADR-007 require that, given identical inputs, Clarion produces +summary-cache contract in ADR-007 require that, given identical inputs, Loomweave produces identical outputs. For LLM-bearing phases (WP6) this is impossible to validate against a live API — it costs money and the API is non-deterministic. **The primitive**: a trait `LlmProvider` with two implementations: -- `AnthropicProvider` — real network calls, used by `clarion analyze` against real repos. +- `AnthropicProvider` — real network calls, used by `loomweave analyze` against real repos. - `RecordingProvider` — wraps `AnthropicProvider`, records every (request, response) pair to a YAML/JSON fixture file on first run; on subsequent runs replays from the fixture and fails if the request-shape diverges. @@ -174,14 +174,14 @@ implementation), `system-design.md` §1 (process topology). - Cargo workspace at repo root. Crate decomposition is an implementation choice (a reasonable starting point is core library + CLI binary + storage; the boundary may shift once WP2's plugin host needs to share types with the core). -- `clarion install | analyze | serve` CLI entry points (skeletons; only `install` +- `loomweave install | analyze | serve` CLI entry points (skeletons; only `install` is functionally complete in WP1). - SQLite schema as specified in `detailed-design.md` §3 (entities, edges, findings, briefings, runs, summaries, guidance, observations). - Writer-actor implementation per ADR-011: single writable connection, mpsc command queue, per-N-files transactions (default `batch_size` per ADR-011), `--shadow-db` opt-in. -- Reader-pool with connection-pool management (pool size from `clarion.yaml`). +- Reader-pool with connection-pool management (pool size from `loomweave.yaml`). - Migration framework using sequentially numbered SQL files committed alongside the storage code. - Checkpoint/resume protocol using the `runs` table. @@ -196,8 +196,8 @@ implementation), `system-design.md` §1 (process topology). **Deliverables**: - Compilable Cargo workspace, `cargo test` passes on a hello-world test per crate. -- `clarion install` initialises `.clarion/` correctly: empty `clarion.db` at the right - schema version, default `clarion.yaml`, `.clarion/` registered for git tracking with +- `loomweave install` initialises `.loomweave/` correctly: empty `loomweave.db` at the right + schema version, default `loomweave.yaml`, `.loomweave/` registered for git tracking with the run-log subpath excluded (see ADR-005 trigger below). - Writer-actor passes a stress test that asserts no `database is locked` errors under concurrent reader load while the writer commits in batches. @@ -210,16 +210,16 @@ implementation), `system-design.md` §1 (process topology). **Exit criteria**: - `cargo test --workspace` passes. -- A blank `clarion install` followed by `git add .clarion && git status` shows the +- A blank `loomweave install` followed by `git add .loomweave && git status` shows the expected paths tracked and the run log excluded. - Writer-actor stress test holds at the configured `batch_size` for ≥10 minutes without lock errors or memory growth >50 MB above baseline. **ADR triggers**: -- **ADR-005** (`.clarion/` git-committable, backlog) — WP1 will force a decision on +- **ADR-005** (`.loomweave/` git-committable, backlog) — WP1 will force a decision on exactly which subpaths land in `.gitignore` (logs, temp scratch, shadow DB) versus which are committed (the DB itself, manifest, briefings, catalog). Author the ADR - when implementing `clarion install`. + when implementing `loomweave install`. --- @@ -281,19 +281,19 @@ ontology ownership boundary). ### WP3 — Python plugin v0.1 -> **Sprint 1 delivery (narrow walking-skeleton scope)** — closed 2026-04-28. The Sprint 1 slice covers L7 (Python qualname production format) and L8 (Wardline REGISTRY probe + version-pin protocol) per [`sprint-1/wp3-python-plugin.md`](./sprint-1/wp3-python-plugin.md). Function entities only (module-level + class methods); classes / decorators / module entities, edge emission (`imports`, `calls`, `decorates`, `contains`), import resolution, and the full `CLA-PY-*` rule catalogue are deferred to the WP3-feature-complete sprint. [`sprint-1/signoffs.md §A.3`](./sprint-1/signoffs.md#a3-python-plugin-wp3) is the gate that closed. +> **Sprint 1 delivery (narrow walking-skeleton scope)** — closed 2026-04-28. The Sprint 1 slice covers L7 (Python qualname production format) and L8 (Wardline REGISTRY probe + version-pin protocol) per [`sprint-1/wp3-python-plugin.md`](./sprint-1/wp3-python-plugin.md). Function entities only (module-level + class methods); classes / decorators / module entities, edge emission (`imports`, `calls`, `decorates`, `contains`), import resolution, and the full `LMWV-PY-*` rule catalogue are deferred to the WP3-feature-complete sprint. [`sprint-1/signoffs.md §A.3`](./sprint-1/signoffs.md#a3-python-plugin-wp3) is the gate that closed. **Anchoring docs**: `detailed-design.md` §1 (Python-specific subsections), `system-design.md` §2 (plugin contract from the plugin's side). **Accepted ADRs**: ADR-022 (ontology boundary — plugin's manifest is the contract), ADR-018 (identity reconciliation — the Python plugin is the producer of the -qualnames Clarion translates). +qualnames Loomweave translates). **Scope**: -- Standalone Python package `clarion-plugin-python` (separate `pyproject.toml`). +- Standalone Python package `loomweave-plugin-python` (separate `pyproject.toml`). - `plugin.toml` declaring the Python ontology: entity kinds (module, class, function, - decorator, etc.), edge kinds (imports, calls, decorates, etc.), rule-IDs (`CLA-PY-*`). + decorator, etc.), edge kinds (imports, calls, decorates, etc.), rule-IDs (`LMWV-PY-*`). - AST-based detection using `ast` module: classes, functions, decorators, imports. - Decorator detection sufficient for the rule-IDs listed in `detailed-design.md` §5. - Import resolution: relative + absolute imports resolved to canonical entity IDs per @@ -302,7 +302,7 @@ qualnames Clarion translates). resolved; inter-module resolved when import resolution succeeded). - Structural finding emission for the Python rule catalogue in `detailed-design.md` §5. - Identity reconciliation hooks per ADR-018: produce qualnames in a - Wardline-translatable form, with explicit translation between Clarion's dotted + Wardline-translatable form, with explicit translation between Loomweave's dotted module prefix and Wardline's separate file-path plus bare-qualname fields. **Out of scope (explicit)**: @@ -312,7 +312,7 @@ qualnames Clarion translates). - Dynamic-import resolution (`importlib`, `__import__`) beyond what `ast` reveals. **Deliverables**: -- Installable Python plugin (e.g., `pipx install clarion-plugin-python` once published; +- Installable Python plugin (e.g., `pipx install loomweave-plugin-python` once published; meanwhile `pip install -e .` from the source tree). - Test suite of Python source fixtures and expected entity/edge/finding output. - Manifest passing WP2's validator without warnings. @@ -352,12 +352,12 @@ qualnames Clarion translates). - **Phase 3 — clustering**: Leiden on the imports+calls subgraph per ADR-006; Louvain fallback when Leiden's quality threshold is not met; deterministic seed. - **Phase 7 — cross-cutting structural findings**: rule evaluator producing the - `CLA-*` finding catalogue from `detailed-design.md` §5; severity per ADR-017. + `LMWV-*` finding catalogue from `detailed-design.md` §5; severity per ADR-017. - **Phase 8 — entity-set diff**: compares the current run's entity set against the previous run's, emits churn metadata for WP6's cache invalidation and WP7's guidance fingerprinting. - Catalog rendering: `catalog.json` + per-subsystem markdown files written under - `.clarion/`. + `.loomweave/`. - Findings commit: `findings.jsonl` written; format per ADR-004 (Filigree-native intake shape, see WP9 for the actual Filigree round-trip). @@ -367,10 +367,10 @@ qualnames Clarion translates). - Findings emission to Filigree: WP9. **Deliverables**: -- `clarion analyze --no-llm` runs end-to-end on a small Python repo, producing a +- `loomweave analyze --no-llm` runs end-to-end on a small Python repo, producing a populated DB, `catalog.json`, per-subsystem markdown, and `findings.jsonl`. -- Test fixtures covering each `CLA-*` rule with positive and negative cases. -- Determinism test: running `clarion analyze --no-llm` twice on identical input +- Test fixtures covering each `LMWV-*` rule with positive and negative cases. +- Determinism test: running `loomweave analyze --no-llm` twice on identical input produces byte-identical artefacts. **Sequencing constraints**: @@ -380,7 +380,7 @@ qualnames Clarion translates). **Exit criteria**: - Determinism test passes. - Cross-cutting rule fixtures pass. -- `clarion analyze --no-llm` against the Clarion docs tree (a real Python-light input) +- `loomweave analyze --no-llm` against the Loomweave docs tree (a real Python-light input) completes without errors and produces a non-empty catalog. **ADR triggers**: none expected (the pipeline ADRs are already authored). @@ -397,13 +397,13 @@ qualnames Clarion translates). **Scope**: - Rust-native secret-detection module (port the small subset of detect-secrets rules enumerated in ADR-013; no Python runtime dependency in core). -- Baseline YAML at `.clarion/secrets-baseline.yaml` for accepted exceptions. +- Baseline YAML at `.loomweave/secrets-baseline.yaml` for accepted exceptions. - File-level block: any file with an unbaselined high-confidence detection is excluded from Phase-1 entity ingest entirely. - Briefing suppression: detected files do not appear in briefings even by name (ADR-013 threat-model row). - `--allow-unredacted-secrets` flag with explicit operator gating; emits a - `CLA-SEC-OVERRIDE` finding to make the override visible. + `LMWV-SEC-OVERRIDE` finding to make the override visible. **Out of scope (explicit)**: - General SAST scanning (Wardline's territory). @@ -412,10 +412,10 @@ qualnames Clarion translates). a key is live). **Deliverables**: -- Scanner runs as the first step of `clarion analyze`, before plugin spawn. +- Scanner runs as the first step of `loomweave analyze`, before plugin spawn. - Baseline file format documented and round-trip tested. - Negative test: a file containing a known live-style secret pattern is excluded; the - briefing for the containing subsystem omits the file; a `CLA-SEC-FILE-EXCLUDED` + briefing for the containing subsystem omits the file; a `LMWV-SEC-FILE-EXCLUDED` finding is emitted. - Override test: `--allow-unredacted-secrets` admits the file but emits the override finding. @@ -442,7 +442,7 @@ qualnames Clarion translates). churn-eager invalidation). **Scope**: -- `clarion.yaml` config loader and merge semantics (project root + user home + CLI +- `loomweave.yaml` config loader and merge semantics (project root + user home + CLI override precedence per `detailed-design.md` §4). - Anthropic SDK wrapper implementing `LlmProvider` trait from WP1's §5.1 primitive. - `temperature: 0` enforced. @@ -465,7 +465,7 @@ churn-eager invalidation). - **Phase 6 — subsystem summarisation**: aggregates module summaries into subsystem summaries; produces the briefing inputs WP7 composes. - All phases gated behind RecordingProvider replay in CI; live-Anthropic runs require - an explicit `CLARION_LLM_LIVE=1` env var. + an explicit `LOOMWEAVE_LLM_LIVE=1` env var. **Out of scope (explicit)**: - Cost-model validation (WP11 — that's the post-impl spike). @@ -473,7 +473,7 @@ churn-eager invalidation). - The MCP/HTTP surfaces that consume summaries (WP8). **Deliverables**: -- `clarion analyze` (without `--no-llm`) completes end-to-end on a small fixture using +- `loomweave analyze` (without `--no-llm`) completes end-to-end on a small fixture using RecordingProvider; the recorded fixture is checked in. - Cache test suite: cache-hit, cache-miss, TTL expiry, churn-eager invalidation each asserted with a focused test. @@ -482,13 +482,13 @@ churn-eager invalidation). **Sequencing constraints**: - Requires WP1 (storage, `LlmProvider` trait), WP4 (Phases 0–3 producing the inputs). -- Should land before WP9 (Loom integrations consume summaries via briefings). +- Should land before WP9 (Weft integrations consume summaries via briefings). **Exit criteria**: - Determinism test: re-running with the same RecordingProvider fixture produces byte-identical summaries. - Cache-hit-rate assertion on a two-run sequence (cold then warm) ≥ a configurable - threshold (initial threshold in `clarion.yaml`; the actual NFR-COST-02 ≥95% number + threshold (initial threshold in `loomweave.yaml`; the actual NFR-COST-02 ≥95% number is validated in WP11, not here). **ADR triggers**: none expected (ADR-007 is comprehensive). @@ -512,16 +512,16 @@ churn-eager invalidation). WP4 Phase 8). - Match rules: glob, scope-prefix, label-set per `detailed-design.md` §2. - Findings: - - `CLA-FACT-GUIDANCE-CHURN-STALE` — guidance sheet anchored to an entity whose + - `LMWV-FACT-GUIDANCE-CHURN-STALE` — guidance sheet anchored to an entity whose fingerprint changed since the sheet was last edited. - - `CLA-FACT-GUIDANCE-ORPHAN` — guidance sheet whose target entity no longer exists. + - `LMWV-FACT-GUIDANCE-ORPHAN` — guidance sheet whose target entity no longer exists. **Out of scope (explicit)**: - A guidance authoring UI (the v0.2 wiki — NG-13). - Guidance versioning beyond fingerprint detection. **Deliverables**: -- `.clarion/guidance/` directory layout documented and parsed by `clarion analyze`. +- `.loomweave/guidance/` directory layout documented and parsed by `loomweave analyze`. - Composition test: a multi-sheet fixture produces the expected merged guidance per precedence rules. - Stale and orphan findings have fixtures that exercise both rules. @@ -551,7 +551,7 @@ churn-eager invalidation). fallback). **Scope**: -- `clarion serve` with two surfaces: +- `loomweave serve` with two surfaces: - **MCP over stdio**: tools enumerated in `detailed-design.md` §6, with session state for cursor-based navigation, structured response envelope per the MCP contract. @@ -562,14 +562,14 @@ fallback). - Fallback: TCP loopback with auto-minted token written to a chmod-`0600` file when UDS is unavailable. - `serve.auth: none` is forbidden in production; if explicitly set, emit a - `CLA-SEC-SERVE-AUTH-NONE` finding at startup. + `LMWV-SEC-SERVE-AUTH-NONE` finding at startup. **Out of scope (explicit)**: - The HTTP write API (`POST /entities`, etc.) — deferred to v0.2 per scope memo. - The semi-dynamic wiki — deferred to v0.2 (NG-13). **Deliverables**: -- `clarion serve` runs and accepts MCP connections; tool catalogue matches +- `loomweave serve` runs and accepts MCP connections; tool catalogue matches `detailed-design.md` §6. - HTTP API exposes the read endpoints from `detailed-design.md` §7; auth path tested for both UDS and TCP-token modes. @@ -591,14 +591,14 @@ fallback). --- -### WP9 — Loom integrations (Filigree + Wardline, Clarion side) +### WP9 — Weft integrations (Filigree + Wardline, Loomweave side) **Anchoring docs**: `system-design.md` §9 (Integrations), `system-design.md` §11 (Suite bootstrap), `detailed-design.md` §7 (integrations implementation). **Accepted ADRs**: ADR-004 (Filigree-native finding intake format), -ADR-015 (Wardline→Filigree emission ownership; v0.1 = Clarion-side SARIF translator), +ADR-015 (Wardline→Filigree emission ownership; v0.1 = Loomweave-side SARIF translator), ADR-016 (observation transport — MCP-spawn for v0.1, Filigree HTTP endpoint for v0.2), ADR-017 (severity and dedup policy applied to emitted findings), ADR-018 (identity reconciliation — direct REGISTRY import with version pinning). @@ -616,18 +616,18 @@ ADR-018 (identity reconciliation — direct REGISTRY import with version pinning - Wardline-config ingestion (`wardline.yaml`, overlays, fingerprint, exceptions, SARIF baseline) at analyse-time, producing the augmentation annotations described in `system-design.md` §9. -- **Suite-compat probe**: at startup, `clarion analyze` checks Filigree and Wardline +- **Suite-compat probe**: at startup, `loomweave analyze` checks Filigree and Wardline versions/availability and falls back per the matrix in `system-design.md` §11. - **Fallback flags**: `--no-filigree` and `--no-wardline` short-circuit the respective integrations and produce the documented degraded-mode behaviour. **Out of scope (explicit)**: -- Filigree `registry_backend: clarion` mode (that's WP10 — Filigree-side work). +- Filigree `registry_backend: loomweave` mode (that's WP10 — Filigree-side work). - Wardline-native finding emitter (deferred to v0.2 per ADR-015). - Observation HTTP transport (deferred to v0.2 per ADR-016). **Deliverables**: -- End-to-end test: `clarion analyze` against a Python fixture writes findings both +- End-to-end test: `loomweave analyze` against a Python fixture writes findings both to local `findings.jsonl` and via POST to a fake Filigree HTTP server; assertions check shape and severity per ADR-017. - Observation test: a Phase-7 finding configured to also produce an observation @@ -637,19 +637,19 @@ ADR-018 (identity reconciliation — direct REGISTRY import with version pinning **Sequencing constraints**: - Requires WP4 (the findings to emit) and WP6 (briefings that reference summaries). -- Filigree-side integration depends on WP10 having shipped `registry_backend: clarion` - *only if* Clarion is operating in non-shadow mode against a real Filigree. In +- Filigree-side integration depends on WP10 having shipped `registry_backend: loomweave` + *only if* Loomweave is operating in non-shadow mode against a real Filigree. In shadow-registry mode, WP10 is not blocking for WP9. **Exit criteria**: - Compat-probe matrix exercised: Filigree present, Filigree absent, Wardline present, Wardline absent — each path produces the documented behaviour. -- Severity-map round-trip: every Clarion `CLA-*` rule-ID has a defined Filigree-side +- Severity-map round-trip: every Loomweave `LMWV-*` rule-ID has a defined Filigree-side severity; round-trip preserves the rule-ID. **ADR triggers**: - **ADR-020** (degraded-mode policy and explicit suite fallbacks, backlog) — the - compat-probe implementation will surface the precise contract for "what does Clarion + compat-probe implementation will surface the precise contract for "what does Loomweave do when sibling X is at version Y < required". Author once the matrix is concrete. --- @@ -668,16 +668,16 @@ ownership decision). - Add `registry_backend` config flag to Filigree's settings. - Define `RegistryProtocol` trait/interface; refactor file-registry call sites to go through it. - - Implement `registry_backend: clarion` mode that delegates to Clarion's entity + - Implement `registry_backend: loomweave` mode that delegates to Loomweave's entity catalog over the HTTP read API (WP8) or a local DB read. - Preserve `registry_backend: local` as the default so Filigree alone keeps working. - - Add `scan_source` field to Filigree's finding records so Clarion-emitted findings + - Add `scan_source` field to Filigree's finding records so Loomweave-emitted findings can be distinguished from Filigree-native ones. - - Contract test: a recorded Clarion `findings.jsonl` POSTed to Filigree round-trips + - Contract test: a recorded Loomweave `findings.jsonl` POSTed to Filigree round-trips cleanly. -- **Clarion side**: - - `clarion sarif import` translator that reads a Wardline SARIF file, translates - to Clarion's finding shape, and POSTs to Filigree (the v0.1 stand-in for the +- **Loomweave side**: + - `loomweave sarif import` translator that reads a Wardline SARIF file, translates + to Loomweave's finding shape, and POSTs to Filigree (the v0.1 stand-in for the deferred Wardline-native emitter — ADR-015). - SARIF property-bag preservation per the rules WP10 will surface (see ADR-019 trigger below). @@ -688,21 +688,21 @@ ownership decision). - Any UI work in Filigree. **Deliverables**: -- Filigree PR (or commit series) implementing `RegistryProtocol` + the `clarion` +- Filigree PR (or commit series) implementing `RegistryProtocol` + the `loomweave` backend; Filigree's existing test suite continues to pass. -- `clarion sarif import` subcommand with a Wardline SARIF fixture and a round-trip +- `loomweave sarif import` subcommand with a Wardline SARIF fixture and a round-trip test. - Schema-compatibility pin per NFR-COMPAT-01. **Sequencing constraints**: - Independent of WP1–WP9 *implementation* sequencing; can start any time the same author has bandwidth, since the repos are independent. -- Filigree-side `registry_backend: clarion` mode must ship before WP9's non-shadow +- Filigree-side `registry_backend: loomweave` mode must ship before WP9's non-shadow mode is testable end-to-end. **Exit criteria**: -- Filigree contract test passes against a Clarion-emitted `findings.jsonl`. -- `clarion sarif import` round-trip preserves rule-ID, severity, and the property-bag +- Filigree contract test passes against a Loomweave-emitted `findings.jsonl`. +- `loomweave sarif import` round-trip preserves rule-ID, severity, and the property-bag fields named in ADR-019 (once authored). **ADR triggers**: @@ -715,13 +715,13 @@ ownership decision). ### WP11 — Post-implementation cost/perf validation -**Anchoring docs**: [`../clarion/v0.1/plans/v0.1-scope-commitments.md`](./v0.1-scope-plans/v0.1-scope-commitments.md) +**Anchoring docs**: [`../loomweave/v0.1/plans/v0.1-scope-commitments.md`](./v0.1-scope-plans/v0.1-scope-commitments.md) §"Validation". **Accepted ADRs**: none — this is a measurement task, not a design task. **Scope**: -- Run `clarion analyze` against `elspeth-slice` (smallest viable subset; target ~50 +- Run `loomweave analyze` against `elspeth-slice` (smallest viable subset; target ~50 files representative of production density) using the `AnthropicProvider` (live API) twice: first to populate the cache, second to measure cache-hit rate. - Capture: per-run cost (USD), cache-hit rate, wall-clock time, per-phase breakdown. @@ -740,12 +740,12 @@ ownership decision). **Deliverables**: - `v0.1-validation-results.md` with the three NFR rows: each is `validated`, `revised`, or `failed-and-redesigned`, with the measured numbers. -- Updated NFR rationale sections in [`../clarion/v0.1/requirements.md`](../clarion/v0.1/requirements.md) +- Updated NFR rationale sections in [`../loomweave/v0.1/requirements.md`](../loomweave/v0.1/requirements.md) reflecting reality, not speculation. **Sequencing constraints**: - Requires WP6 (LLM dispatch) at minimum. WP9 + WP10 are not strictly required (the - spike measures Clarion-only metrics) but would be present if WP11 is the last + spike measures Loomweave-only metrics) but would be present if WP11 is the last package. **Exit criteria**: @@ -760,10 +760,10 @@ response — but that is not anticipated and is not pre-allocated here. | Backlog ADR | Title | Surfaces in WP | Trigger condition | |---|---|---|---| -| ADR-005 | `.clarion/` git-committable subpaths | WP1 | When implementing `clarion install`, decide which subpaths are tracked vs ignored. | +| ADR-005 | `.loomweave/` git-committable subpaths | WP1 | When implementing `loomweave install`, decide which subpaths are tracked vs ignored. | | ADR-009 | Structured briefings vs free-form prose | WP7 | When the guidance composition algorithm reveals what structure (if any) is required. | | ADR-010 | MCP as first-class surface | WP8 | If MCP cursor semantics diverge from the HTTP-tool catalogue in a way worth pinning. | -| ADR-019 | SARIF property-bag preservation | WP10 | When the `clarion sarif import` translator's property-mapping table is concrete. | +| ADR-019 | SARIF property-bag preservation | WP10 | When the `loomweave sarif import` translator's property-mapping table is concrete. | | ADR-020 | Degraded-mode policy and explicit fallbacks | WP9 | When the suite-compat probe matrix is concrete. | None of these is a day-one blocker. Each is authored lazily, when its WP forces the @@ -774,7 +774,7 @@ decision. These are not blockers, but they are the questions a reviewer might reasonably ask that this plan does not yet answer: -- **Q1**: Are the Filigree-side `registry_backend: clarion` mode and the +- **Q1**: Are the Filigree-side `registry_backend: loomweave` mode and the Wardline-config ingestion in WP9 testable in CI without a live Filigree/Wardline install? Plan assumes yes (mocked HTTP server + checked-in `wardline.yaml` fixture) but the first WP9 task should confirm. @@ -782,7 +782,7 @@ that this plan does not yet answer: responses correctly? If WP6 needs streaming, the recording format may need to be designed for it from the start. WP1 should default to non-streaming and revisit if WP6 finds it necessary. -- **Q3**: Should `clarion analyze --no-llm` be a permanent supported mode (used by +- **Q3**: Should `loomweave analyze --no-llm` be a permanent supported mode (used by WP4 testing forever) or a transitional flag removed at WP6 exit? Plan assumes permanent — it has independent value as a "structural-only catalog" mode for users who don't want LLM cost. @@ -808,9 +808,9 @@ Seeding is **not** done by this plan; it is a follow-up action item. ## 10. References -- [Clarion v0.1 requirements](../clarion/v0.1/requirements.md) -- [Clarion v0.1 system design](../clarion/v0.1/system-design.md) -- [Clarion v0.1 detailed design](../clarion/v0.1/detailed-design.md) -- [Clarion ADR index](../clarion/adr/README.md) +- [Loomweave v0.1 requirements](../loomweave/v0.1/requirements.md) +- [Loomweave v0.1 system design](../loomweave/v0.1/system-design.md) +- [Loomweave v0.1 detailed design](../loomweave/v0.1/detailed-design.md) +- [Loomweave ADR index](../loomweave/adr/README.md) - [v0.1 scope commitments memo](./v0.1-scope-plans/v0.1-scope-commitments.md) -- [Loom suite doctrine](../suite/loom.md) +- [Weft suite doctrine](../suite/weft.md) diff --git a/docs/implementation/v0.1-publish/thread-1-pre-publish-blockers.md b/docs/implementation/v0.1-publish/thread-1-pre-publish-blockers.md index 4deeb35b..209e452d 100644 --- a/docs/implementation/v0.1-publish/thread-1-pre-publish-blockers.md +++ b/docs/implementation/v0.1-publish/thread-1-pre-publish-blockers.md @@ -9,7 +9,7 @@ **Scope discipline**: this is *only* Thread 1 (pre-publish operational/security blockers). Two adjacent threads exist and are NOT in this program: - **Thread 2** — reconcile `CON-FILIGREE-02` ("Filigree `registry_backend` is a hard v0.1 dependency") against the 2026-05 scope amendment (deferred WP9-B + WP10 to v0.2). One-day editorial pass in `requirements.md` + amendment memo; out of scope here. -- **Thread 3** — dogfood pass: `clarion analyze` + reproduce the arch-analysis findings via the 7 MCP tools against Clarion / Filigree / Wardline themselves. Out of scope here. +- **Thread 3** — dogfood pass: `loomweave analyze` + reproduce the arch-analysis findings via the 7 MCP tools against Loomweave / Filigree / Wardline themselves. Out of scope here. --- @@ -20,12 +20,12 @@ Four workstreams. **A** is the only one with significant engineering weight; **B ``` A WP5 pre-ingest secret scanner ─────────────────────┐ B Operator-facing entry surface ─────────────────────┤ -C Distribution mechanics + clarion install --force ──┤ +C Distribution mechanics + loomweave install --force ──┤ ↓ D External-operator smoke test (gate) ``` -**A** ships in parallel with **B** and **C**: the scanner is a `clarion-core`-internal change; the docs / packaging touch repo-root + CI + plugin manifests. No file-level collisions expected. +**A** ships in parallel with **B** and **C**: the scanner is a `loomweave-core`-internal change; the docs / packaging touch repo-root + CI + plugin manifests. No file-level collisions expected. **D** is the publish gate. Until D is green on a fresh VM, no `v0.1` tag. @@ -48,7 +48,7 @@ The L-* arch-analysis items already in flight (L-1 `--force`, L-7 blank actor, e ### A.0 Spec source -[ADR-013](../../clarion/adr/ADR-013-pre-ingest-secret-scanner.md) fully specifies behaviour, rule set, baseline format, override semantics, and plugin-boundary interaction. It is Accepted; do not re-litigate. The `system-design.md` §10 paragraph this ADR formalises was retained when ADR-013 was authored. +[ADR-013](../../loomweave/adr/ADR-013-pre-ingest-secret-scanner.md) fully specifies behaviour, rule set, baseline format, override semantics, and plugin-boundary interaction. It is Accepted; do not re-litigate. The `system-design.md` §10 paragraph this ADR formalises was retained when ADR-013 was authored. The requirements floor: @@ -58,14 +58,14 @@ The requirements floor: ### A.1 Crate layout decision -**Decision required at A.1**: does the scanner ship as a new sibling crate `crates/clarion-scanner/`, or as a `clarion-core::secret_scanner` module? +**Decision required at A.1**: does the scanner ship as a new sibling crate `crates/loomweave-scanner/`, or as a `loomweave-core::secret_scanner` module? -**Recommendation**: new sibling crate `crates/clarion-scanner/`. Reasons: -1. ADR-013 calls out the implementation as `clarion_scanner` crate (line 40); use the name the ADR uses. +**Recommendation**: new sibling crate `crates/loomweave-scanner/`. Reasons: +1. ADR-013 calls out the implementation as `loomweave_scanner` crate (line 40); use the name the ADR uses. 2. The rule set is data-heavy (regex tables + entropy tuning) and benefits from being a leaf crate with no `tokio` / `rusqlite` deps. -3. Keeps `clarion-core/src/plugin/host.rs` from growing further (currently 3 126 LOC; arch-analysis §5.4 A-3 named this an accepted-but-watched risk). +3. Keeps `loomweave-core/src/plugin/host.rs` from growing further (currently 3 126 LOC; arch-analysis §5.4 A-3 named this an accepted-but-watched risk). -The CLI consumes it via `clarion-cli/src/analyze.rs` directly; the writer-actor never sees it (findings flow through the existing `HostFinding` → `WriterCmd::InsertEntity` path with `properties.briefing_blocked = "secret_present"` plus a `CLA-SEC-SECRET-DETECTED` finding row). +The CLI consumes it via `loomweave-cli/src/analyze.rs` directly; the writer-actor never sees it (findings flow through the existing `HostFinding` → `WriterCmd::InsertEntity` path with `properties.briefing_blocked = "secret_present"` plus a `LMWV-SEC-SECRET-DETECTED` finding row). ### A.2 Task breakdown @@ -74,9 +74,9 @@ Tasks are sized to fit a single agent pass each (≤ ~500 LOC plus tests). They #### Task 1 — Rule set + pattern registry **Files**: -- Create: `crates/clarion-scanner/Cargo.toml` (workspace member) -- Create: `crates/clarion-scanner/src/lib.rs`, `src/patterns.rs`, `src/entropy.rs` -- Modify: `Cargo.toml` (workspace root — add `crates/clarion-scanner`) +- Create: `crates/loomweave-scanner/Cargo.toml` (workspace member) +- Create: `crates/loomweave-scanner/src/lib.rs`, `src/patterns.rs`, `src/entropy.rs` +- Modify: `Cargo.toml` (workspace root — add `crates/loomweave-scanner`) **Scope**: implement the named-credential regex table from ADR-013 lines 35–38 (AWS, GitHub, Anthropic, OpenAI, Stripe, Slack, JWT, private-key headers, contextual-credential names). Implement Shannon entropy over a byte slice. Public surface: @@ -84,7 +84,7 @@ Tasks are sized to fit a single agent pass each (≤ ~500 LOC plus tests). They pub struct Scanner { /* compiled regex set + entropy thresholds */ } pub struct Detection { pub rule_id: &'static str, // e.g. "AwsAccessKeyId", "HighEntropyBase64" - pub category: SecretCategory, // for the CLA-SEC- finding mapping + pub category: SecretCategory, // for the LMWV-SEC- finding mapping pub byte_offset: usize, pub line_number: u32, pub matched_len: usize, // never persist the literal bytes @@ -96,17 +96,17 @@ impl Scanner { } ``` -**Tests**: one positive + one negative fixture per rule (AWS access key, GitHub PAT, RSA private-key header, etc.). For high-entropy detection: a 32-char base64-looking string passes; a UUID fails. Fixtures live in `crates/clarion-scanner/tests/fixtures/`. +**Tests**: one positive + one negative fixture per rule (AWS access key, GitHub PAT, RSA private-key header, etc.). For high-entropy detection: a 32-char base64-looking string passes; a UUID fails. Fixtures live in `crates/loomweave-scanner/tests/fixtures/`. -**Exit**: `cargo test -p clarion-scanner` green; clippy `-D warnings` clean; the crate compiles with no `tokio` / `rusqlite` / `serde_norway` deps (assert via `cargo tree -p clarion-scanner | head -30`). +**Exit**: `cargo test -p loomweave-scanner` green; clippy `-D warnings` clean; the crate compiles with no `tokio` / `rusqlite` / `serde_norway` deps (assert via `cargo tree -p loomweave-scanner | head -30`). -#### Task 2 — Baseline parser (`.clarion/secrets-baseline.yaml`) +#### Task 2 — Baseline parser (`.loomweave/secrets-baseline.yaml`) **Files**: -- Create: `crates/clarion-scanner/src/baseline.rs` -- Modify: `crates/clarion-scanner/src/lib.rs` (re-export) +- Create: `crates/loomweave-scanner/src/baseline.rs` +- Modify: `crates/loomweave-scanner/src/lib.rs` (re-export) -**Scope**: parse the `detect-secrets` v1.x baseline schema (ADR-013 lines 60–68). Required schema fields: `version` (must equal `"1.0"`), `results` (map of relative-path → list of `{type, hashed_secret, line_number, is_secret, justification}`). The `justification` field is required (ADR-013 line 71). Missing → emit `CLA-INFRA-SECRET-BASELINE-NO-JUSTIFICATION` (rule constant in `clarion-scanner` consumed by the CLI). +**Scope**: parse the `detect-secrets` v1.x baseline schema (ADR-013 lines 60–68). Required schema fields: `version` (must equal `"1.0"`), `results` (map of relative-path → list of `{type, hashed_secret, line_number, is_secret, justification}`). The `justification` field is required (ADR-013 line 71). Missing → emit `LMWV-INFRA-SECRET-BASELINE-NO-JUSTIFICATION` (rule constant in `loomweave-scanner` consumed by the CLI). Use `serde_norway` (already a workspace dep; replaces `serde_yaml` per commit `9ffc5c8`). Match-then-suppress at `Detection` granularity, keyed on `(file_path, hashed_secret, line_number)`. Provide: @@ -115,18 +115,18 @@ pub fn load_baseline(path: &Path) -> Result; pub fn suppress(detections: Vec, baseline: &Baseline, file: &Path) -> SuppressionResult; ``` -Where `SuppressionResult { allowed: Vec, suppressed: Vec }` — both retained so the CLI can emit `CLA-SEC-SECRET-DETECTED` for unsuppressed *and* a `CLA-INFRA-SECRET-BASELINE-MATCH` info-level finding when a baseline entry actually fires (audit surface per `NFR-SEC-04`). +Where `SuppressionResult { allowed: Vec, suppressed: Vec }` — both retained so the CLI can emit `LMWV-SEC-SECRET-DETECTED` for unsuppressed *and* a `LMWV-INFRA-SECRET-BASELINE-MATCH` info-level finding when a baseline entry actually fires (audit surface per `NFR-SEC-04`). **Tests**: fixture baseline file + scanner output → asserts allowed/suppressed partitioning; fixture missing-justification → returns the expected `BaselineError` variant; baseline path absent → returns an empty baseline (no error). -**Exit**: `cargo test -p clarion-scanner` green including the baseline module; baseline round-trip test (parse → serialise → parse) byte-identical. +**Exit**: `cargo test -p loomweave-scanner` green including the baseline module; baseline round-trip test (parse → serialise → parse) byte-identical. #### Task 3 — CLI wiring: pre-ingest hook in `analyze::run` **Files**: -- Modify: `crates/clarion-cli/src/analyze.rs` -- Modify: `crates/clarion-cli/Cargo.toml` (add `clarion-scanner` dep) -- Create: `crates/clarion-cli/src/secret_scan.rs` (orchestration module — keep `analyze.rs` from growing further per arch-analysis H-1) +- Modify: `crates/loomweave-cli/src/analyze.rs` +- Modify: `crates/loomweave-cli/Cargo.toml` (add `loomweave-scanner` dep) +- Create: `crates/loomweave-cli/src/secret_scan.rs` (orchestration module — keep `analyze.rs` from growing further per arch-analysis H-1) **Scope**: between the source-tree walk (currently `collect_source_files` at `analyze.rs:182`) and the per-plugin processing loop, insert a pre-ingest scan pass. For each file in `source_files`: @@ -134,37 +134,37 @@ Where `SuppressionResult { allowed: Vec, suppressed: Vec } 2. Run `Scanner::scan_bytes`; apply baseline suppression. 3. If `allowed` non-empty: - Mark the file `briefing_blocked: secret_present` in a `BTreeMap` carried through to the per-plugin pass. - - Accumulate `CLA-SEC-SECRET-DETECTED` findings (one per detection, severity `error`). + - Accumulate `LMWV-SEC-SECRET-DETECTED` findings (one per detection, severity `error`). 4. Files in this map still go to the plugin (structural extraction runs, ADR-013 line 46) but the entities emitted carry `properties.briefing_blocked = "secret_present"`. -The `briefing_blocked` flag plumbs through `RawEntity.extra` → `EntityRecord.properties_json` → the `entities.properties` column. The MCP `summary` tool already reads `entities.properties` for cache lookup (see `clarion-mcp/src/lib.rs:1010`); add the block check there in Task 5. +The `briefing_blocked` flag plumbs through `RawEntity.extra` → `EntityRecord.properties_json` → the `entities.properties` column. The MCP `summary` tool already reads `entities.properties` for cache lookup (see `loomweave-mcp/src/lib.rs:1010`); add the block check there in Task 5. -**Tests**: integration test in `crates/clarion-cli/tests/analyze.rs` — fixture project with a `.env` containing a fake AWS key. Assert: +**Tests**: integration test in `crates/loomweave-cli/tests/analyze.rs` — fixture project with a `.env` containing a fake AWS key. Assert: - `analyze` exits 0 (`SoftFailed` — partial success path; arch-analysis H-1 coverage gap). - `entities` table has rows for the `.env` file's structural entities. - Those entities' `properties_json` contains `"briefing_blocked":"secret_present"`. -- `findings` table has one `CLA-SEC-SECRET-DETECTED` row referencing that file. +- `findings` table has one `LMWV-SEC-SECRET-DETECTED` row referencing that file. **Exit**: integration test green; the new `secret_scan.rs` module is < 250 LOC; `analyze.rs` net growth ≤ 30 LOC (delegate to the new module). #### Task 4 — Override semantics: `--allow-unredacted-secrets` **Files**: -- Modify: `crates/clarion-cli/src/cli.rs` (clap definition) -- Modify: `crates/clarion-cli/src/analyze.rs` + `secret_scan.rs` +- Modify: `crates/loomweave-cli/src/cli.rs` (clap definition) +- Modify: `crates/loomweave-cli/src/analyze.rs` + `secret_scan.rs` **Scope**: implement the override path per ADR-013 lines 74–82. - TTY: prompt with the detection list; require the operator to type the literal string `yes-i-understand`. -- Non-TTY: require both `--allow-unredacted-secrets` AND `--confirm-allow-unredacted-secrets=yes-i-understand`. Anything else → exit non-zero with `CLA-INFRA-SECRET-OVERRIDE-UNCONFIRMED` to stderr (do **not** silently bypass). -- When the override fires for file `F`: F's entities are NOT marked `briefing_blocked`; emit one `CLA-SEC-UNREDACTED-SECRETS-ALLOWED` finding per affected file (severity `error`, audit surface). +- Non-TTY: require both `--allow-unredacted-secrets` AND `--confirm-allow-unredacted-secrets=yes-i-understand`. Anything else → exit non-zero with `LMWV-INFRA-SECRET-OVERRIDE-UNCONFIRMED` to stderr (do **not** silently bypass). +- When the override fires for file `F`: F's entities are NOT marked `briefing_blocked`; emit one `LMWV-SEC-UNREDACTED-SECRETS-ALLOWED` finding per affected file (severity `error`, audit surface). - Record `{override_used: true, files_affected: [...]}` in `runs.stats` (the existing `runs.stats` text column). **TTY detection**: use `std::io::IsTerminal` on stdin; this is in `std` since Rust 1.70 and the workspace's `rust-toolchain.toml` is well past that. **Tests**: -- Non-TTY override-confirmed: secret-bearing fixture + both flags → no `briefing_blocked`, one `CLA-SEC-UNREDACTED-SECRETS-ALLOWED` finding per file. -- Non-TTY override-unconfirmed: only `--allow-unredacted-secrets` → exit code 78 (`EX_CONFIG` per `sysexits.h`; pick once, document in `cli.rs`); `CLA-INFRA-SECRET-OVERRIDE-UNCONFIRMED` finding NOT persisted (run never started); stderr contains the rule-ID for the operator to grep. +- Non-TTY override-confirmed: secret-bearing fixture + both flags → no `briefing_blocked`, one `LMWV-SEC-UNREDACTED-SECRETS-ALLOWED` finding per file. +- Non-TTY override-unconfirmed: only `--allow-unredacted-secrets` → exit code 78 (`EX_CONFIG` per `sysexits.h`; pick once, document in `cli.rs`); `LMWV-INFRA-SECRET-OVERRIDE-UNCONFIRMED` finding NOT persisted (run never started); stderr contains the rule-ID for the operator to grep. - TTY path: separate `expectrl`-style test or skip with a `#[ignore]` and document; TTY behaviour is verified manually in Workstream D. **Exit**: integration tests green; the override surface has both happy-path and the "footgun absent confirmation" path covered. @@ -172,8 +172,8 @@ The `briefing_blocked` flag plumbs through `RawEntity.extra` → `EntityRecord.p #### Task 5 — MCP-side awareness of `briefing_blocked` **Files**: -- Modify: `crates/clarion-mcp/src/lib.rs` (the `summary` tool dispatch path) -- Modify: `crates/clarion-mcp/tests/storage_tools.rs` +- Modify: `crates/loomweave-mcp/src/lib.rs` (the `summary` tool dispatch path) +- Modify: `crates/loomweave-mcp/tests/storage_tools.rs` **Scope**: when `summary(id)` is called on an entity whose `properties.briefing_blocked == "secret_present"`, return an envelope shape (do **not** invoke the LLM, do **not** consume budget): @@ -182,11 +182,11 @@ The `briefing_blocked` flag plumbs through `RawEntity.extra` → `EntityRecord.p "entity_id": "python:function:demo.foo|function", "summary": null, "briefing_blocked": "secret_present", - "remediation": "File flagged by pre-ingest secret scan. Fix the secret or whitelist via .clarion/secrets-baseline.yaml; ADR-013." + "remediation": "File flagged by pre-ingest secret scan. Fix the secret or whitelist via .loomweave/secrets-baseline.yaml; ADR-013." } ``` -This is the consult-mode-agent surface for "the absence of a summary is policy, not pipeline failure" (ADR-013 line 49). The four already-existing `issues_unavailable` envelopes in `clarion-mcp::filigree` are the precedent for this envelope shape. +This is the consult-mode-agent surface for "the absence of a summary is policy, not pipeline failure" (ADR-013 line 49). The four already-existing `issues_unavailable` envelopes in `loomweave-mcp::filigree` are the precedent for this envelope shape. **Tests**: storage-tools test with a fixture entity flagged `briefing_blocked` → `summary` tool returns the envelope; no row is added to `summary_cache`; the budget ledger is untouched. @@ -195,9 +195,9 @@ This is the consult-mode-agent surface for "the absence of a summary is policy, #### Task 6 — Rule catalogue entries in `detailed-design.md` **Files**: -- Modify: `docs/clarion/1.0/detailed-design.md` (§5 rule catalogue) +- Modify: `docs/loomweave/1.0/detailed-design.md` (§5 rule catalogue) -**Scope**: append rule rows for `CLA-SEC-SECRET-DETECTED`, `CLA-SEC-UNREDACTED-SECRETS-ALLOWED`, `CLA-INFRA-SECRET-BASELINE-NO-JUSTIFICATION`, `CLA-INFRA-SECRET-BASELINE-MATCH`, `CLA-INFRA-SECRET-OVERRIDE-UNCONFIRMED`. Each row: rule-ID, severity, category, one-sentence description, one-sentence remediation, ADR pointer. +**Scope**: append rule rows for `LMWV-SEC-SECRET-DETECTED`, `LMWV-SEC-UNREDACTED-SECRETS-ALLOWED`, `LMWV-INFRA-SECRET-BASELINE-NO-JUSTIFICATION`, `LMWV-INFRA-SECRET-BASELINE-MATCH`, `LMWV-INFRA-SECRET-OVERRIDE-UNCONFIRMED`. Each row: rule-ID, severity, category, one-sentence description, one-sentence remediation, ADR pointer. This makes the WP9-B Filigree-emission story (deferred to v0.2) able to round-trip these IDs without a separate spec pass. @@ -209,7 +209,7 @@ This makes the WP9-B Filigree-emission story (deferred to v0.2) able to round-tr - Create: `docs/operator/secret-scanning.md` - Modify: `docs/operator/README.md` (add link) -**Scope**: operator-facing doc — what gets blocked, how to whitelist via `.clarion/secrets-baseline.yaml`, what the override does, how to find the audit trail (`findings` table queries, future Filigree integration). One page, ≤ 250 lines. Link from the top-level README (Workstream B Task 1). +**Scope**: operator-facing doc — what gets blocked, how to whitelist via `.loomweave/secrets-baseline.yaml`, what the override does, how to find the audit trail (`findings` table queries, future Filigree integration). One page, ≤ 250 lines. Link from the top-level README (Workstream B Task 1). **Exit**: a non-engineer can read this doc and resolve a baseline false-positive without reading ADR-013. @@ -218,7 +218,7 @@ This makes the WP9-B Filigree-emission story (deferred to v0.2) able to round-tr All tasks green; in addition: - `cargo test --workspace --all-features` passes. - Existing CI gates (ADR-023 floor) unchanged in pass status. -- A new E2E test under `tests/e2e/` (parallels `sprint_1_walking_skeleton.sh`) runs `clarion install && clarion analyze` against a fixture project containing one known secret and asserts: exit 0, entities present, briefing_blocked flagged, finding persisted. +- A new E2E test under `tests/e2e/` (parallels `sprint_1_walking_skeleton.sh`) runs `loomweave install && loomweave analyze` against a fixture project containing one known secret and asserts: exit 0, entities present, briefing_blocked flagged, finding persisted. - The Sprint-1 walking-skeleton E2E continues to pass (no regression on the clean-fixture path). --- @@ -230,18 +230,18 @@ All tasks green; in addition: **Files**: - Create: `README.md` at repo root. -**Scope**: there is no top-level README currently. The reader-ladder under `docs/suite/briefing.md` → `docs/suite/loom.md` → `docs/clarion/1.0/README.md` assumes the reader already knows to start there. A first-time visitor (PyPI / crates.io / GitHub front page) has no entry point. +**Scope**: there is no top-level README currently. The reader-ladder under `docs/suite/briefing.md` → `docs/suite/weft.md` → `docs/loomweave/1.0/README.md` assumes the reader already knows to start there. A first-time visitor (PyPI / crates.io / GitHub front page) has no entry point. The README must answer, in order: -1. **What this is** — one paragraph. Use the briefing's framing ("Clarion is a code-archaeology tool…") but compress to ~80 words. +1. **What this is** — one paragraph. Use the briefing's framing ("Loomweave is a code-archaeology tool…") but compress to ~80 words. 2. **What it does today** — bullet list of the 7 MCP tools and what each answers, with one example invocation each. -3. **Quick start** — `clarion install && clarion analyze && clarion serve`, with the expected stdout shapes. Link to the operator tutorial (Task B.2). +3. **Quick start** — `loomweave install && loomweave analyze && loomweave serve`, with the expected stdout shapes. Link to the operator tutorial (Task B.2). 4. **Status** — explicit "v0.1 — Python only; structural + on-demand LLM summarisation; Filigree finding emission deferred to v0.2." Quote the scope: don't oversell. -5. **Project layout** — three-sentence map (Rust workspace + Python plugin + docs) with links to `docs/clarion/1.0/README.md` for the design ladder and `docs/clarion/adr/README.md` for the ADR index. +5. **Project layout** — three-sentence map (Rust workspace + Python plugin + docs) with links to `docs/loomweave/1.0/README.md` for the design ladder and `docs/loomweave/adr/README.md` for the ADR index. 6. **Contributing** — pointer to `CLAUDE.md` and the test commands (ADR-023 floor). -**Length target**: ≤ 200 lines. No installation instructions deeper than `cargo install ...` + `pipx install clarion-plugin-python` (Workstream C delivers the actual commands). +**Length target**: ≤ 200 lines. No installation instructions deeper than `cargo install ...` + `pipx install loomweave-plugin-python` (Workstream C delivers the actual commands). **Exit**: a developer who has never seen the repo can answer "is this for me?" in 60 seconds. @@ -251,10 +251,10 @@ The README must answer, in order: - Create: `docs/operator/getting-started.md` - Modify: `docs/operator/README.md` (index) -**Scope**: a single-flow tutorial: install Clarion, run against a tiny example repo provided in `examples/quickstart-repo/` (or use `crates/clarion-plugin-fixture`'s test inputs — pick one, document), connect a consult-mode agent over MCP, ask one question, see a real answer. Includes: +**Scope**: a single-flow tutorial: install Loomweave, run against a tiny example repo provided in `examples/quickstart-repo/` (or use `crates/loomweave-plugin-fixture`'s test inputs — pick one, document), connect a consult-mode agent over MCP, ask one question, see a real answer. Includes: - Prerequisite versions (Rust toolchain per `rust-toolchain.toml`; Python 3.11+; `pyright-langserver` 1.1.409 — pinned in the Python plugin manifest). -- Required env vars for live LLM calls (`OPENROUTER_API_KEY`). Note that `clarion analyze` works without the LLM (structural-only); summarisation requires the key. +- Required env vars for live LLM calls (`OPENROUTER_API_KEY`). Note that `loomweave analyze` works without the LLM (structural-only); summarisation requires the key. - The seven MCP tool names with one example each. - Troubleshooting: plugin not discovered → check `$PATH`; secret block fires → link to `secret-scanning.md`. @@ -269,16 +269,16 @@ The README must answer, in order: ## 4. Workstream C — Distribution + install ergonomics -### C.1 `clarion install --force` (arch-analysis L-1) +### C.1 `loomweave install --force` (arch-analysis L-1) **Files**: -- Modify: `crates/clarion-cli/src/install.rs`, `src/cli.rs` +- Modify: `crates/loomweave-cli/src/install.rs`, `src/cli.rs` -**Scope**: the `--force` flag is declared in the clap definition (`cli.rs:17–18`) and rejected at runtime (`install.rs:87–92`). Wire it up: when set, remove existing `.clarion/` *atomically* (rename-to-tmpdir + remove tmpdir, never partial deletes), then proceed. Refuse if `.clarion/clarion.db` shows a `runs` row with `status='running'` (someone else is using this DB) unless `--force --force` (double-force) is passed — the operator owns the override. +**Scope**: the `--force` flag is declared in the clap definition (`cli.rs:17–18`) and rejected at runtime (`install.rs:87–92`). Wire it up: when set, remove existing `.loomweave/` *atomically* (rename-to-tmpdir + remove tmpdir, never partial deletes), then proceed. Refuse if `.loomweave/loomweave.db` shows a `runs` row with `status='running'` (someone else is using this DB) unless `--force --force` (double-force) is passed — the operator owns the override. This closes Filigree issue `clarion-2d178ddda0` (P3, ready). -**Exit**: integration test in `crates/clarion-cli/tests/install.rs` exercises the three paths (no `.clarion/` → install; `.clarion/` present without `--force` → exit non-zero with helpful message; `.clarion/` present with `--force` → atomic replace, success). +**Exit**: integration test in `crates/loomweave-cli/tests/install.rs` exercises the three paths (no `.loomweave/` → install; `.loomweave/` present without `--force` → exit non-zero with helpful message; `.loomweave/` present with `--force` → atomic replace, success). ### C.2 Distribution decision @@ -287,8 +287,8 @@ This closes Filigree issue `clarion-2d178ddda0` (P3, ready). | Option | Rust binary | Python plugin | |---|---|---| | (a) Source-only — `cargo install --git` + `pipx install --editable git+https://…` | repo URL | repo URL | -| (b) GitHub Releases — pre-built binaries per platform; plugin sdist attached | `gh release download` + `mv to ~/.cargo/bin/` | `pipx install ./clarion-plugin-python-*.tar.gz` | -| (c) Public registries — `cargo install clarion-cli` (crates.io) + `pipx install clarion-plugin-python` (PyPI) | crates.io | PyPI | +| (b) GitHub Releases — pre-built binaries per platform; plugin sdist attached | `gh release download` + `mv to ~/.cargo/bin/` | `pipx install ./loomweave-plugin-python-*.tar.gz` | +| (c) Public registries — `cargo install loomweave-cli` (crates.io) + `pipx install loomweave-plugin-python` (PyPI) | crates.io | PyPI | **Recommendation**: (b) for the v0.1 publish, (c) for v0.2 once names are reserved and the publish cadence is established. Reasons: - (a) burns ten minutes of cargo compile on every new install — bad first impression. @@ -299,12 +299,12 @@ If (b) is chosen, file an ADR (ADR-032 candidate: "v0.1 distribution via GitHub **Files** (if (b) chosen): - Create: `.github/workflows/release.yml` -- Create: `docs/clarion/adr/ADR-032-v0.1-distribution.md` (or reuse the next free ADR number — check the index) +- Create: `docs/loomweave/adr/ADR-032-v0.1-distribution.md` (or reuse the next free ADR number — check the index) **Workflow shape**: - Trigger: `push: tags: ['v0.1*']`. -- Matrix: `x86_64-unknown-linux-gnu`, `aarch64-apple-darwin`, `x86_64-apple-darwin` minimum. Windows is out of v0.1 scope (no requirement; `setrlimit` is Unix-only — `clarion-core::plugin::limits`). -- Build the `clarion` binary; build the Python plugin sdist (`python -m build --sdist plugins/python`). +- Matrix: `x86_64-unknown-linux-gnu`, `aarch64-apple-darwin`, `x86_64-apple-darwin` minimum. Windows is out of v0.1 scope (no requirement; `setrlimit` is Unix-only — `loomweave-core::plugin::limits`). +- Build the `loomweave` binary; build the Python plugin sdist (`python -m build --sdist plugins/python`). - Attach both to the GH release; auto-generate release notes from `git log v0.1-sprint-2..HEAD` filtered to merge commits. **Exit**: a tag `v0.1.0` push produces a GH release with downloadable artifacts; a hand-test on a fresh VM (Workstream D) installs from those artifacts successfully. @@ -313,18 +313,18 @@ If (b) is chosen, file an ADR (ADR-032 candidate: "v0.1 distribution via GitHub **Files**: - Modify: `docs/operator/getting-started.md` (Workstream B Task 2) -- Possibly: `crates/clarion-cli/src/cli.rs` (add a `clarion doctor` subcommand — optional, see below) +- Possibly: `crates/loomweave-cli/src/cli.rs` (add a `loomweave doctor` subcommand — optional, see below) -**Scope**: plugin discovery walks `$PATH` looking for `clarion-plugin-*` executables (ADR-002 / L9 convention). For someone running `pipx install clarion-plugin-python`, the plugin lands in `~/.local/bin/` — which is `$PATH` on most Linux but not always on macOS, and is silent when missing. Two paths: +**Scope**: plugin discovery walks `$PATH` looking for `loomweave-plugin-*` executables (ADR-002 / L9 convention). For someone running `pipx install loomweave-plugin-python`, the plugin lands in `~/.local/bin/` — which is `$PATH` on most Linux but not always on macOS, and is silent when missing. Two paths: - Document the `$PATH` requirement crisply in the tutorial. Cheap; punts the problem. -- Add a `clarion doctor` subcommand that prints discovered plugins and a yes/no for "found a Python plugin." Spends a day; the operator gets a self-diagnosis path. +- Add a `loomweave doctor` subcommand that prints discovered plugins and a yes/no for "found a Python plugin." Spends a day; the operator gets a self-diagnosis path. -**Recommendation**: tutorial only for v0.1; `clarion doctor` is a v0.2 nice-to-have. Document the failure mode (zero plugins discovered → `SkippedNoPlugins`, which currently exits 0 — verify and note this). +**Recommendation**: tutorial only for v0.1; `loomweave doctor` is a v0.2 nice-to-have. Document the failure mode (zero plugins discovered → `SkippedNoPlugins`, which currently exits 0 — verify and note this). ### C.4 Workstream C exit criteria -- `clarion install --force` lands and tests green. +- `loomweave install --force` lands and tests green. - Distribution decision recorded as an ADR; the chosen path is exercised end-to-end (a release is produced). - Tutorial documents the installation path that the chosen distribution implies. @@ -343,9 +343,9 @@ Steps: 1. Install Rust binary per Workstream C's chosen path. 2. Install Python plugin per the same. -3. `clarion install` against a small public Python project (suggestion: `requests==2.32.x` source tarball — ~7k LOC, well-behaved, no secrets). -4. `clarion analyze` — assert exit 0, non-empty entities count. -5. `clarion serve` in one shell; connect a consult-mode agent via MCP in another (Claude Desktop or `mcptool` CLI — pick one, document). +3. `loomweave install` against a small public Python project (suggestion: `requests==2.32.x` source tarball — ~7k LOC, well-behaved, no secrets). +4. `loomweave analyze` — assert exit 0, non-empty entities count. +5. `loomweave serve` in one shell; connect a consult-mode agent via MCP in another (Claude Desktop or `mcptool` CLI — pick one, document). 6. Ask the agent three pre-scripted questions: - "List the top-level modules in this project." - "What calls `requests.get`?" @@ -370,7 +370,7 @@ The smoke test passes if all eight steps complete without operator improvisation To prevent scope creep mid-execution, the following are explicitly OUT: - **WP9-B / WP10** (findings emission to Filigree, registry_backend, SARIF translator) — Thread 2; v0.2 in the amended plan. -- **WP4 phases beyond Phase 1** (clustering, Phase-7 `CLA-*` cross-cutting rules, Phase-8 entity-set diff) — v0.2. +- **WP4 phases beyond Phase 1** (clustering, Phase-7 `LMWV-*` cross-cutting rules, Phase-8 entity-set diff) — v0.2. - **WP7 guidance system** — v0.2. - **Multi-language plugins** — `NG-15`, v0.2+. - **MCP `summary(id)` module/subsystem aggregation** — `ADR-030` defers to v0.2; v0.1 ships leaf-only. @@ -395,7 +395,7 @@ filigree create --type=task --title="Publish-prep: top-level README" --labels="r filigree create --type=task --title="Publish-prep: getting-started tutorial" --labels="release:v0.1,sprint:3,docs" # Workstream C -filigree create --type=task --title="Publish-prep: clarion install --force" --labels="release:v0.1,sprint:3,crate:cli" +filigree create --type=task --title="Publish-prep: loomweave install --force" --labels="release:v0.1,sprint:3,crate:cli" # (Folds in clarion-2d178ddda0; close that with a forward-pointer.) filigree create --type=task --title="Publish-prep: choose v0.1 distribution path + ADR + release workflow" \ --labels="release:v0.1,sprint:3" @@ -422,12 +422,12 @@ filigree close clarion-2d178ddda0 --reason="superseded by Publish-prep --force t ## 8. References -- [ADR-013 — Pre-ingest secret scanner with LLM-dispatch block](../../clarion/adr/ADR-013-pre-ingest-secret-scanner.md) -- [ADR-007 — Summary cache key (briefing_blocked semantics)](../../clarion/adr/ADR-007-summary-cache-key.md) -- [ADR-021 — Plugin authority hybrid (path-jail upstream of scanner)](../../clarion/adr/ADR-021-plugin-authority-hybrid.md) -- [ADR-023 — Tooling baseline (CI floor every PR must clear)](../../clarion/adr/ADR-023-tooling-baseline.md) -- [Requirements — NFR-SEC-01, NFR-SEC-04, NFR-OPS-01, NFR-OPS-04](../../clarion/v0.1/requirements.md) -- [System design — §10 Security, pre-ingest redaction paragraph](../../clarion/v0.1/system-design.md) +- [ADR-013 — Pre-ingest secret scanner with LLM-dispatch block](../../loomweave/adr/ADR-013-pre-ingest-secret-scanner.md) +- [ADR-007 — Summary cache key (briefing_blocked semantics)](../../loomweave/adr/ADR-007-summary-cache-key.md) +- [ADR-021 — Plugin authority hybrid (path-jail upstream of scanner)](../../loomweave/adr/ADR-021-plugin-authority-hybrid.md) +- [ADR-023 — Tooling baseline (CI floor every PR must clear)](../../loomweave/adr/ADR-023-tooling-baseline.md) +- [Requirements — NFR-SEC-01, NFR-SEC-04, NFR-OPS-01, NFR-OPS-04](../../loomweave/v0.1/requirements.md) +- [System design — §10 Security, pre-ingest redaction paragraph](../../loomweave/v0.1/system-design.md) - [v0.1-plan.md — WP5 scope](../v0.1-plan.md#wp5--pre-ingest-secret-scanner) - [Sprint-2 scope amendment — explicit WP5 deferral rationale, "production deployment against unknown corpora gates on this returning"](../sprint-2/scope-amendment-2026-05.md) - [Arch analysis final report — current RC1 architecture report](../arch-analysis-2026-05-20-2124/04-final-report.md) diff --git a/docs/implementation/v0.1-publish/ws-a-secret-scanner.md b/docs/implementation/v0.1-publish/ws-a-secret-scanner.md index 7c2005f7..53813ecd 100644 --- a/docs/implementation/v0.1-publish/ws-a-secret-scanner.md +++ b/docs/implementation/v0.1-publish/ws-a-secret-scanner.md @@ -3,8 +3,8 @@ **Status**: DRAFT — not yet seeded into Filigree; awaiting kickoff. **Predecessor**: [`thread-1-pre-publish-blockers.md` §2](./thread-1-pre-publish-blockers.md#2-workstream-a--wp5-pre-ingest-secret-scanner) **Successor**: Workstream D — external-operator smoke test (publish gate). -**Spec source**: [ADR-013 — Pre-Ingest Secret Scanner with LLM-Dispatch Block](../../clarion/adr/ADR-013-pre-ingest-secret-scanner.md) (Accepted; do not re-litigate). -**Anchoring ADRs**: [ADR-007 (cache key)](../../clarion/adr/ADR-007-summary-cache-key.md), [ADR-013](../../clarion/adr/ADR-013-pre-ingest-secret-scanner.md), [ADR-017 (severity + dedup)](../../clarion/adr/ADR-017-severity-and-dedup.md), [ADR-021 (plugin authority hybrid)](../../clarion/adr/ADR-021-plugin-authority-hybrid.md), [ADR-022 (core/plugin ontology)](../../clarion/adr/ADR-022-core-plugin-ontology.md), [ADR-023 (tooling baseline)](../../clarion/adr/ADR-023-tooling-baseline.md). +**Spec source**: [ADR-013 — Pre-Ingest Secret Scanner with LLM-Dispatch Block](../../loomweave/adr/ADR-013-pre-ingest-secret-scanner.md) (Accepted; do not re-litigate). +**Anchoring ADRs**: [ADR-007 (cache key)](../../loomweave/adr/ADR-007-summary-cache-key.md), [ADR-013](../../loomweave/adr/ADR-013-pre-ingest-secret-scanner.md), [ADR-017 (severity + dedup)](../../loomweave/adr/ADR-017-severity-and-dedup.md), [ADR-021 (plugin authority hybrid)](../../loomweave/adr/ADR-021-plugin-authority-hybrid.md), [ADR-022 (core/plugin ontology)](../../loomweave/adr/ADR-022-core-plugin-ontology.md), [ADR-023 (tooling baseline)](../../loomweave/adr/ADR-023-tooling-baseline.md). **Requirements floor**: `NFR-SEC-01` (pre-ingest scan + block + baseline), `NFR-SEC-04` (security events as findings), `NFR-OPS-01` / `NFR-OPS-04` (single-binary distribution). **Effort estimate**: 6–9 working days at agentic velocity. Tasks 1 and 2 parallelisable; 3 → 4 → 5 sequential; 6 + 7 parallel with any. @@ -17,7 +17,7 @@ names as authoritative and line numbers as historical orientation only. ## 1. Purpose WP5 closes the design-review CRITICAL flag on secret exfiltration: every byte -reaching the LLM provider must first pass a Clarion-owned scanner. ADR-013 +reaching the LLM provider must first pass a Loomweave-owned scanner. ADR-013 locked the behaviour; this document turns the ADR into seven implementation tasks an agent (or two agents working in parallel) can execute, and pins the file paths, public APIs, schema changes, rule-ID catalogue entries, and tests @@ -32,28 +32,28 @@ case). In scope (this work package): -- New workspace crate `crates/clarion-scanner/` implementing the ADR-013 +- New workspace crate `crates/loomweave-scanner/` implementing the ADR-013 rule set as Rust-native regex + Shannon entropy detection. -- Baseline parser for `.clarion/secrets-baseline.yaml` (`detect-secrets` +- Baseline parser for `.loomweave/secrets-baseline.yaml` (`detect-secrets` v1.x format). -- CLI wiring: pre-ingest pass in `clarion-cli::analyze::run` between +- CLI wiring: pre-ingest pass in `loomweave-cli::analyze::run` between source-tree walk and per-plugin dispatch. - `--allow-unredacted-secrets` override surface with TTY + non-TTY gates. - MCP-side awareness of `briefing_blocked: secret_present` in - `crates/clarion-mcp/`'s `summary` tool dispatch. -- Five new `CLA-SEC-*` / `CLA-INFRA-SECRET-*` rule-IDs in the + `crates/loomweave-mcp/`'s `summary` tool dispatch. +- Five new `LMWV-SEC-*` / `LMWV-INFRA-SECRET-*` rule-IDs in the `detailed-design.md` §5 catalogue. - Operator documentation under `docs/operator/secret-scanning.md`. Out of scope (deferred or owned elsewhere): -- Filigree emission of `CLA-SEC-*` findings — WP9-B, v0.2. +- Filigree emission of `LMWV-SEC-*` findings — WP9-B, v0.2. - Custom-rule integration from Wardline — v0.2. - Post-ingest scanning on LLM responses — v0.2+ additive defence. - Redaction-instead-of-block (ADR-013 Alternative 2) — v0.2+ with opt-in. - The override-finding monitoring loop (filigree-side dashboard work) — not part of v0.1 publish. -- Windows support — `setrlimit` is Unix-only (`clarion-core::plugin::limits`); +- Windows support — `setrlimit` is Unix-only (`loomweave-core::plugin::limits`); WP5 follows the same posture. ## 3. Locked surfaces and pre-existing context @@ -61,23 +61,23 @@ Out of scope (deferred or owned elsewhere): WP5 reads from and writes to surfaces already present at `main`: - Workspace at `Cargo.toml` (resolver 3, Rust 2024, MSRV 1.88). Current - members: `clarion-core`, `clarion-storage`, `clarion-cli`, `clarion-mcp`, - `clarion-plugin-fixture`. The scanner becomes the **sixth**. -- `crates/clarion-cli/src/analyze.rs` already contains: + members: `loomweave-core`, `loomweave-storage`, `loomweave-cli`, `loomweave-mcp`, + `loomweave-plugin-fixture`. The scanner becomes the **sixth**. +- `crates/loomweave-cli/src/analyze.rs` already contains: - `pub async fn run(project_path: PathBuf) -> Result<()>` at line 52. - `fn collect_source_files(root, wanted_extensions) -> Vec` at line 1740 — the walk WP5's pre-ingest hook intercepts immediately after. - `RunOutcome::SoftFailed { reason }` at line 1038 — the partial-success path the override-unconfirmed exit must NOT take (override-unconfirmed runs never start a `runs` row at all). -- `crates/clarion-core/src/plugin/host.rs`: +- `crates/loomweave-core/src/plugin/host.rs`: - `pub struct RawEntity { … extra … }` at line 105 — the `extra` field is the carrier for `briefing_blocked`. Per the host doc-comment at line 74 and 222, `extra` flows into `properties_json` downstream. - `pub struct HostFinding` at `plugin/host_findings.rs:84` with subcode constructors (`HostFinding::malformed_entity`, etc.) — the pattern WP5's finding constructors follow. -- `crates/clarion-mcp/src/lib.rs`: +- `crates/loomweave-mcp/src/lib.rs`: - `fn entity_properties_json(entity: &EntityRow) -> Value` at line 2382 — central reader for `properties_json`; WP5's MCP awareness layer hooks above this, before LLM dispatch. @@ -90,7 +90,7 @@ WP5 reads from and writes to surfaces already present at `main`: Surfaces this package does NOT touch: - Plugin protocol (no `analyze_file` RPC change). -- Writer-actor command set in `crates/clarion-storage/` — the scanner +- Writer-actor command set in `crates/loomweave-storage/` — the scanner produces `HostFinding`s and entity `extra` map entries; no new `WriterCmd` variants. - SQLite schema — no new tables or columns. `entities.properties` and @@ -100,14 +100,14 @@ Surfaces this package does NOT touch: The codebase has two distinct "finding" concepts; WP5 emits into one of them. -- **`HostFinding`** (`crates/clarion-core/src/plugin/host_findings.rs:84`) is +- **`HostFinding`** (`crates/loomweave-core/src/plugin/host_findings.rs:84`) is a plugin-host **integrity event**: subcode + message + structured metadata map. It carries no severity, no `rule_id`, no `(file, line)` slot. It is drained via `PluginHost::take_findings` and logged through `log_plugin_findings` (`analyze.rs:1042`). Per the doc-comment at line 79–82, "for Sprint 1 they are collected only" — they are NOT persisted to the `findings` table. -- **`FindingRecord`** (`crates/clarion-storage/src/commands.rs:113`) is the +- **`FindingRecord`** (`crates/loomweave-storage/src/commands.rs:113`) is the ADR-004 finding shape that the writer-actor persists. It carries `rule_id`, `severity`, `entity_id`, `message`, `evidence_json`, `properties_json`, lifecycle status, etc. Write path: send @@ -120,7 +120,7 @@ pattern for a CLI-side, application-level finding emitted directly through the writer. WP5's `secret_scan` module follows that pattern verbatim — build a `FindingRecord`, send via the writer's command channel, await ack. -`HostFinding` is **not** the channel for `CLA-SEC-*` / `CLA-INFRA-SECRET-*` +`HostFinding` is **not** the channel for `LMWV-SEC-*` / `LMWV-INFRA-SECRET-*` because (a) it lacks the severity / rule-ID slots ADR-013 mandates and (b) it is not persisted. The Thread-1 §2.A.1 reference to "the existing `HostFinding` → `WriterCmd::InsertEntity` path" is imprecise; the @@ -131,12 +131,12 @@ For each detection, the `FindingRecord` is populated: | Field | Value | |---|---| | `id` | `uuid::Uuid::new_v4().to_string()` | -| `tool` | `"clarion"` | +| `tool` | `"loomweave"` | | `tool_version` | `env!("CARGO_PKG_VERSION")` | | `run_id` | current run's `run_id` | -| `rule_id` | `"CLA-SEC-SECRET-DETECTED"` (or other; see §6 catalogue) | +| `rule_id` | `"LMWV-SEC-SECRET-DETECTED"` (or other; see §6 catalogue) | | `kind` | `"defect"` (per ADR-004; secrets are defects pending remediation) | -| `severity` | `"ERROR"` (or `"INFO"` for `CLA-INFRA-SECRET-BASELINE-MATCH`) | +| `severity` | `"ERROR"` (or `"INFO"` for `LMWV-INFRA-SECRET-BASELINE-MATCH`) | | `confidence` | `Some(1.0)` for named-pattern matches; `Some(0.6)` for high-entropy detections (rough heuristic — refine in Task 1) | | `confidence_basis` | `Some("pattern")` or `Some("entropy")` | | `entity_id` | `core:file:{blake3(relative_path)}` synthetic file anchor for file-level findings; the file path is also retained in `evidence_json` | @@ -150,7 +150,7 @@ For each detection, the `FindingRecord` is populated: ## 4. Architecture at a glance ``` -clarion analyze +loomweave analyze │ ├─ collect_source_files(...) [unchanged] │ @@ -198,20 +198,20 @@ depends on 3; 6 + 7 are documentation, parallel with any. ### Task 1 — Scanner crate, rule registry, entropy -**Owner**: `crates/clarion-scanner/` +**Owner**: `crates/loomweave-scanner/` **Estimated size**: 350–500 LOC including tests. #### Files | Action | Path | |---|---| -| create | `crates/clarion-scanner/Cargo.toml` | -| create | `crates/clarion-scanner/src/lib.rs` | -| create | `crates/clarion-scanner/src/patterns.rs` | -| create | `crates/clarion-scanner/src/entropy.rs` | -| create | `crates/clarion-scanner/tests/fixtures/*.txt` (positive + negative per rule) | -| create | `crates/clarion-scanner/tests/scanner.rs` | -| modify | `Cargo.toml` (workspace root) — add `crates/clarion-scanner` member | +| create | `crates/loomweave-scanner/Cargo.toml` | +| create | `crates/loomweave-scanner/src/lib.rs` | +| create | `crates/loomweave-scanner/src/patterns.rs` | +| create | `crates/loomweave-scanner/src/entropy.rs` | +| create | `crates/loomweave-scanner/tests/fixtures/*.txt` (positive + negative per rule) | +| create | `crates/loomweave-scanner/tests/scanner.rs` | +| modify | `Cargo.toml` (workspace root) — add `crates/loomweave-scanner` member | #### Scope @@ -273,14 +273,14 @@ The scanner crate is intentionally a leaf: - `sha1` (new — small, well-maintained) - No `tokio`, no `rusqlite`, no `serde_norway`. -Verification at exit: `cargo tree -p clarion-scanner | head -30` shows no +Verification at exit: `cargo tree -p loomweave-scanner | head -30` shows no `tokio` or `rusqlite` ancestors. If a downstream crate's `regex` features -leak something heavier, pin the feature set in `clarion-scanner`'s +leak something heavier, pin the feature set in `loomweave-scanner`'s `Cargo.toml`. #### Tests -`crates/clarion-scanner/tests/scanner.rs`: +`crates/loomweave-scanner/tests/scanner.rs`: - One positive + one negative fixture per named pattern (AWS access key, GitHub PAT `ghp_…`, GitHub fine-grained `github_pat_…`, Anthropic @@ -297,18 +297,18 @@ leak something heavier, pin the feature set in `clarion-scanner`'s #### Exit criteria -- `cargo test -p clarion-scanner` green. -- `cargo clippy -p clarion-scanner -- -D warnings` clean. -- `cargo tree -p clarion-scanner` shows no `tokio` / `rusqlite` / +- `cargo test -p loomweave-scanner` green. +- `cargo clippy -p loomweave-scanner -- -D warnings` clean. +- `cargo tree -p loomweave-scanner` shows no `tokio` / `rusqlite` / `serde_norway` deps. - `cargo build --workspace` green (new crate compiles into the workspace without changing existing build). --- -### Task 2 — Baseline parser (`.clarion/secrets-baseline.yaml`) +### Task 2 — Baseline parser (`.loomweave/secrets-baseline.yaml`) -**Owner**: `crates/clarion-scanner/` +**Owner**: `crates/loomweave-scanner/` **Estimated size**: 200–300 LOC including tests. **Parallelisable with Task 1**: yes — touches separate module. @@ -316,10 +316,10 @@ leak something heavier, pin the feature set in `clarion-scanner`'s | Action | Path | |---|---| -| create | `crates/clarion-scanner/src/baseline.rs` | -| modify | `crates/clarion-scanner/src/lib.rs` (re-export) | -| create | `crates/clarion-scanner/tests/fixtures/baselines/*.yaml` | -| modify | `crates/clarion-scanner/tests/scanner.rs` OR new `tests/baseline.rs` | +| create | `crates/loomweave-scanner/src/baseline.rs` | +| modify | `crates/loomweave-scanner/src/lib.rs` (re-export) | +| create | `crates/loomweave-scanner/tests/fixtures/baselines/*.yaml` | +| modify | `crates/loomweave-scanner/tests/scanner.rs` OR new `tests/baseline.rs` | #### Scope @@ -337,14 +337,14 @@ results: justification: "..." # REQUIRED (ADR-013 line 71) ``` -`justification` missing → emit `CLA-INFRA-SECRET-BASELINE-NO-JUSTIFICATION` +`justification` missing → emit `LMWV-INFRA-SECRET-BASELINE-NO-JUSTIFICATION` (at load time, surfaces during CLI startup; one finding per offending entry). Match-then-suppress at `Detection` granularity, keyed on `(file_path, hashed_secret, line_number)`. Both `allowed` and `suppressed` -partitions are retained so the CLI can emit one `CLA-SEC-SECRET-DETECTED` -per unsuppressed detection AND one `CLA-INFRA-SECRET-BASELINE-MATCH` +partitions are retained so the CLI can emit one `LMWV-SEC-SECRET-DETECTED` +per unsuppressed detection AND one `LMWV-INFRA-SECRET-BASELINE-MATCH` info-level finding per *actually-fired* baseline entry (the audit-surface requirement of `NFR-SEC-04`). @@ -418,7 +418,7 @@ missing required fields. #### Exit criteria -- `cargo test -p clarion-scanner` (combined with Task 1) green. +- `cargo test -p loomweave-scanner` (combined with Task 1) green. - Round-trip baseline test passes. - `BaselineError` is the only error type leaking out of the module; no bare `anyhow::Error` at the public surface. @@ -427,7 +427,7 @@ missing required fields. ### Task 3 — CLI wiring: pre-ingest hook in `analyze::run` -**Owner**: `crates/clarion-cli/` +**Owner**: `crates/loomweave-cli/` **Estimated size**: 300–400 LOC including the new orchestration module + tests. **Depends on**: Task 1, Task 2. @@ -435,19 +435,19 @@ missing required fields. | Action | Path | |---|---| -| create | `crates/clarion-cli/src/secret_scan.rs` (orchestration module — keep `analyze.rs` from growing further per arch-analysis H-1) | -| modify | `crates/clarion-cli/src/analyze.rs` | -| modify | `crates/clarion-cli/Cargo.toml` (add `clarion-scanner` dep) | -| modify | `crates/clarion-core/src/plugin/host.rs` (stamp `briefing_blocked` into RawEntity.extra for blocked files — see "Host integration" below) | -| create | `crates/clarion-cli/tests/secret_scan.rs` (integration tests) | -| create | `crates/clarion-cli/tests/fixtures/secret-project/...` (fixture trees) | +| create | `crates/loomweave-cli/src/secret_scan.rs` (orchestration module — keep `analyze.rs` from growing further per arch-analysis H-1) | +| modify | `crates/loomweave-cli/src/analyze.rs` | +| modify | `crates/loomweave-cli/Cargo.toml` (add `loomweave-scanner` dep) | +| modify | `crates/loomweave-core/src/plugin/host.rs` (stamp `briefing_blocked` into RawEntity.extra for blocked files — see "Host integration" below) | +| create | `crates/loomweave-cli/tests/secret_scan.rs` (integration tests) | +| create | `crates/loomweave-cli/tests/fixtures/secret-project/...` (fixture trees) | #### Scope Between `collect_source_files` (`analyze.rs:1740`) and the per-plugin processing loop, insert a pre-ingest scan pass: -1. Load `.clarion/secrets-baseline.yaml` via Task 2's `load_baseline`. +1. Load `.loomweave/secrets-baseline.yaml` via Task 2's `load_baseline`. 2. For each file in `source_files`: a. Read the file buffer. b. `Scanner::scan_bytes(&buf)` → `Vec`. @@ -467,11 +467,11 @@ processing loop, insert a pre-ingest scan pass: `briefing_blocked` as open-vocabulary. 4. Emit findings via `WriterCmd::InsertFinding` (see §3.1): - - One `CLA-SEC-SECRET-DETECTED` per `allowed` detection (severity + - One `LMWV-SEC-SECRET-DETECTED` per `allowed` detection (severity `ERROR`). - - One `CLA-INFRA-SECRET-BASELINE-MATCH` per `fired_entries` element + - One `LMWV-INFRA-SECRET-BASELINE-MATCH` per `fired_entries` element (severity `INFO`). - - One `CLA-INFRA-SECRET-BASELINE-NO-JUSTIFICATION` per + - One `LMWV-INFRA-SECRET-BASELINE-NO-JUSTIFICATION` per `BaselineError::MissingJustification` surfaced at load time. 5. Pass the `BlockSet` through to the per-plugin processing loop so the plugin host can stamp `briefing_blocked: secret_present` into the @@ -479,7 +479,7 @@ processing loop, insert a pre-ingest scan pass: #### Host integration -The plugin host (`crates/clarion-core/src/plugin/host.rs`) currently +The plugin host (`crates/loomweave-core/src/plugin/host.rs`) currently accepts `RawEntity` from the plugin and translates `extra` into `properties_json` downstream (host.rs:74, 222). WP5 adds one path: @@ -499,10 +499,10 @@ for v0.1. #### Tests -`crates/clarion-cli/tests/secret_scan.rs`: +`crates/loomweave-cli/tests/secret_scan.rs`: 1. **Happy path — clean project**: fixture with no secrets → `analyze` - exits 0; no `CLA-SEC-*` findings persisted. + exits 0; no `LMWV-SEC-*` findings persisted. 2. **One file with AWS key in `.env`**: fixture with a committed `.env` containing `AKIAIOSFODNN7EXAMPLE` → - `analyze` exits 0 with `RunOutcome::Completed`. Secret detection @@ -513,14 +513,14 @@ for v0.1. - `entities` table has rows for the `.env` file's structural entities. - Those entities' `properties_json` contains `"briefing_blocked":"secret_present"`. - - `findings` table has one `CLA-SEC-SECRET-DETECTED` row referencing + - `findings` table has one `LMWV-SEC-SECRET-DETECTED` row referencing the file. -3. **Baseline suppression**: same `.env` fixture + a `.clarion/secrets-baseline.yaml` - with the matching entry → no `CLA-SEC-SECRET-DETECTED` finding; one - `CLA-INFRA-SECRET-BASELINE-MATCH` info-level finding; no +3. **Baseline suppression**: same `.env` fixture + a `.loomweave/secrets-baseline.yaml` + with the matching entry → no `LMWV-SEC-SECRET-DETECTED` finding; one + `LMWV-INFRA-SECRET-BASELINE-MATCH` info-level finding; no `briefing_blocked` flag on entities. 4. **Baseline missing justification**: malformed baseline → - `CLA-INFRA-SECRET-BASELINE-NO-JUSTIFICATION` finding; analyze + `LMWV-INFRA-SECRET-BASELINE-NO-JUSTIFICATION` finding; analyze completes with `RunOutcome::Completed` (load errors degrade to "treat baseline as empty + surface finding", they do not abort the run and do not promote it to `SoftFailed`). @@ -530,7 +530,7 @@ for v0.1. #### Exit criteria - All five integration tests green. -- `crates/clarion-cli/src/secret_scan.rs` ≤ 250 LOC (the orchestration +- `crates/loomweave-cli/src/secret_scan.rs` ≤ 250 LOC (the orchestration module; arch-analysis H-1 ceiling). - `analyze.rs` net growth ≤ 30 LOC (the new module owns the body; analyze delegates). @@ -543,7 +543,7 @@ for v0.1. ### Task 4 — Override semantics: `--allow-unredacted-secrets` -**Owner**: `crates/clarion-cli/` +**Owner**: `crates/loomweave-cli/` **Estimated size**: 250–350 LOC including tests. **Depends on**: Task 3. @@ -551,9 +551,9 @@ for v0.1. | Action | Path | |---|---| -| modify | `crates/clarion-cli/src/cli.rs` (clap definition) | -| modify | `crates/clarion-cli/src/secret_scan.rs` (override logic) | -| modify | `crates/clarion-cli/tests/secret_scan.rs` (override tests) | +| modify | `crates/loomweave-cli/src/cli.rs` (clap definition) | +| modify | `crates/loomweave-cli/src/secret_scan.rs` (override logic) | +| modify | `crates/loomweave-cli/tests/secret_scan.rs` (override tests) | #### Scope @@ -583,24 +583,24 @@ Behaviour: findings). Document this in the operator doc so operators can safely leave the flag set in CI configurations. - **Detections present, no override flag**: existing block behaviour - (Task 3) — `briefing_blocked` stamped, `CLA-SEC-SECRET-DETECTED` + (Task 3) — `briefing_blocked` stamped, `LMWV-SEC-SECRET-DETECTED` emitted, analyze continues for structural pass. - **Detections present, `--allow-unredacted-secrets` only, TTY**: - Print detection summary to stderr (file path, line, rule-ID, but NEVER the matched bytes). - Prompt: `Type 'yes-i-understand' to proceed: `. - Match → proceed without blocking; emit - `CLA-SEC-UNREDACTED-SECRETS-ALLOWED` per affected file; record + `LMWV-SEC-UNREDACTED-SECRETS-ALLOWED` per affected file; record `override_used: true, files_affected: [...]` in `runs.stats`. - Anything else (EOF, mismatched string, `^C`) → exit 78 (`EX_CONFIG`); no `runs` row started. - **Detections present, `--allow-unredacted-secrets` only, non-TTY**: exit 78 immediately with - `CLA-INFRA-SECRET-OVERRIDE-UNCONFIRMED` rule-ID on stderr (the rule-ID + `LMWV-INFRA-SECRET-OVERRIDE-UNCONFIRMED` rule-ID on stderr (the rule-ID is the operator's grep target; no `findings` row is persisted because the run never started). - **Detections present, both flags, non-TTY, `--confirm-allow-unredacted-secrets=yes-i-understand`**: - proceed; emit `CLA-SEC-UNREDACTED-SECRETS-ALLOWED` findings; record + proceed; emit `LMWV-SEC-UNREDACTED-SECRETS-ALLOWED` findings; record `runs.stats` override entry. - **Detections present, both flags, wrong confirm value**: exit 78; rule-ID on stderr; no run row. @@ -631,11 +631,11 @@ sites). 1. **Non-TTY override-confirmed**: fixture with one AWS key, both flags set, confirm value correct → exits 0, no `briefing_blocked`, one - `CLA-SEC-UNREDACTED-SECRETS-ALLOWED` per file, `runs.stats` carries + `LMWV-SEC-UNREDACTED-SECRETS-ALLOWED` per file, `runs.stats` carries the override keys. 2. **Non-TTY override-unconfirmed**: only `--allow-unredacted-secrets`, no confirm → exit 78, stderr contains - `CLA-INFRA-SECRET-OVERRIDE-UNCONFIRMED`, no `runs` row in the DB. + `LMWV-INFRA-SECRET-OVERRIDE-UNCONFIRMED`, no `runs` row in the DB. 3. **Non-TTY override-wrong-confirm**: both flags but `--confirm-allow-unredacted-secrets=oops` → exit 78, stderr contains the rule-ID. @@ -659,7 +659,7 @@ sites). ### Task 5 — MCP-side awareness of `briefing_blocked` -**Owner**: `crates/clarion-mcp/` +**Owner**: `crates/loomweave-mcp/` **Estimated size**: 150–250 LOC including tests. **Depends on**: Task 3 (writes the flag); independent of Task 4. **Parallelisable with Task 4**: yes. @@ -668,8 +668,8 @@ sites). | Action | Path | |---|---| -| modify | `crates/clarion-mcp/src/lib.rs` (summary tool dispatch) | -| modify | `crates/clarion-mcp/tests/storage_tools.rs` (or split into a new test file) | +| modify | `crates/loomweave-mcp/src/lib.rs` (summary tool dispatch) | +| modify | `crates/loomweave-mcp/tests/storage_tools.rs` (or split into a new test file) | #### Scope @@ -689,13 +689,13 @@ When `summary(id)` is called on an entity whose "entity_id": "python:function:demo.foo|function", "summary": null, "briefing_blocked": "secret_present", - "remediation": "File flagged by pre-ingest secret scan. Fix the secret or whitelist via .clarion/secrets-baseline.yaml. See ADR-013." + "remediation": "File flagged by pre-ingest secret scan. Fix the secret or whitelist via .loomweave/secrets-baseline.yaml. See ADR-013." } ``` This is parallel to the existing `summary_scope_deferred` envelope -(`crates/clarion-mcp/src/lib.rs:2341`) and the four `issues_unavailable` -envelopes in `clarion-mcp::filigree`. Add a `summary_briefing_blocked` +(`crates/loomweave-mcp/src/lib.rs:2341`) and the four `issues_unavailable` +envelopes in `loomweave-mcp::filigree`. Add a `summary_briefing_blocked` helper next to `summary_scope_deferred`. #### Hook point @@ -745,7 +745,7 @@ existing `summary_scope_deferred` check, since both are | Action | Path | |---|---| -| modify | `docs/clarion/1.0/detailed-design.md` (§5 rule catalogue) | +| modify | `docs/loomweave/1.0/detailed-design.md` (§5 rule catalogue) | #### Scope @@ -754,11 +754,11 @@ existing table shape in §5 (do not invent a new format). | Rule-ID | Severity | Category | Description (one sentence) | Remediation (one sentence) | ADR | |---|---|---|---|---|---| -| `CLA-SEC-SECRET-DETECTED` | error | security | Pre-ingest secret scanner detected a credential pattern in a file slated for LLM dispatch. | Remove the secret, rotate the credential, or whitelist via `.clarion/secrets-baseline.yaml` with a justification. | [ADR-013](../../clarion/adr/ADR-013-pre-ingest-secret-scanner.md) | -| `CLA-SEC-UNREDACTED-SECRETS-ALLOWED` | error | security | Operator invoked `--allow-unredacted-secrets`; file content reached the LLM provider with secrets intact. | Audit override usage via `filigree list --rule-id=CLA-SEC-UNREDACTED-SECRETS-ALLOWED --since 30d`. | [ADR-013](../../clarion/adr/ADR-013-pre-ingest-secret-scanner.md) | -| `CLA-INFRA-SECRET-BASELINE-NO-JUSTIFICATION` | error | infra | Baseline entry missing required `justification` field; entry not honoured. | Add a `justification` string explaining why the match is safe. | [ADR-013](../../clarion/adr/ADR-013-pre-ingest-secret-scanner.md) | -| `CLA-INFRA-SECRET-BASELINE-MATCH` | info | infra | Baseline entry suppressed a scanner detection (audit surface). | None — informational, retained for `NFR-SEC-04` audit. | [ADR-013](../../clarion/adr/ADR-013-pre-ingest-secret-scanner.md) | -| `CLA-INFRA-SECRET-OVERRIDE-UNCONFIRMED` | error | infra | `--allow-unredacted-secrets` supplied without confirmation; run aborted before start. | Supply `--confirm-allow-unredacted-secrets=yes-i-understand` in non-TTY contexts or run interactively. | [ADR-013](../../clarion/adr/ADR-013-pre-ingest-secret-scanner.md) | +| `LMWV-SEC-SECRET-DETECTED` | error | security | Pre-ingest secret scanner detected a credential pattern in a file slated for LLM dispatch. | Remove the secret, rotate the credential, or whitelist via `.loomweave/secrets-baseline.yaml` with a justification. | [ADR-013](../../loomweave/adr/ADR-013-pre-ingest-secret-scanner.md) | +| `LMWV-SEC-UNREDACTED-SECRETS-ALLOWED` | error | security | Operator invoked `--allow-unredacted-secrets`; file content reached the LLM provider with secrets intact. | Audit override usage via `filigree list --rule-id=LMWV-SEC-UNREDACTED-SECRETS-ALLOWED --since 30d`. | [ADR-013](../../loomweave/adr/ADR-013-pre-ingest-secret-scanner.md) | +| `LMWV-INFRA-SECRET-BASELINE-NO-JUSTIFICATION` | error | infra | Baseline entry missing required `justification` field; entry not honoured. | Add a `justification` string explaining why the match is safe. | [ADR-013](../../loomweave/adr/ADR-013-pre-ingest-secret-scanner.md) | +| `LMWV-INFRA-SECRET-BASELINE-MATCH` | info | infra | Baseline entry suppressed a scanner detection (audit surface). | None — informational, retained for `NFR-SEC-04` audit. | [ADR-013](../../loomweave/adr/ADR-013-pre-ingest-secret-scanner.md) | +| `LMWV-INFRA-SECRET-OVERRIDE-UNCONFIRMED` | error | infra | `--allow-unredacted-secrets` supplied without confirmation; run aborted before start. | Supply `--confirm-allow-unredacted-secrets=yes-i-understand` in non-TTY contexts or run interactively. | [ADR-013](../../loomweave/adr/ADR-013-pre-ingest-secret-scanner.md) | These IDs make the WP9-B Filigree-emission story (v0.2) able to round-trip without a separate spec pass. @@ -800,13 +800,13 @@ without reading ADR-013. Sections: 2. **What gets blocked** (file-level; structural extraction continues; summaries don't). 3. **How to whitelist a false positive** (edit - `.clarion/secrets-baseline.yaml`; example entry; commit it). + `.loomweave/secrets-baseline.yaml`; example entry; commit it). 4. **The override flag** (`--allow-unredacted-secrets` + `--confirm-allow-unredacted-secrets=yes-i-understand`; when to use it; what gets audited). 5. **Exit codes** (0 / 1 / 78). 6. **Finding the audit trail** ( - `select * from findings where rule_id like 'CLA-SEC-%'`; + `select * from findings where rule_id like 'LMWV-SEC-%'`; forward-pointer to Filigree integration in v0.2). 7. **Limitations** (pattern-based scanning has false negatives; novel secret shapes; air-gapped alternatives for truly high-risk repos — @@ -859,13 +859,13 @@ not plugin-emitted. | Layer | Tests | Location | |---|---|---| -| Unit | Scanner pattern fixtures (positive + negative per rule); entropy bounds; baseline parse / suppress / round-trip | `crates/clarion-scanner/tests/` | -| Unit | Override CLI exit codes; `runs.stats` JSON shape | `crates/clarion-cli/tests/secret_scan.rs` | -| Unit | MCP `summary` envelope on blocked entity | `crates/clarion-mcp/tests/storage_tools.rs` | -| Integration | `analyze` over fixture project with one secret-bearing file → entities + findings + properties_json shape | `crates/clarion-cli/tests/secret_scan.rs` | +| Unit | Scanner pattern fixtures (positive + negative per rule); entropy bounds; baseline parse / suppress / round-trip | `crates/loomweave-scanner/tests/` | +| Unit | Override CLI exit codes; `runs.stats` JSON shape | `crates/loomweave-cli/tests/secret_scan.rs` | +| Unit | MCP `summary` envelope on blocked entity | `crates/loomweave-mcp/tests/storage_tools.rs` | +| Integration | `analyze` over fixture project with one secret-bearing file → entities + findings + properties_json shape | `crates/loomweave-cli/tests/secret_scan.rs` | | Integration | Baseline suppression; baseline missing-justification | same | | Integration | Non-TTY override paths (3 cases) | same | -| E2E | `clarion install && clarion analyze` against a fixture project with one known secret; assert exit 0, entities present, briefing_blocked flagged, finding persisted; assert walking-skeleton fixture still green | `tests/e2e/wp5_secret_scan.sh` (new), parallels `tests/e2e/sprint_1_walking_skeleton.sh` | +| E2E | `loomweave install && loomweave analyze` against a fixture project with one known secret; assert exit 0, entities present, briefing_blocked flagged, finding persisted; assert walking-skeleton fixture still green | `tests/e2e/wp5_secret_scan.sh` (new), parallels `tests/e2e/sprint_1_walking_skeleton.sh` | | Manual | TTY interactive prompt path | WS-D smoke test step 8 | ### CI gates (ADR-023 floor, unchanged) @@ -896,11 +896,11 @@ A new E2E (`wp5_secret_scan.sh`) is added to the CI matrix's | R-1 | High-entropy detection on UUIDs and base64 checksums creates false-positive flood on real repos | Bound length thresholds per ADR-013 (≥20 chars base64, ≥40 chars hex); document baseline workflow prominently in Task 7; Workstream D smoke test on `requests` will surface real-world false-positive rate before publish | | R-2 | sha1 hashing of literal secrets means a baseline rebuilt from a different `detect-secrets` version may mismatch | Document the exact hash function (sha1 over the literal matched bytes, no normalisation); migration story for `detect-secrets` v2.x is a v0.2 problem | | R-3 | The override flag could become normalised in CI configurations ("we always pass it because…") | Audit-surface design: every override is a finding; v0.2 Filigree integration makes them visible; operator doc explicitly names this anti-pattern | -| R-4 | Plugin-host integration to stamp `briefing_blocked` adds a code path through a 3 126-LOC file flagged by arch-analysis §5.4 A-3 | Keep the host-side change small (≤ 30 LOC, a single map lookup); the orchestration lives in `clarion-cli/src/secret_scan.rs`, not in the host | -| R-5 | Baseline format compatibility with `detect-secrets` v1.x must be exact; an operator running `detect-secrets scan --baseline` and dropping the file in `.clarion/` must work | Round-trip test in Task 2; smoke-test the workflow with a real `detect-secrets`-generated baseline in WS-D | +| R-4 | Plugin-host integration to stamp `briefing_blocked` adds a code path through a 3 126-LOC file flagged by arch-analysis §5.4 A-3 | Keep the host-side change small (≤ 30 LOC, a single map lookup); the orchestration lives in `loomweave-cli/src/secret_scan.rs`, not in the host | +| R-5 | Baseline format compatibility with `detect-secrets` v1.x must be exact; an operator running `detect-secrets scan --baseline` and dropping the file in `.loomweave/` must work | Round-trip test in Task 2; smoke-test the workflow with a real `detect-secrets`-generated baseline in WS-D | | Q-1 | Should baseline entries carry an `expires_at` field so stale entries are surfaced? | Out of scope for v0.1; surface as a v0.2 enhancement if the override-monitoring loop also lands then | | Q-2 | Does `briefing_blocked: secret_present` propagate up to module / subsystem summaries when those are aggregated (deferred to v0.2 per ADR-030)? | Defer to v0.2; the leaf summary skip is sufficient for v0.1; document in Task 7 that "summaries of containing modules may still be generated and may infer secret content indirectly — fix the underlying file" | -| Q-3 | Should the scanner also redact secrets from log lines emitted by Clarion itself (the `runs//log.jsonl` ADR-013 line 16 cites)? | **Resolved at planning time**: `runs//log.jsonl` is referenced only in `clarion-cli/src/install.rs:74` (the gitignore template) and `tests/install.rs:40` (the test asserting it's ignored). No writer code exists in `crates/` that emits to that path today. WP5 has nothing to redact. If a per-run JSONL log lands later (likely WP6 batched pipeline or WP9-B), that work must include a redaction pass for any file content emitted from `briefing_blocked` files; cite this row when the log writer is authored. | +| Q-3 | Should the scanner also redact secrets from log lines emitted by Loomweave itself (the `runs//log.jsonl` ADR-013 line 16 cites)? | **Resolved at planning time**: `runs//log.jsonl` is referenced only in `loomweave-cli/src/install.rs:74` (the gitignore template) and `tests/install.rs:40` (the test asserting it's ignored). No writer code exists in `crates/` that emits to that path today. WP5 has nothing to redact. If a per-run JSONL log lands later (likely WP6 batched pipeline or WP9-B), that work must include a redaction pass for any file content emitted from `briefing_blocked` files; cite this row when the log writer is authored. | All three questions resolved at planning time. None blocks task kickoff. @@ -921,7 +921,7 @@ The workstream is signed-off when ALL of the following hold: verified during WS-D smoke test step 8. - [ ] All five rule-IDs appear in `detailed-design.md` §5. - [ ] No new clippy warnings; no `unsafe` blocks introduced. -- [ ] `cargo tree -p clarion-scanner` shows no `tokio` / `rusqlite` / +- [ ] `cargo tree -p loomweave-scanner` shows no `tokio` / `rusqlite` / `serde_norway` ancestors. When this gate closes, Workstream D's smoke-test step 8 (planted-`.env`) @@ -951,7 +951,7 @@ filigree create --type=task \ # Task 2 filigree create --type=task \ - --title="WP5 Task 2 — Baseline parser (.clarion/secrets-baseline.yaml)" \ + --title="WP5 Task 2 — Baseline parser (.loomweave/secrets-baseline.yaml)" \ --labels="release:v0.1,sprint:3,wp:5,adr:013,crate:scanner" \ --priority=1 # capture as $T2 @@ -1013,13 +1013,13 @@ off via the criteria in §9. ## 11. References -- [ADR-013 — Pre-Ingest Secret Scanner with LLM-Dispatch Block](../../clarion/adr/ADR-013-pre-ingest-secret-scanner.md) — canonical spec. -- [ADR-007 — Summary cache key](../../clarion/adr/ADR-007-summary-cache-key.md) — `briefing_blocked` interaction. -- [ADR-017 — Severity and dedup](../../clarion/adr/ADR-017-severity-and-dedup.md) — `CLA-SEC-*` namespace ownership. -- [ADR-021 — Plugin authority hybrid](../../clarion/adr/ADR-021-plugin-authority-hybrid.md) — path-jail upstream of scanner. -- [ADR-022 — Core/plugin ontology boundary](../../clarion/adr/ADR-022-core-plugin-ontology.md) — secret detection as a core-owned algorithm. -- [ADR-023 — Tooling baseline](../../clarion/adr/ADR-023-tooling-baseline.md) — CI floor every task in this workstream must clear. -- [Requirements — NFR-SEC-01, NFR-SEC-04, NFR-OPS-01, NFR-OPS-04](../../clarion/v0.1/requirements.md) — requirement floor. +- [ADR-013 — Pre-Ingest Secret Scanner with LLM-Dispatch Block](../../loomweave/adr/ADR-013-pre-ingest-secret-scanner.md) — canonical spec. +- [ADR-007 — Summary cache key](../../loomweave/adr/ADR-007-summary-cache-key.md) — `briefing_blocked` interaction. +- [ADR-017 — Severity and dedup](../../loomweave/adr/ADR-017-severity-and-dedup.md) — `LMWV-SEC-*` namespace ownership. +- [ADR-021 — Plugin authority hybrid](../../loomweave/adr/ADR-021-plugin-authority-hybrid.md) — path-jail upstream of scanner. +- [ADR-022 — Core/plugin ontology boundary](../../loomweave/adr/ADR-022-core-plugin-ontology.md) — secret detection as a core-owned algorithm. +- [ADR-023 — Tooling baseline](../../loomweave/adr/ADR-023-tooling-baseline.md) — CI floor every task in this workstream must clear. +- [Requirements — NFR-SEC-01, NFR-SEC-04, NFR-OPS-01, NFR-OPS-04](../../loomweave/v0.1/requirements.md) — requirement floor. - [Thread 1 — Pre-publish blockers (program of work)](./thread-1-pre-publish-blockers.md) — the umbrella program. - [v0.1-plan.md — WP5 scope](../v0.1-plan.md#wp5--pre-ingest-secret-scanner) — original work-package definition. - [Sprint 2 scope amendment — WP5 deferral rationale](../sprint-2/scope-amendment-2026-05.md) — "production deployment against unknown corpora gates on this returning." diff --git a/docs/implementation/v0.1-reviews/README.md b/docs/implementation/v0.1-reviews/README.md index ad7285e5..70c80b93 100644 --- a/docs/implementation/v0.1-reviews/README.md +++ b/docs/implementation/v0.1-reviews/README.md @@ -1,12 +1,12 @@ -# Clarion v0.1 Review Archive +# Loomweave v0.1 Review Archive -This folder holds retained historical reviews that shaped the Clarion v0.1 docset. +This folder holds retained historical reviews that shaped the Loomweave v0.1 docset. These documents are supporting context, not normative sources. The canonical design lives in [../README.md](../README.md). ## Pre-restructure reviews -These reviews evaluated the single-file design (`2026-04-17-clarion-v0.1-design.md`) that the current three-layer docset replaced. Retained as the empirical source for many ADRs and for the corrections folded into the layered design. +These reviews evaluated the single-file design (`2026-04-17-loomweave-v0.1-design.md`) that the current three-layer docset replaced. Retained as the empirical source for many ADRs and for the corrections folded into the layered design. - [pre-restructure/design-review.md](./pre-restructure/design-review.md) — source of several early corrections and ADR candidates. - [pre-restructure/integration-recon.md](./pre-restructure/integration-recon.md) — reality check against Filigree and Wardline code as it existed during the v0.1 design pass. @@ -16,7 +16,7 @@ These reviews evaluated the single-file design (`2026-04-17-clarion-v0.1-design. - [panel-2026-04-17/00-executive-synthesis.md](./panel-2026-04-17/00-executive-synthesis.md) — final synthesis and priority call from the panel pass. - [panel-2026-04-17/04-self-sufficiency.md](./panel-2026-04-17/04-self-sufficiency.md) — review of whether each layer stands on its own without hidden dependencies. - [panel-2026-04-17/09-threat-model.md](./panel-2026-04-17/09-threat-model.md) — security review and control recommendations referenced by the ADR set. -- [panel-2026-04-17/11-doctrine-panel-synthesis.md](./panel-2026-04-17/11-doctrine-panel-synthesis.md) — doctrine-specific synthesis used by the Loom and integration ADRs. +- [panel-2026-04-17/11-doctrine-panel-synthesis.md](./panel-2026-04-17/11-doctrine-panel-synthesis.md) — doctrine-specific synthesis used by the Weft and integration ADRs. ## Cleanup note diff --git a/docs/implementation/v0.1-reviews/panel-2026-04-17/00-executive-synthesis.md b/docs/implementation/v0.1-reviews/panel-2026-04-17/00-executive-synthesis.md index fc57c31e..b92de439 100644 --- a/docs/implementation/v0.1-reviews/panel-2026-04-17/00-executive-synthesis.md +++ b/docs/implementation/v0.1-reviews/panel-2026-04-17/00-executive-synthesis.md @@ -1,16 +1,16 @@ -# Clarion v0.1 — Executive Review Synthesis +# Loomweave v0.1 — Executive Review Synthesis **Panel date:** 2026-04-17 **Synthesiser:** Plan Review Synthesiser Agent **Inputs:** 10 specialist reviews (structure, doc-critique, editorial, self-sufficiency, link-integrity, architecture-critique, ADR review, debt catalog, STRIDE threat model, doctrine reader-panel) -**Target artefact:** `/home/john/clarion/docs/` — 5,670 lines across 17 markdown files, docs-only pre-implementation -**Decision this review gates:** design-complete → begin Clarion v0.1 implementation +**Target artefact:** `/home/john/loomweave/docs/` — 5,670 lines across 17 markdown files, docs-only pre-implementation +**Decision this review gates:** design-complete → begin Loomweave v0.1 implementation --- ## 1. Headline verdict -**Conditional-go with rework gates.** The docset is unusually disciplined — stable IDs, explicit non-goals, layered derivation, honest ADRs — and the three-layer design is fundamentally sound enough to build from. But Clarion v0.1 as currently scoped is roughly six products in one release, resting on eight unwritten P0 ADRs, an unbenchmarked $15 cost model, a cross-repo Filigree dependency that does not exist yet, and a default-off HTTP auth posture that a security tool cannot ship. Implementation can start on the catalog + Python plugin + SQLite writer core; everything touching Filigree `registry_backend`, the Wardline→Filigree bridge, and the summary-cache cost model should be gated behind the rework below. +**Conditional-go with rework gates.** The docset is unusually disciplined — stable IDs, explicit non-goals, layered derivation, honest ADRs — and the three-layer design is fundamentally sound enough to build from. But Loomweave v0.1 as currently scoped is roughly six products in one release, resting on eight unwritten P0 ADRs, an unbenchmarked $15 cost model, a cross-repo Filigree dependency that does not exist yet, and a default-off HTTP auth posture that a security tool cannot ship. Implementation can start on the catalog + Python plugin + SQLite writer core; everything touching Filigree `registry_backend`, the Wardline→Filigree bridge, and the summary-cache cost model should be gated behind the rework below. --- @@ -21,14 +21,14 @@ Ten items, ranked by convergent severity × reversibility. "Convergence" counts | # | Issue | Convergence | Cost | Blocking? | |---|---|---|---|---| | **1** | **Author the 8 P0 ADRs** (006 clustering, 007 cache key, 011 writer-actor, 013 secret scanner, 014 registry backend, 015 Wardline emission, 016 observation transport, 017 severity/dedup, 018 identity reconciliation) as standalone files. Plus one missing ADR: core/plugin ontology boundary. | 4 reviewers: `07-adr-review.md`, `08-debt.md` C1, `04-self-sufficiency.md` Issue 3, `02-doc-critique.md` (dual-table hazard) | 3–5 days | **YES** — first implementer will litigate each in PR threads otherwise | -| **2** | **Resolve the Filigree `registry_backend` dependency.** Either author the Filigree-side `RegistryProtocol` design jointly with Filigree maintainers, or drop the "Clarion owns structural truth" framing and document shadow-registry mode as the v0.1 shape. | 3 reviewers: `06-architecture-critique.md` §1+§6, `08-debt.md` C2, `11-doctrine-panel-synthesis.md` 2.7 + Dev persona | 1–2 days to rewrite, 1–2 weeks if Filigree patch is in scope | **YES** — the headline product claim rests on it | -| **3** | **Flip HTTP API default from `auth: none` to authenticated-or-not-listening** (Unix domain socket mode 0600, or auto-minted token on first `clarion serve`). A security tool cannot ship with loopback-is-trusted as the default posture. | 1 reviewer primary (`09-threat-model.md` T-02, risk score 9), but reinforced by `06-architecture-critique.md` §7 observability gap | 2–4 hours design change, small implementation | **YES** — reputational blocker on first public demo | -| **4** | **Write ADR on the Wardline→Filigree bridge (ADR-015, ADR-017)** and decide whether Clarion is the permanent bridge or a temporary one with a retirement condition on Wardline's native emitter. | 3 reviewers: `06-architecture-critique.md` §5, `08-debt.md` C5, `11-doctrine-panel-synthesis.md` 2.1 (the "triangle") | 1 day | **YES** — per `loom.md` §4 pairwise rule this is a doctrine violation without a written retirement condition | +| **2** | **Resolve the Filigree `registry_backend` dependency.** Either author the Filigree-side `RegistryProtocol` design jointly with Filigree maintainers, or drop the "Loomweave owns structural truth" framing and document shadow-registry mode as the v0.1 shape. | 3 reviewers: `06-architecture-critique.md` §1+§6, `08-debt.md` C2, `11-doctrine-panel-synthesis.md` 2.7 + Dev persona | 1–2 days to rewrite, 1–2 weeks if Filigree patch is in scope | **YES** — the headline product claim rests on it | +| **3** | **Flip HTTP API default from `auth: none` to authenticated-or-not-listening** (Unix domain socket mode 0600, or auto-minted token on first `loomweave serve`). A security tool cannot ship with loopback-is-trusted as the default posture. | 1 reviewer primary (`09-threat-model.md` T-02, risk score 9), but reinforced by `06-architecture-critique.md` §7 observability gap | 2–4 hours design change, small implementation | **YES** — reputational blocker on first public demo | +| **4** | **Write ADR on the Wardline→Filigree bridge (ADR-015, ADR-017)** and decide whether Loomweave is the permanent bridge or a temporary one with a retirement condition on Wardline's native emitter. | 3 reviewers: `06-architecture-critique.md` §5, `08-debt.md` C5, `11-doctrine-panel-synthesis.md` 2.1 (the "triangle") | 1 day | **YES** — per `weft.md` §4 pairwise rule this is a doctrine violation without a written retirement condition | | **5** | **Benchmark or downgrade the cost model.** `$15 ± 50%` (NFR-COST-01), ≥95% cache hit rate (NFR-COST-02), ≤60 min wall-clock (NFR-PERF-01) are currently speculation treated as acceptance criteria. Run a throw-away spike on `elspeth-slice`, or mark each as "hypothesis, to be validated before GA." | 2 reviewers: `06-architecture-critique.md` §6, `08-debt.md` C4 + M9 | 2–3 days (spike) or 30 min (downgrade language) | Should-fix; blocks only if these stay as contractual acceptance gates | | **6** | **Core-enforced plugin boundary**: path jail (refuse plugin-returned paths outside project root), per-run entity-count cap, per-message Content-Length ceiling, per-plugin RSS limit via `ulimit` on spawn. Turn "trusted plugin" from doctrine into enforced minimum. | 2 reviewers: `09-threat-model.md` T-01/T-08/T-11/T-12 (compound critical), `06-architecture-critique.md` §3 | 1–2 days design, 2–3 days implementation | **YES** — mitigates three critical risk-9/risk-6 threats | -| **7** | **Broaden `loom.md` §5 failure test or explicitly scope it.** Current test ("does removing a sibling change the *meaning* of the remaining product's own data") catches semantic drift but not initialization coupling (the `wardline.core.registry.REGISTRY` direct Python import) or pipeline-stage dependencies (the SARIF triangle). Add ~200 words in §5 naming these v0.1 asterisks with retirement conditions. | 4 reviewers in effect: `11-doctrine-panel-synthesis.md` 2.2 + 2.3 (4 of 5 personas), `06-architecture-critique.md` §8, `04-self-sufficiency.md` §4 friction point | 2–3 hours | **YES** for doctrine credibility; the enrichment axiom is the suite's load-bearing claim | +| **7** | **Broaden `weft.md` §5 failure test or explicitly scope it.** Current test ("does removing a sibling change the *meaning* of the remaining product's own data") catches semantic drift but not initialization coupling (the `wardline.core.registry.REGISTRY` direct Python import) or pipeline-stage dependencies (the SARIF triangle). Add ~200 words in §5 naming these v0.1 asterisks with retirement conditions. | 4 reviewers in effect: `11-doctrine-panel-synthesis.md` 2.2 + 2.3 (4 of 5 personas), `06-architecture-critique.md` §8, `04-self-sufficiency.md` §4 friction point | 2–3 hours | **YES** for doctrine credibility; the enrichment axiom is the suite's load-bearing claim | | **8** | **Collapse the dual ADR tables** (system-design §12 + detailed-design §11). The docset itself admits they must be kept in sync manually — this is the warning, not the remedy. Pick one canonical home and cross-reference. While there, fix the "ADR-005 through ADR-013" phrase (`05-link-integrity.md` MEDIUM defect): both documents undercount by 7 entries. | 3 reviewers: `02-doc-critique.md` must-fix #1, `05-link-integrity.md`, `04-self-sufficiency.md` Issue 3 | 1 hour | Should-fix; this is the highest-probability silent-drift hazard in the docset | -| **9** | **Complete the Phase-7 rule-ID catalogue** in detailed-design §5. Preamble promises exhaustive coverage; actual §5 lists 3 rules while 20+ `CLA-*` IDs are scattered across all three documents. Add §5.1 with rule ID, phase, severity, kind, description. Fix the `# ... etc.` stub at detailed-design.md:128 while there. Reconcile `CLA-INFRA-PARSE-ERROR` vs `CLA-PY-PARSE-ERROR` namespace inconsistency (`04-self-sufficiency.md` Issue 7). | 2 reviewers: `02-doc-critique.md` detailed-design must-fix #1+#3, `04-self-sufficiency.md` Issue 7 | 3–4 hours | Should-fix; implementer of the finding emission layer currently has to grep three documents | +| **9** | **Complete the Phase-7 rule-ID catalogue** in detailed-design §5. Preamble promises exhaustive coverage; actual §5 lists 3 rules while 20+ `LMWV-*` IDs are scattered across all three documents. Add §5.1 with rule ID, phase, severity, kind, description. Fix the `# ... etc.` stub at detailed-design.md:128 while there. Reconcile `LMWV-INFRA-PARSE-ERROR` vs `LMWV-PY-PARSE-ERROR` namespace inconsistency (`04-self-sufficiency.md` Issue 7). | 2 reviewers: `02-doc-critique.md` detailed-design must-fix #1+#3, `04-self-sufficiency.md` Issue 7 | 3–4 hours | Should-fix; implementer of the finding emission layer currently has to grep three documents | | **10** | **Inline a 15-term mini-glossary in both requirements.md and system-design.md** (finding, briefing, entity, edge, scan_run_id, guidance_fingerprint, knowledge_basis, tier, manifest, run_id, scope lens, writer-actor, pre-ingest redaction, capability probe, EntityId). Resolves the "glossary is a pointer-only stub" defect in both upper layers. | 3 reviewers: `02-doc-critique.md` system-design must-fix #2, `04-self-sufficiency.md` Issue 2, `01-structure.md` §7 | 1 hour | Nice-to-have; reduces cross-layer bouncing for every subsequent reader | Items 1–4 and 6–7 are the blocking set. Items 5, 8, 9, 10 are should-fix but not blocking. Total estimated rework: **6–9 days of focused writing** if cost-model validation runs in parallel; **2–3 weeks** if it blocks. @@ -39,9 +39,9 @@ Items 1–4 and 6–7 are the blocking set. Items 5, 8, 9, 10 are should-fix but Five themes surface in three or more reviews from independent angles. Each is more important than any single finding it comprises. -### 3.1 The Wardline → Clarion → Filigree triangle +### 3.1 The Wardline → Loomweave → Filigree triangle -`06-architecture-critique.md` §1+§5, `09-threat-model.md` TB-5+TB-6, and `11-doctrine-panel-synthesis.md` findings 2.1+2.2 (four of five reader personas) converge on the same shape: in v0.1, Wardline findings reach Filigree *only* through Clarion's SARIF translator, and Clarion's Python plugin *directly imports* `wardline.core.registry.REGISTRY` at startup. The first is a pipeline dependency that makes the Wardline+Filigree pair no longer pairwise-composable. The second is categorically tighter than the HTTP/file couplings it sits next to in the briefing's data-flow table. The architecture critic reads it as over-scope; the threat modeller reads it as a supply-chain blast-radius multiplier; the reader panel reads it as a doctrine violation that the §5 failure test is worded too narrowly to catch. All three are correct and describe the same structural fact. +`06-architecture-critique.md` §1+§5, `09-threat-model.md` TB-5+TB-6, and `11-doctrine-panel-synthesis.md` findings 2.1+2.2 (four of five reader personas) converge on the same shape: in v0.1, Wardline findings reach Filigree *only* through Loomweave's SARIF translator, and Loomweave's Python plugin *directly imports* `wardline.core.registry.REGISTRY` at startup. The first is a pipeline dependency that makes the Wardline+Filigree pair no longer pairwise-composable. The second is categorically tighter than the HTTP/file couplings it sits next to in the briefing's data-flow table. The architecture critic reads it as over-scope; the threat modeller reads it as a supply-chain blast-radius multiplier; the reader panel reads it as a doctrine violation that the §5 failure test is worded too narrowly to catch. All three are correct and describe the same structural fact. ### 3.2 Unauthored P0 ADRs = pre-implementation debt @@ -49,11 +49,11 @@ Five themes surface in three or more reviews from independent angles. Each is mo ### 3.3 Scope over-ambition at the v0.1 boundary -`06-architecture-critique.md` §1 (explicit: "Clarion v0.1 is trying to ship roughly six products in one release"), `08-debt.md` pattern observation ("debt estimates assume stated v0.1 scope; if scope contracts, several major items drop to minor"), and the recurring acknowledgement across `07-adr-review.md` and `09-threat-model.md` that v0.1 is carrying infrastructure (registry-backend displacement, SARIF bridge, MCP server, HTTP auth, Wardline-derived guidance) that any of its siblings might reasonably own. The architecture critic's realistic-minimum ("catalog + Python plugin + SQLite + local finding writer") is the same scope envelope the debt cataloguer arrives at by cost-accounting and the threat modeller arrives at by risk-surface minimisation. +`06-architecture-critique.md` §1 (explicit: "Loomweave v0.1 is trying to ship roughly six products in one release"), `08-debt.md` pattern observation ("debt estimates assume stated v0.1 scope; if scope contracts, several major items drop to minor"), and the recurring acknowledgement across `07-adr-review.md` and `09-threat-model.md` that v0.1 is carrying infrastructure (registry-backend displacement, SARIF bridge, MCP server, HTTP auth, Wardline-derived guidance) that any of its siblings might reasonably own. The architecture critic's realistic-minimum ("catalog + Python plugin + SQLite + local finding writer") is the same scope envelope the debt cataloguer arrives at by cost-accounting and the threat modeller arrives at by risk-surface minimisation. ### 3.4 Glossary / vocabulary drift across layers -`04-self-sufficiency.md` Issues 4+6+7, `02-doc-critique.md` system-design issue "Glossary stub", and `03-editorial.md` §3 each flag a different face of the same problem: the docset has no standalone glossary; briefing token bounds differ between layers (≤100/400/1500/3600 in requirements vs ~60/300/900/1800 in system-design and detailed-design); `knowledge_basis` casing drifts between snake_case on the wire and CamelCase in Rust types; `CLA-INFRA-PARSE-ERROR` and `CLA-PY-PARSE-ERROR` are used interchangeably in violation of the namespace contract in REQ-FINDING-02. Each drift is individually minor. As a class, they mean a plugin author reading only requirements.md implements the wrong shape. +`04-self-sufficiency.md` Issues 4+6+7, `02-doc-critique.md` system-design issue "Glossary stub", and `03-editorial.md` §3 each flag a different face of the same problem: the docset has no standalone glossary; briefing token bounds differ between layers (≤100/400/1500/3600 in requirements vs ~60/300/900/1800 in system-design and detailed-design); `knowledge_basis` casing drifts between snake_case on the wire and CamelCase in Rust types; `LMWV-INFRA-PARSE-ERROR` and `LMWV-PY-PARSE-ERROR` are used interchangeably in violation of the namespace contract in REQ-FINDING-02. Each drift is individually minor. As a class, they mean a plugin author reading only requirements.md implements the wrong shape. ### 3.5 Cost-model fiction @@ -84,12 +84,12 @@ Every review surfaced something load-bearing that the docset does well. Enumerat - **Stable ID discipline is real.** Every requirement carries a stable ID, a rationale, a verification method, a `See:` pointer. The `Addresses:` / `See:` symmetry is the right mechanism; `04-self-sufficiency.md` §8 finding 1. - **Explicit non-goals with deferral IDs (NG-01 through NG-25).** Each non-goal is specific and traceable, not vague future-work. `04-self-sufficiency.md` §8 finding 4 names NG-14 (rename tracking), NG-17 (triage-feedback loop), NG-25 (annotation descriptor) as exemplary. - **Revision-history appendix (detailed-design Appendix D).** The Rev 2→3→4→5 change tables let any reader reconstruct why a decision has its current shape. `04-self-sufficiency.md` §8 finding 3 calls it "rare in design docs." -- **§5 enrichment / failure-test formulation in `loom.md`.** All five reader personas accepted the central thesis as sincere and precisely expressed; adversarial readers (Priya, Sam, Dev) each explicitly credited the author for naming the stealth-monolith failure mode rather than hand-waving past it. `11-doctrine-panel-synthesis.md` §11. +- **§5 enrichment / failure-test formulation in `weft.md`.** All five reader personas accepted the central thesis as sincere and precisely expressed; adversarial readers (Priya, Sam, Dev) each explicitly credited the author for naming the stealth-monolith failure mode rather than hand-waving past it. `11-doctrine-panel-synthesis.md` §11. - **Navigation spine post-restructure (commit dfb9d95).** All five READMEs load-bearing; four personas find the right document in <10 seconds. `01-structure.md` rates findability 4.5/5. - **Link graph integrity.** 67 relative links verified, zero broken. One real defect (the "ADR-005 through ADR-013" undercount). `05-link-integrity.md`. - **ADR internal consistency.** The four authored ADRs are coherent as a set; `Related Decisions` links are reciprocal; ADR-002 correctly inherits Rust and isolates the plugin boundary so an ADR-001 reversal wouldn't cascade. `07-adr-review.md` opening. - **ADR-004 (Filigree-native intake) is genuinely evidence-forced.** The only ADR in the set driven by external reality (integration-recon), not preference; alternatives are real, both honestly rejected. `07-adr-review.md`. -- **Loom axiom is cited, not decorative.** CON-LOOM-01 appears in integration requirements, references in system-design §1+§11, drives degraded modes (NFR-RELIABILITY-02). `04-self-sufficiency.md` §4. +- **Weft axiom is cited, not decorative.** CON-WEFT-01 appears in integration requirements, references in system-design §1+§11, drives degraded modes (NFR-RELIABILITY-02). `04-self-sufficiency.md` §4. - **Honest register discipline across layers.** Doctrine → orientation → requirements → design → implementation-reference; each document declares its audience and stays in lane. `03-editorial.md` overall verdict. These are not consolation prizes. Several are rare enough that losing them during the rework would be a regression. @@ -103,8 +103,8 @@ The panel cannot decide these for you. They are commitments, not reviews. **Q1. Is v0.1 the catalog + Python plugin + SQLite + local finding writer, or is it the full triangle-integrating fabric the briefing describes?** Every critical debt item (`08-debt.md` C1–C5), the architecture critic's scope verdict, and the doctrine panel's triangle concern reduce to this choice. Picking the smaller scope makes items 4, 5, and much of 1 non-blocking. Picking the larger scope means several acceptance criteria currently stand on speculation. You cannot defer this: every downstream ADR depends on it. -**Q2. Is the Filigree `registry_backend` flag a Clarion v0.1 commitment or a v0.2 aspiration?** -This is the load-bearing pairwise-composability question (`loom.md` §4) and the integration-recon gap that kept recurring across `06`, `08`, and the doctrine panel. If v0.1, you need Filigree-maintainer buy-in and a cross-repo PR plan; if v0.2, the briefing's "Clarion owns structural truth" language needs to be rewritten as "Clarion shadows the file mapping until the Filigree flag lands," and the doctrine documents need to name the deferral honestly. +**Q2. Is the Filigree `registry_backend` flag a Loomweave v0.1 commitment or a v0.2 aspiration?** +This is the load-bearing pairwise-composability question (`weft.md` §4) and the integration-recon gap that kept recurring across `06`, `08`, and the doctrine panel. If v0.1, you need Filigree-maintainer buy-in and a cross-repo PR plan; if v0.2, the briefing's "Loomweave owns structural truth" language needs to be rewritten as "Loomweave shadows the file mapping until the Filigree flag lands," and the doctrine documents need to name the deferral honestly. **Q3. What is the authority model for plugins?** There is no ADR for the core/plugin ontology boundary (`07-adr-review.md`'s highest-priority missing ADR) and no ADR for plugin failure/degraded-mode semantics. There is also no signed-manifest or hash-pin story — `09-threat-model.md` ranks this the compound-critical risk — and no core-enforced path jail or resource cap. Answer: is the plugin a trusted extension that declares its own capabilities, or an untrusted input that the core validates? The v0.1 design currently assumes the former while shipping to an audience that will assume the latter. diff --git a/docs/implementation/v0.1-reviews/panel-2026-04-17/04-self-sufficiency.md b/docs/implementation/v0.1-reviews/panel-2026-04-17/04-self-sufficiency.md index 0be475fd..8d263bea 100644 --- a/docs/implementation/v0.1-reviews/panel-2026-04-17/04-self-sufficiency.md +++ b/docs/implementation/v0.1-reviews/panel-2026-04-17/04-self-sufficiency.md @@ -1,4 +1,4 @@ -# Self-Sufficiency & Derivation Review — Clarion v0.1 Layered Design Set +# Self-Sufficiency & Derivation Review — Loomweave v0.1 Layered Design Set Reviewer: muna-wiki-management:self-sufficiency-reviewer Date: 2026-04-17 @@ -21,7 +21,7 @@ The following requirements are addressed in prose but **not listed in any `Addre - **NFR-OBSERV-01 / -02 / -03** (structured logs, stats.json, Prometheus metrics) — Referenced as "See: §5 (Policy Engine, Observability)" and "§9 (HTTP Read API)"; neither section lists them. §5 has a brief "Observability" paragraph that names stats.json but doesn't cover log rotation or the metrics endpoint catalog. - **NFR-COMPAT-01 / -02 / -03** (Filigree schema pin, Wardline REGISTRY pin, Anthropic SDK pin) — §9 mentions schema pin in one paragraph; REGISTRY pin is in §2; SDK pin has no system-design home at all. None of the three are in any `Addresses:` header. - **CON-RUST-01** — No `Addresses:` header claims it. §12 ADR-001 and §2 core/plugin split cover it, but the constraint itself is not mechanically traceable. -- **NG-01, NG-02, NG-04, NG-05, NG-06, NG-07** — Named in §1 prose ("What Clarion is NOT") but only NG-03 appears in an `Addresses:` header. +- **NG-01, NG-02, NG-04, NG-05, NG-06, NG-07** — Named in §1 prose ("What Loomweave is NOT") but only NG-03 appears in an `Addresses:` header. **Verdict:** Every requirement's *content* is addressed somewhere; the `Addresses:` headers are the load-bearing trace apparatus per the preamble and they under-list. This is a mechanical gap, not a design gap, but it matters because requirements.md says IDs are "load-bearing" and presumably a reverse-traceability query would miss these. @@ -36,7 +36,7 @@ Minor free-floating items: - §4 "Why not a single giant transaction" — a design rationale without a requirement; acceptable as explanatory. - §4 Writer-actor vs. shadow-DB (ADR-011) — ADR only; no specific REQ. Acceptable since it's a decision, not a capability. - §5 Prompt caching strategy (four segments) — traces to CON-ANTHROPIC-01; fine. -- §9 `metadata` nesting convention (`metadata.clarion.*`, `metadata.wardline_properties.*`) — traces loosely via REQ-FINDING-03 and CON-FILIGREE-01 but the specific nesting rule isn't in any requirement. +- §9 `metadata` nesting convention (`metadata.loomweave.*`, `metadata.wardline_properties.*`) — traces loosely via REQ-FINDING-03 and CON-FILIGREE-01 but the specific nesting rule isn't in any requirement. - §10 Threat-model table rows — "Personal API key charged when committing team DB" and "DB tampering" have no requirement backing. Defensible as threat-modeling practice, but the reader cannot verify they're in scope by ID lookup. No major free-floating mechanisms. @@ -47,23 +47,23 @@ No major free-floating mechanisms. Items in detailed-design that have no system-design anchor: -- §3 Commit-ref and dirty-tree handling (`-dirty` suffix, `CLA-INFRA-DIRTY-TREE-RUN`, `--require-clean`) — Not mentioned in system-design §4 or §6. REQ-CATALOG-07 (`first_seen_commit` / `last_seen_commit`) implies it peripherally but doesn't require the policy. +- §3 Commit-ref and dirty-tree handling (`-dirty` suffix, `LMWV-INFRA-DIRTY-TREE-RUN`, `--require-clean`) — Not mentioned in system-design §4 or §6. REQ-CATALOG-07 (`first_seen_commit` / `last_seen_commit`) implies it peripherally but doesn't require the policy. - §3 Generated SQL columns + views (`git_churn_count`, `priority`, `guidance_sheets` view) — An implementation choice with no system-design analogue. -- §3 `clarion.yaml:storage.commit_db: false` opt-out and `clarion db sync push/pull` — No system-design mechanism; NFR-OPS-03 says `.clarion/` is committable by default but does not describe the opt-out. -- §7 "How Wardline picks up the token in CI" (`CLARION_TOKEN` env, `clarion check-auth --from wardline`) — Operationally important; no system-design mechanism for it. §9 "Token auth" mentions the env var in passing but not `clarion check-auth`. -- §1 Plugin decorator-detection table rows (class-decoration, metaclass, `__init_subclass__`, dynamic dispatch refusals) — The specific `CLA-PY-ANNOTATION-AMBIGUOUS` rule ID is not named in system-design; REQ-PLUGIN-06 is generic about decorator detection. +- §3 `loomweave.yaml:storage.commit_db: false` opt-out and `loomweave db sync push/pull` — No system-design mechanism; NFR-OPS-03 says `.loomweave/` is committable by default but does not describe the opt-out. +- §7 "How Wardline picks up the token in CI" (`LOOMWEAVE_TOKEN` env, `loomweave check-auth --from wardline`) — Operationally important; no system-design mechanism for it. §9 "Token auth" mentions the env var in passing but not `loomweave check-auth`. +- §1 Plugin decorator-detection table rows (class-decoration, metaclass, `__init_subclass__`, dynamic dispatch refusals) — The specific `LMWV-PY-ANNOTATION-AMBIGUOUS` rule ID is not named in system-design; REQ-PLUGIN-06 is generic about decorator detection. - §9.1 "Three auto-create paths in Filigree" — named here with specific file references; system-design §9 / §11 mention them as "three auto-create paths" without enumeration. - §10 Acceptance Ecosystem item 7 (`add_file_association` round-trip) — No system-design mechanism; a requirement might be missing here. These are small and none are load-bearing, but they represent detail that a reader of system-design alone cannot anticipate. -## 4. Doctrine ↔ requirements (Loom axiom honoured?) +## 4. Doctrine ↔ requirements (Weft axiom honoured?) **Verdict: honoured cleanly.** -- **Solo-useful** — NFR-RELIABILITY-02 (`--no-filigree`, `--no-wardline`), CON-LOOM-01 explicit, NG-06 (no hosted service). Satisfied. +- **Solo-useful** — NFR-RELIABILITY-02 (`--no-filigree`, `--no-wardline`), CON-WEFT-01 explicit, NG-06 (no hosted service). Satisfied. - **Pairwise-composable** — REQ-INTEG-FILIGREE-01..05 and REQ-INTEG-WARDLINE-01..06 are each independent one-to-one contracts. No "only works with all three" requirement exists. -- **Enrich-only** — CON-LOOM-01 names it explicitly; requirements never demand a sibling for Clarion's *own* data coherence. Wardline absence degrades to `confidence_basis: clarion_augmentation` (sem-preserving); Filigree absence degrades to local `findings.jsonl` (sem-preserving). +- **Enrich-only** — CON-WEFT-01 names it explicitly; requirements never demand a sibling for Loomweave's *own* data coherence. Wardline absence degrades to `confidence_basis: loomweave_augmentation` (sem-preserving); Filigree absence degrades to local `findings.jsonl` (sem-preserving). **One minor friction point:** REQ-INTEG-FILIGREE-03 (registry-backend consumption) and CON-FILIGREE-02 depend on Filigree shipping a flag. The shadow-registry fallback is documented and preserves the axiom. No violation, but it's the closest-to-load-bearing of any requirement and worth naming. @@ -93,13 +93,13 @@ No doctrinal violations. The gloss is "~N is typical; ≤N is ceiling" and system-design §3 *does* call this out. But the detailed-design table is unqualified, and a reader bouncing between them will see two distinct sets of numbers without immediate clarity on which is the contract. -2. **SARIF translator framing** — requirements.md REQ-FINDING-04 calls it a "general-purpose SARIF → Filigree translator" (permanent). System-design §9 calls it "general-purpose" and notes v0.2 adds a native Wardline POST. Detailed-design §7 calls the translator permanent but also calls the v0.1 Wardline path a "workaround" and v0.1 decision "Option A". Slight inconsistency about whether "Clarion-side ownership of Wardline-specific SARIF mapping" is a workaround or the v0.1 design. +2. **SARIF translator framing** — requirements.md REQ-FINDING-04 calls it a "general-purpose SARIF → Filigree translator" (permanent). System-design §9 calls it "general-purpose" and notes v0.2 adds a native Wardline POST. Detailed-design §7 calls the translator permanent but also calls the v0.1 Wardline path a "workaround" and v0.1 decision "Option A". Slight inconsistency about whether "Loomweave-side ownership of Wardline-specific SARIF mapping" is a workaround or the v0.1 design. -3. **"scan_source" values** — system-design §9 lists "`clarion`, `wardline`, `cov`, `sec`" as suite-wide reserved. Detailed-design §7 lists same plus says "no registry" / "free-form". Requirements.md REQ-INTEG-FILIGREE-04 uses `scan_source: "clarion"`. Consistent, but the "reserved namespace" claim has no canonical source. +3. **"scan_source" values** — system-design §9 lists "`loomweave`, `wardline`, `cov`, `sec`" as suite-wide reserved. Detailed-design §7 lists same plus says "no registry" / "free-form". Requirements.md REQ-INTEG-FILIGREE-04 uses `scan_source: "loomweave"`. Consistent, but the "reserved namespace" claim has no canonical source. 4. **Briefing `knowledge_basis` values** — system-design §3 mentions `StaticOnly / RuntimeInformed / HumanVerified` (CamelCase enum). Requirements.md REQ-BRIEFING-04 uses `static_only | runtime_informed | human_verified` (snake_case). Detailed-design §2 uses CamelCase Rust enum but the wire shape is unspecified. Minor, but a plugin author reading only requirements.md would implement the wrong casing. -5. **Python plugin error codes drift** — requirements.md REQ-ANALYZE-06 lists `CLA-PY-PARSE-ERROR, CLA-INFRA-PLUGIN-CRASH, CLA-INFRA-LLM-ERROR, CLA-INFRA-BUDGET-WARNING`. System-design §6 table uses the same. Detailed-design §10 "Error surfaces" lists `CLA-INFRA-PARSE-ERROR` (INFRA, not PY) among others — namespace inconsistency with REQ-FINDING-02 which reserves `CLA-PY-*` for Python-plugin structural findings. +5. **Python plugin error codes drift** — requirements.md REQ-ANALYZE-06 lists `LMWV-PY-PARSE-ERROR, LMWV-INFRA-PLUGIN-CRASH, LMWV-INFRA-LLM-ERROR, LMWV-INFRA-BUDGET-WARNING`. System-design §6 table uses the same. Detailed-design §10 "Error surfaces" lists `LMWV-INFRA-PARSE-ERROR` (INFRA, not PY) among others — namespace inconsistency with REQ-FINDING-02 which reserves `LMWV-PY-*` for Python-plugin structural findings. 6. **NG-25 vs system-design §2 "v0.2 generalisation"** — detailed-design §1 Observe-vs-enforce says "v0.2 adds `wardline annotations descriptor --format yaml`". NG-25 in requirements.md says the same. System-design §9 lists it as a Wardline-side v0.2 prerequisite. Consistent. @@ -122,7 +122,7 @@ No doctrinal violations. - Fix: In detailed-design §2 table, append the hard ceilings in the same column ("typical ~60 / ceiling ≤100") so the contract is visible at point of use. **Issue 5: Free-floating implementation details in detailed-design** -- Location: detailed-design §3 (dirty-tree policy, `--require-clean`, `clarion db sync push/pull`, `storage.commit_db: false`), §7 (`clarion check-auth --from wardline`). +- Location: detailed-design §3 (dirty-tree policy, `--require-clean`, `loomweave db sync push/pull`, `storage.commit_db: false`), §7 (`loomweave check-auth --from wardline`). - Fix: Either add a one-line mention in the corresponding system-design section (§4 for dirty-tree and commit-db opt-out, §9 for CI auth check) or append a requirement. **Issue 6: `knowledge_basis` casing drift** @@ -130,17 +130,17 @@ No doctrinal violations. - Fix: Name the wire format once, canonically. Likely `static_only | runtime_informed | human_verified` on the wire and `KnowledgeBasis::StaticOnly` internally; call this out in system-design §3. **Issue 7: Parse-error rule-ID namespace inconsistency** -- Location: detailed-design §10 "Error surfaces" uses `CLA-INFRA-PARSE-ERROR`; requirements.md REQ-ANALYZE-06 uses `CLA-PY-PARSE-ERROR`. -- Fix: Pick one. REQ-FINDING-02 says `CLA-INFRA-*` is for pipeline failures and `CLA-PY-*` for Python-plugin structural findings — a parse error is Python-plugin emitted, so `CLA-PY-PARSE-ERROR` is correct and detailed-design §10 is the error. +- Location: detailed-design §10 "Error surfaces" uses `LMWV-INFRA-PARSE-ERROR`; requirements.md REQ-ANALYZE-06 uses `LMWV-PY-PARSE-ERROR`. +- Fix: Pick one. REQ-FINDING-02 says `LMWV-INFRA-*` is for pipeline failures and `LMWV-PY-*` for Python-plugin structural findings — a parse error is Python-plugin emitted, so `LMWV-PY-PARSE-ERROR` is correct and detailed-design §10 is the error. **Issue 8: SARIF translator framing** -- Location: detailed-design §9.2 frames Clarion-side SARIF translator ownership as "Option A (recommended for v0.1)" i.e. a workaround; system-design §9 and requirements.md REQ-FINDING-04 frame it as permanent. -- Fix: Reconcile. The permanent-feature framing is the one that should prevail (it is consistent with Loom federation — Clarion owns a suite-wide utility). +- Location: detailed-design §9.2 frames Loomweave-side SARIF translator ownership as "Option A (recommended for v0.1)" i.e. a workaround; system-design §9 and requirements.md REQ-FINDING-04 frame it as permanent. +- Fix: Reconcile. The permanent-feature framing is the one that should prevail (it is consistent with Weft federation — Loomweave owns a suite-wide utility). ## 8. Strengths 1. **Stable ID discipline is genuine.** Every requirement has a stable ID, a rationale, a verification method, and a forward pointer. The `Addresses:` / `See:` symmetry is the right mechanism; it just needs tightening. -2. **Loom doctrine is load-bearing throughout.** CON-LOOM-01 is cited in integration requirements, referenced in system-design §1 and §11, and the enrich-only posture drives degraded modes (NFR-RELIABILITY-02). The axiom is not decorative. +2. **Weft doctrine is load-bearing throughout.** CON-WEFT-01 is cited in integration requirements, referenced in system-design §1 and §11, and the enrich-only posture drives degraded modes (NFR-RELIABILITY-02). The axiom is not decorative. 3. **Revision-history appendix in detailed-design is exemplary.** The Rev 2→3→4→5 change tables let a reader reconstruct why any given decision has its current shape. This is rare in design docs. 4. **Explicit Non-Goals with renamed-deferral IDs.** NG-14 (rename tracking), NG-17 (triage-feedback loop), NG-25 (annotation descriptor) — each is a specific, traceable deferral rather than a vague "future work". @@ -152,9 +152,9 @@ No doctrinal violations. - requirements.md → ADR-001 file (for a locked author directive) - system-design.md → detailed-design for SQL schemas, crate picks, full YAML, rule-ID enumeration (by design of the layering) - system-design.md §12 → authored ADR files in ../adr/ (once written) -- requirements.md → loom.md §5 (reaching to doctrine, appropriate) -- system-design.md §11 → loom.md §3, §6 (doctrine citation) -- detailed-design.md → loom.md §3 (unification caveat) +- requirements.md → weft.md §5 (reaching to doctrine, appropriate) +- system-design.md §11 → weft.md §3, §6 (doctrine citation) +- detailed-design.md → weft.md §3 (unification caveat) - Detailed-design pointers to integration-recon.md and design-review.md (historical reviews) - system-design.md §12 → detailed-design §11 ADR table (parallel table; navigation) - Multiple sibling-ADR cross-refs (ADR-008 → ADR-014 supersession) @@ -167,7 +167,7 @@ No doctrinal violations. - system-design.md §12 ADR summaries → detailed-design §11 ADR backlog + authored ADR files. For "To author" P0 ADRs, *both* targets are inadequate (one is a summary, one is a backlog entry). - requirements.md CON-RUST-01 → ADR-001 and system-design §12 for rationale. The rationale ("primary author's directive") is in requirements.md, but the "consequences accepted" list is not — a reader verifying whether the consequences are acceptable must leave. - requirements.md NFR-SEC-02 → system-design §10 for the layered-defence mechanisms. Appropriate, but the NFR-SEC-02 rationale says "Layered defence is required because no single mechanism is sufficient" without naming the layers. A reader evaluating whether NFR-SEC-02 is adequately specified cannot, from requirements alone. -- requirements.md NG-07 → loom.md ("Shuttle's territory"). Mostly fine, but the federation implication (Clarion must not drift toward change execution even if operators ask for it) is not stated in requirements.md itself. +- requirements.md NG-07 → weft.md ("Shuttle's territory"). Mostly fine, but the federation implication (Loomweave must not drift toward change execution even if operators ask for it) is not stated in requirements.md itself. --- diff --git a/docs/implementation/v0.1-reviews/panel-2026-04-17/09-threat-model.md b/docs/implementation/v0.1-reviews/panel-2026-04-17/09-threat-model.md index f2a91004..d8ff3509 100644 --- a/docs/implementation/v0.1-reviews/panel-2026-04-17/09-threat-model.md +++ b/docs/implementation/v0.1-reviews/panel-2026-04-17/09-threat-model.md @@ -1,11 +1,11 @@ -# Clarion v0.1 — STRIDE Threat Model +# Loomweave v0.1 — STRIDE Threat Model **Reviewer role**: Threat analyst (STRIDE + attack trees) **Date**: 2026-04-17 -**Scope**: Docs-only Clarion v0.1. Pre-implementation. +**Scope**: Docs-only Loomweave v0.1. Pre-implementation. **Inputs**: `requirements.md`, `system-design.md`, `detailed-design.md`, ADR-002/003/004, `reviews/pre-restructure/integration-recon.md` (moved 2026-04-18; original location `reviews/integration-recon.md`). -This is a reflexive-hygiene exercise. Clarion is itself a security tool; a weak threat model in v0.1 would undercut its credibility. The design already contains a §10 "Security" section with a short threat table, five NFR-SEC requirements, and a named list of "defences NOT in v0.1." This review extends it: mapping every trust boundary, STRIDE'ing each, and calling out the unstated assumptions that carry real risk. +This is a reflexive-hygiene exercise. Loomweave is itself a security tool; a weak threat model in v0.1 would undercut its credibility. The design already contains a §10 "Security" section with a short threat table, five NFR-SEC requirements, and a named list of "defences NOT in v0.1." This review extends it: mapping every trust boundary, STRIDE'ing each, and calling out the unstated assumptions that carry real risk. **Design acknowledges** where §10 addresses the threat explicitly. **Design ignores** where the threat is not named. **Implicit assumption** where the design depends on a property it does not enforce. @@ -18,14 +18,14 @@ This is a reflexive-hygiene exercise. Clarion is itself a security tool; a weak │ Developer workstation / CI runner (user's full privilege) │ │ │ ┌──────────┴──────────┐ stdio+framed ┌────────────────────────┐ │ - │ Clarion core │ ◄───JSON-RPC 2.0─►│ Language plugin (subproc) │ + │ Loomweave core │ ◄───JSON-RPC 2.0─►│ Language plugin (subproc) │ │ (Rust, single bin) │ │ pipx venv, Python code │ └──────────┬──────────┘ └────────┬───────────────────┘ │ │ │ writer actor │ fs read ▼ ▼ ┌─────────────────────┐ ┌────────────────────────┐ - │ .clarion/clarion.db │ │ Source tree (untrusted) │ + │ .loomweave/loomweave.db │ │ Source tree (untrusted) │ │ SQLite WAL, git- │ │ docstrings, comments, │ │ committable │ │ .env, fixtures, third- │ └──────────┬──────────┘ │ party vendored code │ @@ -36,7 +36,7 @@ This is a reflexive-hygiene exercise. Clarion is itself a security tool; a weak │ Anthropic API │◄──prompt+source──┐ │ ingest │ (3rd-party LLM) │ │ │ └─────────────────────┘ ┌──────┴─────────┴──────────┐ - │ clarion serve (HTTP+MCP) │ + │ loomweave serve (HTTP+MCP) │ │ ───────────────────────── │ │ ◄── Wardline / siblings │ │ ◄── MCP agents (stdio) │ @@ -57,10 +57,10 @@ Boundaries identified (TB-1..TB-8): | TB-1 | Core ↔ Plugin subprocess | bidirectional RPC | None (same UID) — plugin is *isolated* but not *sandboxed* | | TB-2 | Plugin ↔ Source tree (filesystem) | plugin reads | Plugin reads *attacker-controlled bytes* (docstrings, fixtures) | | TB-3 | Core ↔ Anthropic API | egress HTTPS | Source + guidance leaves the host; API key at risk | -| TB-4 | `clarion serve` ↔ HTTP/MCP client (Wardline, agents) | ingress on loopback | Any local process on the host can connect | -| TB-5 | Clarion → Filigree (HTTP + MCP subprocess) | egress | Findings flow to a sibling with looser validation | -| TB-6 | Clarion ← Wardline state files (fs ingest) | ingress | Clarion trusts `wardline.*.json` contents as authoritative | -| TB-7 | Git-committed store (`.clarion/clarion.db`) ↔ teammate pull | async, via VCS | Teammate inherits whatever a commit author wrote | +| TB-4 | `loomweave serve` ↔ HTTP/MCP client (Wardline, agents) | ingress on loopback | Any local process on the host can connect | +| TB-5 | Loomweave → Filigree (HTTP + MCP subprocess) | egress | Findings flow to a sibling with looser validation | +| TB-6 | Loomweave ← Wardline state files (fs ingest) | ingress | Loomweave trusts `wardline.*.json` contents as authoritative | +| TB-7 | Git-committed store (`.loomweave/loomweave.db`) ↔ teammate pull | async, via VCS | Teammate inherits whatever a commit author wrote | | TB-8 | Plugin package install (pipx/PyPI) ↔ plugin runtime | supply-chain | Registry compromise → code execution at user privilege | --- @@ -73,10 +73,10 @@ Boundaries identified (TB-1..TB-8): |---|---|---| | S | Plugin impersonates a different `plugin_id` in its manifest; emissions are attributed to a namespace it doesn't own | **Ignored.** `plugin_id` is self-declared; no registry of trusted plugin_ids, no signature | | T | Malformed `Content-Length` framing to desync the core parser; oversized `Content-Length` to OOM the core buffer | **Partial.** ADR-002 cites framing correctness; design-level cap on Content-Length is not stated | -| R | Plugin crash or misbehaviour not auditable after the fact | **Addressed:** `CLA-INFRA-PLUGIN-CRASH`, circuit breaker, stats.json | +| R | Plugin crash or misbehaviour not auditable after the fact | **Addressed:** `LMWV-INFRA-PLUGIN-CRASH`, circuit breaker, stats.json | | I | Plugin reads source outside `analysis.include` or the project root | **Implicit assumption:** no path jail; plugin runs at user privilege, `file_list(include, exclude)` is advisory not enforced | | D | Runaway plugin floods the bounded mpsc (100 msgs), stalls writer actor; or publishes 10M bogus entities per file | **Partial.** Backpressure on mpsc; no per-run entity-count cap, no message-size cap | -| E | Malicious plugin spawns subprocesses, exfiltrates data, writes outside `.clarion/` | **Ignored.** §10 explicitly names "Plugin sandbox (seccomp/landlock) — NOT in v0.1." Plugin = arbitrary code execution at user UID | +| E | Malicious plugin spawns subprocesses, exfiltrates data, writes outside `.loomweave/` | **Ignored.** §10 explicitly names "Plugin sandbox (seccomp/landlock) — NOT in v0.1." Plugin = arbitrary code execution at user UID | ### TB-2: Plugin ↔ Source tree @@ -93,40 +93,40 @@ Boundaries identified (TB-1..TB-8): | | Threat | Design posture | |---|---|---| -| S | API key forgery (attacker uses stolen Anthropic key) | Outside Clarion's scope per key management | +| S | API key forgery (attacker uses stolen Anthropic key) | Outside Loomweave's scope per key management | | T | MITM on HTTPS; response tampering injects malicious briefing JSON | **Implicit:** relies on TLS cert validation in the HTTP client. Not named. | | R | Cannot prove which prompt produced which briefing (for a customer dispute or leak investigation) | **Addressed:** `runs//log.jsonl` stores request/response (default git-excluded per NFR-SEC-05) | | I | Source code leaks to the provider — including secrets, proprietary logic | **Partial:** `detect-secrets` pre-ingest scan (NFR-SEC-01) is pattern-matching; misses novel secret shapes, business-secret prose, vendored GPL-incompatible code. Design names this as defence-in-depth, not completeness | -| D | Provider rate-limit or outage halts analyse | **Addressed:** retries, `CLA-INFRA-LLM-ERROR`, partial manifests | -| E | Prompt-injection promotes the LLM to drive *Clarion's* control flow (return tool-call-looking content, break schema) | **Addressed:** schema validation + controlled vocabulary + guidance promotion gate (NFR-SEC-02) | +| D | Provider rate-limit or outage halts analyse | **Addressed:** retries, `LMWV-INFRA-LLM-ERROR`, partial manifests | +| E | Prompt-injection promotes the LLM to drive *Loomweave's* control flow (return tool-call-looking content, break schema) | **Addressed:** schema validation + controlled vocabulary + guidance promotion gate (NFR-SEC-02) | -### TB-4: `clarion serve` ↔ HTTP/MCP clients +### TB-4: `loomweave serve` ↔ HTTP/MCP clients | | Threat | Design posture | |---|---|---| | S | Any local process on shared dev host / devcontainer connects to loopback HTTP; no auth required by default | **Explicitly accepted.** Token auth opt-in (REQ-HTTP-03); default is `auth: none`. §10 names loopback-is-not-a-boundary. The *default* is the risk | | T | DNS rebinding attack against `127.0.0.1:port` from a browser tab | **Ignored.** No `Host:` header check, no Origin-pinning stated | | R | HTTP request log? | **Not named** — no access log specified | -| I | `GET /api/v1/entities/{id}/source` returns source bytes to any loopback caller | **Implicit assumption:** loopback = trusted. With `auth: none` any user/process on the host reads source through Clarion | +| I | `GET /api/v1/entities/{id}/source` returns source bytes to any loopback caller | **Implicit assumption:** loopback = trusted. With `auth: none` any user/process on the host reads source through Loomweave | | D | Unbounded search / neighbor query consumes RAM; concurrent sessions exhaust read pool (16 conns) | **Partial:** per-tool token budgets (REQ-MCP-04); no explicit concurrency cap per client | | E | MCP `emit_observation` / `propose_guidance` written without consent from a headless agent that sets `auto_emit: true` | **Addressed for interactive**, **ignored for headless:** the `auto_emit: true` client-declared capability has no server-side gate. A hostile MCP client simply sets the flag | -### TB-5: Clarion → Filigree +### TB-5: Loomweave → Filigree | | Threat | Design posture | |---|---|---| -| S | Clarion forges `scan_source: "wardline"` (or any string); Filigree has no enum (recon §2.1) | **Ignored.** Recon explicitly notes `scan_source` is free-form server-side; suite coordination is "social convention" | -| T | Clarion POSTs findings with attacker-influenced `message` / `suggestion` fields from adversarial source → Filigree displays as HTML in dashboard → XSS | **Ignored.** No sanitisation contract stated on either side. The dashboard UI's handling is Filigree's problem, but Clarion is the upstream that passes adversarial data through | -| R | Finding provenance forgeable: `metadata.clarion.entity_id` can name any ID; `run_id` is client-generated | **Ignored.** No signing, no server-minted run IDs | +| S | Loomweave forges `scan_source: "wardline"` (or any string); Filigree has no enum (recon §2.1) | **Ignored.** Recon explicitly notes `scan_source` is free-form server-side; suite coordination is "social convention" | +| T | Loomweave POSTs findings with attacker-influenced `message` / `suggestion` fields from adversarial source → Filigree displays as HTML in dashboard → XSS | **Ignored.** No sanitisation contract stated on either side. The dashboard UI's handling is Filigree's problem, but Loomweave is the upstream that passes adversarial data through | +| R | Finding provenance forgeable: `metadata.loomweave.entity_id` can name any ID; `run_id` is client-generated | **Ignored.** No signing, no server-minted run IDs | | I | Observation bodies contain source excerpts → Filigree's DB captures them → leaks via Filigree backup / API | **Implicit:** relies on operator knowing this | | D | POST storm from a looping analyse; Filigree has no per-scan-source rate limit | **Not analysed** | -| E | `metadata.clarion.*` crafted to match a Filigree reserved key in a future version → behaviour change | **Partial:** design says "namespacing convention, published in Filigree docs" but Filigree doesn't enforce it (recon §2.1 confirms) | +| E | `metadata.loomweave.*` crafted to match a Filigree reserved key in a future version → behaviour change | **Partial:** design says "namespacing convention, published in Filigree docs" but Filigree doesn't enforce it (recon §2.1 confirms) | -### TB-6: Clarion ← Wardline state files +### TB-6: Loomweave ← Wardline state files | | Threat | Design posture | |---|---|---| -| S | Attacker commits crafted `wardline.fingerprint.json` / `wardline.exceptions.json`; Clarion auto-generates `critical: true` guidance sheets from it (REQ-GUIDANCE-04) that then steer all future LLM output | **Implicit assumption:** Wardline files are trusted because they're in the repo. The attack vector is a PR that modifies both source and `wardline.yaml` to *legitimise* a backdoor via Clarion's derived guidance. Not called out | +| S | Attacker commits crafted `wardline.fingerprint.json` / `wardline.exceptions.json`; Loomweave auto-generates `critical: true` guidance sheets from it (REQ-GUIDANCE-04) that then steer all future LLM output | **Implicit assumption:** Wardline files are trusted because they're in the repo. The attack vector is a PR that modifies both source and `wardline.yaml` to *legitimise* a backdoor via Loomweave's derived guidance. Not called out | | T | JSON parse DoS (quadratic blowup, deeply nested structures) | **Not analysed** | | R | — | n/a | | I | — | n/a | @@ -138,7 +138,7 @@ Boundaries identified (TB-1..TB-8): | | Threat | Design posture | |---|---|---| | S | — | | -| T | Teammate A edits `.clarion/clarion.db` with sqlite3 CLI, commits poisoned briefings; teammate B's MCP session reads them as truth | **Partial:** §10 names it ("DB content-hash verification on load — NOT in v0.1"); `clarion db verify` CLI is v0.2; until then *any DB commit is trusted* | +| T | Teammate A edits `.loomweave/loomweave.db` with sqlite3 CLI, commits poisoned briefings; teammate B's MCP session reads them as truth | **Partial:** §10 names it ("DB content-hash verification on load — NOT in v0.1"); `loomweave db verify` CLI is v0.2; until then *any DB commit is trusted* | | R | No commit-author binding on briefings; can't tell which operator's API key produced which row | **Partial:** summary_cache row provenance exists in design but author identity isn't bound | | I | Committed DB contains summaries of source that was later redacted from history; history diverges | **Ignored** | | D | — | | @@ -148,8 +148,8 @@ Boundaries identified (TB-1..TB-8): | | Threat | Design posture | |---|---|---| -| S | Typosquat on PyPI: `clarion-plugin-pyhton` | **Ignored** | -| T | Compromised PyPI upload of `clarion-plugin-python` | **Ignored** — no hash pin in v0.1; NG-16 defers to v0.2 | +| S | Typosquat on PyPI: `loomweave-plugin-pyhton` | **Ignored** | +| T | Compromised PyPI upload of `loomweave-plugin-python` | **Ignored** — no hash pin in v0.1; NG-16 defers to v0.2 | | R | — | | | I | Plugin deps pull a malicious transitive dep that beacons out | **Ignored** | | D | — | | @@ -161,11 +161,11 @@ Rust-side: cargo deps inherit standard supply-chain risk (`cargo-audit`, `cargo- ## 3. Plugin threat model (drill-down) -The plugin boundary is Clarion's biggest single risk. Per ADR-002 and §10: +The plugin boundary is Loomweave's biggest single risk. Per ADR-002 and §10: - Plugins run as **subprocesses at the user's full UID**. No seccomp, no landlock, no namespace isolation. Filesystem = everything the user can read. - Third-party plugins are anticipated (`plugin_id` namespace; Python plugin is the v0.1 reference). -- Install is `pipx install clarion-plugin-X` — execution at install time, no hash pin (NG-16). +- Install is `pipx install loomweave-plugin-X` — execution at install time, no hash pin (NG-16). **What a malicious plugin can do, uncaught in v0.1:** @@ -190,26 +190,26 @@ The plugin boundary is Clarion's biggest single risk. Per ADR-002 and §10: Findings are the suite-wide exchange shape (Principle 4). Threats: 1. **Deserialisation attacks.** Rust side: `serde_json` is generally safe but recursion-depth bombs and untagged enum ambiguity in `FindingKind` are real. No max-depth or size cap is named. Filigree side (recon): hand-rolled `isinstance` validation, no pydantic/jsonschema — deeper structural abuse is plausible. -2. **Injection via `message` / `suggestion` into downstream tools.** Filigree's dashboard renders findings; Clarion's `suggestion` field is truncated at 10,000 chars but not sanitised. Markdown / HTML / terminal-escape injection at render time is an open vector — **not mentioned in the design.** -3. **Forgery of provenance.** `metadata.clarion.entity_id`, `scan_run_id`, `confidence`, `confidence_basis` are all self-asserted. A malicious plugin or a compromised Clarion binary can emit findings claiming any entity provenance. No signing. -4. **Cross-tool impersonation.** `scan_source` is free-text (recon §2.1); Clarion can POST with `scan_source: "wardline"` and vice versa. Auditors reading Filigree can't tell who the real emitter is. -5. **Rule-ID namespace pollution.** `rule_id` is free text; any scanner can emit `CLA-*`, any can emit `WL-*`. No namespace authority. +2. **Injection via `message` / `suggestion` into downstream tools.** Filigree's dashboard renders findings; Loomweave's `suggestion` field is truncated at 10,000 chars but not sanitised. Markdown / HTML / terminal-escape injection at render time is an open vector — **not mentioned in the design.** +3. **Forgery of provenance.** `metadata.loomweave.entity_id`, `scan_run_id`, `confidence`, `confidence_basis` are all self-asserted. A malicious plugin or a compromised Loomweave binary can emit findings claiming any entity provenance. No signing. +4. **Cross-tool impersonation.** `scan_source` is free-text (recon §2.1); Loomweave can POST with `scan_source: "wardline"` and vice versa. Auditors reading Filigree can't tell who the real emitter is. +5. **Rule-ID namespace pollution.** `rule_id` is free text; any scanner can emit `LMWV-*`, any can emit `WL-*`. No namespace authority. 6. **`metadata` bag schema drift.** Recon notes `metadata` round-trips verbatim; Filigree does not validate its shape. Large / nested metadata is a storage-cost DoS on Filigree. --- ## 5. Integration threats -**Clarion ↔ Filigree:** -- HTTP POST with no named auth (recon confirms `scan_source` is free-form; no bearer-token contract between Clarion and Filigree is stated in the design). On a shared dev host, any local process can post findings *as Clarion*. -- MCP-over-stdio subprocess for observations: subprocess spawns are not audited; if Clarion spawns `filigree mcp` the parent-env leaks into the child. +**Loomweave ↔ Filigree:** +- HTTP POST with no named auth (recon confirms `scan_source` is free-form; no bearer-token contract between Loomweave and Filigree is stated in the design). On a shared dev host, any local process can post findings *as Loomweave*. +- MCP-over-stdio subprocess for observations: subprocess spawns are not audited; if Loomweave spawns `filigree mcp` the parent-env leaks into the child. - Replay: no nonce on `scan_run_id`; re-POST of the same findings is idempotent-by-dedup-key but there is no defence against an attacker POSTing yesterday's findings today to mask a real regression. -**Clarion ↔ Wardline:** -- File-based ingest; anyone who can write `wardline.yaml` or `wardline.fingerprint.json` in the tree controls Clarion's Wardline-derived guidance. PR authorship is the only gate. -- Direct Python import of `wardline.core.registry.REGISTRY` at plugin startup: **Python import executes arbitrary code in the importing process.** If the pipx venv's `wardline` package is compromised, Clarion's plugin is compromised. +**Loomweave ↔ Wardline:** +- File-based ingest; anyone who can write `wardline.yaml` or `wardline.fingerprint.json` in the tree controls Loomweave's Wardline-derived guidance. PR authorship is the only gate. +- Direct Python import of `wardline.core.registry.REGISTRY` at plugin startup: **Python import executes arbitrary code in the importing process.** If the pipx venv's `wardline` package is compromised, Loomweave's plugin is compromised. -**Clarion HTTP read API ← any local caller:** +**Loomweave HTTP read API ← any local caller:** - Default `auth: none` on loopback. §10 explicitly accepts this and states the mitigation is *operator choice* to enable token auth. The *default* is the weakness. --- @@ -217,7 +217,7 @@ Findings are the suite-wide exchange shape (Principle 4). Threats: ## 6. Supply-chain - **Rust deps:** `tokio`, `rusqlite`, `serde`, `reqwest`-family, `tree-sitter`, `anthropic` SDK. `cargo-audit` / `cargo-vet` not named in v0.1 CI. SBOM production not named. -- **Plugin deps (Python):** tree-sitter, libcst, detect-secrets, Wardline (`wardline.core.registry`). `pipx` pins the venv; Clarion pins `REGISTRY_VERSION` at release time but does not pin dep hashes. +- **Plugin deps (Python):** tree-sitter, libcst, detect-secrets, Wardline (`wardline.core.registry`). `pipx` pins the venv; Loomweave pins `REGISTRY_VERSION` at release time but does not pin dep hashes. - **No signing of releases.** Neither core binary nor plugin wheels are signed in the v0.1 plan. - **No plugin registry.** Plugins are discovered via PyPI names in `plugins.toml`. NG-16 defers hash-pinning. @@ -233,16 +233,16 @@ L = likelihood (1–3), I = impact (1–3). Risk = L×I. | T-02 | HTTP API default `auth: none` + loopback-trusted assumption (TB-4) | 3 | 3 | **9** | Any local process reads source / writes observations; shared Docker / devcontainers invalidate "loopback = private" | | T-03 | Wardline-derived guidance with `critical: true` survives token budget (TB-6) | 2 | 3 | **6** | A PR that edits `wardline.yaml` steers every future LLM summary; the "observe vs. enforce" boundary doesn't protect against adversarial enforcement input | | T-04 | Prompt-injection → cross-entity poisoning via findings messages (TB-2 + finding fmt) | 3 | 2 | **6** | `message` / `evidence` on a finding becomes LLM context for a neighbouring entity's briefing; schema-validation only gates output shape | -| T-05 | DNS rebinding / non-loopback-enforcement on `clarion serve` (TB-4) | 2 | 3 | **6** | No `Host:` / `Origin:` check stated; a browser tab on the host can POST observations | +| T-05 | DNS rebinding / non-loopback-enforcement on `loomweave serve` (TB-4) | 2 | 3 | **6** | No `Host:` / `Origin:` check stated; a browser tab on the host can POST observations | | T-06 | Headless `auto_emit: true` MCP client bypasses consent gate (TB-4) | 3 | 2 | **6** | Client-declared capability with no server-side authorisation; any agent can claim headless | | T-07 | Committed DB tampering (TB-7); content-hash verify is v0.2 | 2 | 3 | **6** | Teammate inherits poisoned briefings; `git log` on a binary is not a review surface | | T-08 | Plugin path traversal / symlink escape from project root (TB-1, TB-2) | 2 | 3 | **6** | `file_list` is advisory; no path jail enforced core-side | -| T-09 | `scan_source` forgeability → cross-tool impersonation on Filigree (TB-5) | 2 | 2 | **4** | Free-text field, no auth between Clarion and Filigree; audit trail is ambiguous | +| T-09 | `scan_source` forgeability → cross-tool impersonation on Filigree (TB-5) | 2 | 2 | **4** | Free-text field, no auth between Loomweave and Filigree; audit trail is ambiguous | | T-10 | Secret scanner false negatives leak source + secrets to Anthropic (TB-3) | 2 | 3 | **6** | `detect-secrets` is pattern-matching; novel shapes slip through; design acknowledges defence-in-depth posture | | T-11 | Framing / size DoS on JSON-RPC (TB-1) | 2 | 2 | **4** | No explicit Content-Length cap, no per-message size limit named | | T-12 | Entity-count / finding-count DoS from malicious plugin (TB-1) | 2 | 2 | **4** | No per-run bound; writer actor saturates, WAL grows unbounded | | T-13 | LLM response tampering (MITM) (TB-3) | 1 | 3 | **3** | Relies on default TLS validation; cert pinning not named | -| T-14 | Rendering injection via finding `message` into Filigree dashboard (TB-5) | 2 | 2 | **4** | Upstream Clarion has no sanitisation contract; downstream Filigree lacks one too (recon) | +| T-14 | Rendering injection via finding `message` into Filigree dashboard (TB-5) | 2 | 2 | **4** | Upstream Loomweave has no sanitisation contract; downstream Filigree lacks one too (recon) | | T-15 | Plugin supply-chain compromise (PyPI typosquat / takeover) (TB-8) | 2 | 3 | **6** | No hash pinning (NG-16 defers); direct Python import of `wardline` compounds blast radius | | T-16 | Run log (`runs//log.jsonl`) accidentally committed → source + responses leak (TB-7) | 2 | 2 | **4** | Default-git-ignored per NFR-SEC-05; human override is easy | | T-17 | Finding-format deserialisation (depth / size) DoS (finding fmt) | 1 | 2 | **2** | serde_json defaults are reasonable; bound if explicitly set | @@ -255,17 +255,17 @@ L = likelihood (1–3), I = impact (1–3). Risk = L×I. Ranked by risk reduction: 1. **Plugin path-jail + resource limits.** Core-side validation that plugin-returned paths resolve inside the project root; per-plugin RSS cap; per-run entity-count cap; per-message Content-Length cap. Without these, plugin containment is purely reputational. -2. **Default-off is the wrong default.** HTTP API `auth: none` default on loopback should be flipped: auto-generate a token on first `clarion serve`, bind to a Unix-domain socket under `.clarion/serve.sock` (mode 0600) when no token is configured. Opt-*out* of auth, not opt-in. +2. **Default-off is the wrong default.** HTTP API `auth: none` default on loopback should be flipped: auto-generate a token on first `loomweave serve`, bind to a Unix-domain socket under `.loomweave/serve.sock` (mode 0600) when no token is configured. Opt-*out* of auth, not opt-in. 3. **`Host:` / `Origin:` header allowlist** on the HTTP read API to defeat DNS rebinding. 4. **Server-side consent gate** independent of `auto_emit: true` — the flag should require an operator-side config entry, not just a client assertion. 5. **Signed plugin manifest or hash-pinning now, not v0.2.** Even a simple `sha256` in `plugins.toml` cuts T-15 significantly. NG-16's v0.2 deferral is asymmetric with NFR-SEC-01's v0.1 secret scanning. 6. **`scan_source` authentication.** A shared secret or token between each scanner and Filigree — even a per-project HMAC over `(scan_run_id, scan_source)` — closes T-09. 7. **Input-size / depth limits on all JSON ingest paths** (JSON-RPC messages, Wardline state files, SARIF imports, finding-format reads). -8. **Content-hash on load for `.clarion/clarion.db`** (row-level, over entities + guidance + findings), mentioned in §10 as deferred — move to v0.1 because T-07 is the thing Clarion's git-committable-DB USP creates. +8. **Content-hash on load for `.loomweave/loomweave.db`** (row-level, over entities + guidance + findings), mentioned in §10 as deferred — move to v0.1 because T-07 is the thing Loomweave's git-committable-DB USP creates. 9. **Canonical capability model for plugins.** The manifest declares `capabilities` for confidence-basis but not for trust. A "this plugin may emit findings with `confidence_basis: ast_match`" capability bit, signed by the installer, would let the core downgrade claims from untrusted plugins. 10. **Supply-chain CI gates.** `cargo-audit` on the core, `pip-audit` + hash-pins on the plugin venv. Announce SBOM in v0.1. 11. **Symlink-safety rule.** Document and enforce that the analyser does not follow symlinks out of the project root. -12. **Rendering contract for `message` / `suggestion`.** Define that Clarion emits plain text, not markdown/HTML, in fields that cross TB-5; require downstream to treat as untrusted. +12. **Rendering contract for `message` / `suggestion`.** Define that Loomweave emits plain text, not markdown/HTML, in fields that cross TB-5; require downstream to treat as untrusted. --- @@ -281,13 +281,13 @@ Ranked by risk reduction: - No detailed-design §10 or §11 content was read beyond what §10 of system-design surfaces; detailed-design may already specify some of the missing controls. If so, several rows above should downgrade. - Plugin packaging / registry specifics beyond "pipx + `plugins.toml`" are not in the reviewed slice. - No end-to-end HTTP framework choice is stated (axum? warp?), so `Host:` / `Origin:` defaults are unknown. -- Filigree's auth posture beyond the recon is unread; a token scheme might already exist on Filigree's side that the Clarion design would consume. +- Filigree's auth posture beyond the recon is unread; a token scheme might already exist on Filigree's side that the Loomweave design would consume. ## 11. Caveats - This is a docs-only threat model. "The design ignores X" is a documentation gap, not proof of an exploitable bug. Implementation may add the control; equally, implementation may drift from any design control. - STRIDE is breadth-first. Depth (e.g., a full Rust-side deserialisation audit, a full prompt-injection fuzz) is out of scope. -- The Loom federation axiom ("no central orchestrator") is load-bearing here: several integration threats (T-09, T-05) come from *refusing* a central auth plane. Countermeasures must respect the axiom — shared secrets must be pairwise, not suite-wide. +- The Weft federation axiom ("no central orchestrator") is load-bearing here: several integration threats (T-09, T-05) come from *refusing* a central auth plane. Countermeasures must respect the axiom — shared secrets must be pairwise, not suite-wide. --- @@ -295,10 +295,10 @@ Ranked by risk reduction: If nothing else makes v0.1, these three do. Each closes a critical (risk ≥ 9) or compound-critical class that would otherwise embarrass a security-tool release: -1. **Flip the HTTP API default to "authenticated or not listening."** Either bind to a mode-0600 Unix-domain socket by default, or auto-mint a token on first `clarion serve`. `auth: none` on loopback is defended in §10 by naming "loopback is not a boundary"; the design should then not make `none` the default. Closes T-02 and reduces T-05 / T-06. +1. **Flip the HTTP API default to "authenticated or not listening."** Either bind to a mode-0600 Unix-domain socket by default, or auto-mint a token on first `loomweave serve`. `auth: none` on loopback is defended in §10 by naming "loopback is not a boundary"; the design should then not make `none` the default. Closes T-02 and reduces T-05 / T-06. 2. **Core-enforced plugin boundary: path jail + per-run entity/message caps + Content-Length ceiling.** Even without seccomp, the core must refuse plugin-returned paths outside the project root, cap total entities per run, cap per-message size, and cap per-plugin memory via ulimits on spawn. This turns "trusted-source-only" from a doctrine into an enforced minimum. Closes T-08, T-11, T-12 and raises the floor on T-01. -3. **Plugin hash-pinning + release signing now, not v0.2.** `plugins.toml` records a `sha256` over the plugin wheel; `clarion analyze` refuses to run a mismatched plugin. Pair with `cargo-audit` / `pip-audit` CI gates. NG-16's deferral is inconsistent with shipping a security tool. Closes T-15 and materially reduces T-01. +3. **Plugin hash-pinning + release signing now, not v0.2.** `plugins.toml` records a `sha256` over the plugin wheel; `loomweave analyze` refuses to run a mismatched plugin. Pair with `cargo-audit` / `pip-audit` CI gates. NG-16's deferral is inconsistent with shipping a security tool. Closes T-15 and materially reduces T-01. These three share a property: they are *configuration and enforcement* changes, not new subsystems. v0.1 can land them. Sandboxing, DB row signing, full capability model — those are genuinely v0.2. These three are not. diff --git a/docs/implementation/v0.1-reviews/panel-2026-04-17/11-doctrine-panel-synthesis.md b/docs/implementation/v0.1-reviews/panel-2026-04-17/11-doctrine-panel-synthesis.md index 19d29352..dd4252d1 100644 --- a/docs/implementation/v0.1-reviews/panel-2026-04-17/11-doctrine-panel-synthesis.md +++ b/docs/implementation/v0.1-reviews/panel-2026-04-17/11-doctrine-panel-synthesis.md @@ -1,7 +1,7 @@ -# Loom Doctrine Panel — Cross-Panel Synthesis +# Weft Doctrine Panel — Cross-Panel Synthesis -**Panel ID:** loom-doctrine-2026-04-17 -**Target documents:** `docs/suite/briefing.md`, `docs/suite/loom.md` +**Panel ID:** weft-doctrine-2026-04-17 +**Target documents:** `docs/suite/briefing.md`, `docs/suite/weft.md` **Synthesiser:** Panel Synthesiser Agent **Date:** 2026-04-17 **Panel size:** 5 personas (p1 Priya, p2 Marcus, p3 Yasmin, p4 Dev, p5 Sam) @@ -17,12 +17,12 @@ naming the stealth-monolith failure mode rather than hand-waving past it. What every technical reader found, independently and from different angles, is that the v0.1 *implementation* described in `briefing.md` embeds two structural -couplings that the `loom.md` failure test was not designed to catch: +couplings that the `weft.md` failure test was not designed to catch: -1. A **Wardline -> Clarion -> Filigree findings triangle** that makes the - Wardline+Filigree pair functionally dependent on Clarion in v0.1, even though +1. A **Wardline -> Loomweave -> Filigree findings triangle** that makes the + Wardline+Filigree pair functionally dependent on Loomweave in v0.1, even though §4's pairwise composability rule declares this prohibited. -2. A **direct Python import** (`wardline.core.registry.REGISTRY`) from Clarion's +2. A **direct Python import** (`wardline.core.registry.REGISTRY`) from Loomweave's plugin into Wardline's runtime, which is a startup-time, code-level dependency categorically different from the HTTP/file couplings in the same data-flow table. @@ -33,31 +33,31 @@ operationally violating the principle the test is meant to enforce. Three of five personas surfaced this scope mismatch independently. That is structural. Secondary finding: the doctrine is well-written but **untested enrichment**. -Clarion is the product whose absence would most severely stress the architecture -and it does not yet exist. Every claim the doctrine makes about Clarion's +Loomweave is the product whose absence would most severely stress the architecture +and it does not yet exist. Every claim the doctrine makes about Loomweave's behaviour is, at present, theoretical. --- ## 2. Convergent concerns (ranked high-signal to lower-signal) -### 2.1 The Wardline -> Filigree via Clarion triangle — CONFIDENCE: HIGH +### 2.1 The Wardline -> Filigree via Loomweave triangle — CONFIDENCE: HIGH Three personas (p1 Priya, p3 Yasmin, p5 Sam) independently identified that the -Wardline+Filigree pair is not a pair in v0.1 — it is a triangle with Clarion as +Wardline+Filigree pair is not a pair in v0.1 — it is a triangle with Loomweave as required middleware. A fourth (p4 Dev) flagged the same pattern obliquely via the "bootstrapping the suite fabric" phrase. -- **Priya (p1):** "the Filigree + Wardline pair currently requires Clarion as a +- **Priya (p1):** "the Filigree + Wardline pair currently requires Loomweave as a translator ... This is a real violation of §4's pairwise composability rule, and neither doc calls it out explicitly." -- **Yasmin (p3):** "In v0.1, Clarion's translator is the only conduit for +- **Yasmin (p3):** "In v0.1, Loomweave's translator is the only conduit for Wardline findings reaching Filigree's triage store." - **Sam (p5):** "Wardline's findings don't go directly to Filigree. They go - through Clarion's translator first. This means Clarion is on the critical + through Loomweave's translator first. This means Loomweave is on the critical path for Wardline findings to reach Filigree." - **Dev (p4):** "If those protocols, once landed, become the connective tissue - that other products depend on, then Clarion becomes the fabric and 'no Loom + that other products depend on, then Loomweave becomes the fabric and 'no Weft runtime' becomes a naming convention, not an architectural reality." This finding passes the **convergent-reasons test**. Priya arrived via §4 @@ -67,7 +67,7 @@ governance framing. The conclusions converge; the reasoning does not — these are genuinely independent signals, not one prior repeated in four vocabularies. The docs acknowledge it obliquely (briefing: "Wardline should eventually have a -native emitter"; loom.md: silent). All four personas explicitly noted that +native emitter"; weft.md: silent). All four personas explicitly noted that "eventually" is doing heavy lifting. Marcus (p2) is the one persona who did not surface this — consistent with his declared blind spot on data-flow details. @@ -79,7 +79,7 @@ is the single most-cited specific text-level concern in the panel. - **Priya (p1):** "Direct import. Not a file read. Not an HTTP call. A Python import. That is a code-level, startup-time dependency... This is the row that concerns me most in the entire document." -- **Yasmin (p3):** "Clarion's Python plugin takes a direct runtime dependency +- **Yasmin (p3):** "Loomweave's Python plugin takes a direct runtime dependency on Wardline's Python package. That is a tight coupling that the 'each tool is independently usable' principle is supposed to prevent." - **Dev (p4):** "That's not a narrow interop contract — that's a direct import @@ -106,7 +106,7 @@ as worded, is too narrow to catch the couplings they observed. drift but not initialization coupling. The direct-import row passes the stated test while still being a form of coupling that would break in a deployment where Wardline isn't on the same Python path." -- **Yasmin (p3):** "Clarion's SARIF translator is not enrichment in the §5 +- **Yasmin (p3):** "Loomweave's SARIF translator is not enrichment in the §5 sense; it is a pipeline stage. The doctrine's enrichment principle does not directly govern it, and that is a gap in the doctrine's coverage." - **Sam (p5):** "I think the doctrine's failure test technically passes. But I @@ -139,20 +139,20 @@ Marcus reads it as honest self-disclosure (the status table redeems the oversold lede); Dev reads it as governance theatre (a doctrine applied to a non-design is placeholder-grade). Same fact, two postures. -### 2.5 Identity-translation centralisation in Clarion — CONFIDENCE: MEDIUM-HIGH +### 2.5 Identity-translation centralisation in Loomweave — CONFIDENCE: MEDIUM-HIGH -Three personas (p1, p3, p5) noted that Clarion's role as reconciler for all +Three personas (p1, p3, p5) noted that Loomweave's role as reconciler for all three identity schemes makes it the de-facto translation authority for the -suite, even though §6 disclaims a "neutral Loom identity oracle." See the +suite, even though §6 disclaims a "neutral Weft identity oracle." See the unreliable-narrator check (section 4) for Sam's partial recoil from his stronger form of this claim. -- **Priya (p1):** "Clarion a de-facto shared identity authority. Not a shared +- **Priya (p1):** "Loomweave a de-facto shared identity authority. Not a shared store — the store is local files — but a shared authority." - **Yasmin (p3):** "Three identity schemes, one translator. Honest. Also: a significant single point of governance responsibility." -- **Sam (p5):** "Clarion isn't a 'neutral identity oracle' in the sense - loom.md §6 disclaims. But it is *the place where all identity reconciliation +- **Sam (p5):** "Loomweave isn't a 'neutral identity oracle' in the sense + weft.md §6 disclaims. But it is *the place where all identity reconciliation happens*." The reasoning across all three is closely aligned, so I downgrade from HIGH to @@ -163,14 +163,14 @@ centralised infrastructure") in three vocabularies. Still substantial signal. Two personas (p3 Yasmin, p5 Sam) raised entity-ID versioning concerns. Yasmin framed it as SOC 2 audit continuity; Sam framed it as cross-reference -resolution. Both asked: what happens to existing Filigree records when Clarion +resolution. Both asked: what happens to existing Filigree records when Loomweave re-keys its catalog? -- **Yasmin Q5:** "When Clarion's EntityId minting algorithm changes between +- **Yasmin Q5:** "When Loomweave's EntityId minting algorithm changes between versions, what is the migration story for Filigree records that carry stale EntityId cross-references?" -- **Sam Q2:** "Filigree issues reference entities by Clarion EntityId. If - Clarion is removed from a deployment, those references become opaque +- **Sam Q2:** "Filigree issues reference entities by Loomweave EntityId. If + Loomweave is removed from a deployment, those references become opaque strings." Not universal (p1, p2, p4 did not raise it), so MEDIUM. But Yasmin's original @@ -181,12 +181,12 @@ verbatim on this question. That makes it a high-quality MEDIUM finding. One persona (Dev, p4) flagged this with full clarity; Priya (p1) noted it partially. The briefing asks Filigree to add a pluggable `registry_backend` "so -Clarion can own the file registry." Dev noted this reverses the stated +Loomweave can own the file registry." Dev noted this reverses the stated direction of enrichment: -- **Dev (p4):** "What happens to Filigree in `--no-clarion`? If Filigree's +- **Dev (p4):** "What happens to Filigree in `--no-loomweave`? If Filigree's registry backend silently breaks or degrades to an incoherent state without - Clarion, that's a load-bearing dependency in the direction loom.md most + Loomweave, that's a load-bearing dependency in the direction weft.md most explicitly prohibits." Only one persona raised it to full prominence, but it is a concrete, testable, @@ -194,7 +194,7 @@ Tier 1 text-surface concern. MEDIUM confidence purely on persona count. ### 2.8 MCP failure behaviour during consult — CONFIDENCE: LOW-MEDIUM -Priya (p1, Q5) flagged that Clarion's observation-emission path to Filigree +Priya (p1, Q5) flagged that Loomweave's observation-emission path to Filigree via MCP has no documented failure behaviour. No other persona raised it. Low persona count but highly specific — keep as a LOW-MEDIUM noted gap. @@ -221,7 +221,7 @@ substitutes doctrine for working code. This is the **adoption-vs-contribution tension** that the collision test (see section 5) was designed to surface. Marcus is evaluating whether to put Filigree+Wardline in front of his platform lead (yes, conditionally). Dev is -evaluating whether to invest time contributing to Clarion (not yet, because +evaluating whether to invest time contributing to Loomweave (not yet, because §7's go/no-go test is being applied to a non-design). **The documents are doing their job for the adopter persona and not yet doing @@ -230,12 +230,12 @@ their job for the contributor persona.** ### 3.2 Yasmin's compliance lens vs Sam's adversarial lens on identity On the same identity-scheme text (briefing's "three concurrent identity -schemes" paragraph and loom.md §6's "product that cares does the translation"), +schemes" paragraph and weft.md §6's "product that cares does the translation"), Yasmin and Sam diverged sharply: - **Yasmin (p3):** The identity scheme passes the principle *at rest* — once a finding is in Filigree, Filigree's triage authority is durable. Her - concern is *over time*: what happens when Clarion's translator changes? + concern is *over time*: what happens when Loomweave's translator changes? - **Sam (p5):** The identity scheme passes the doctrine's failure test *as worded* but the doctrine's test is scoped narrowly. His concern is *scope of the test itself*. @@ -268,8 +268,8 @@ reject Dev's conclusion; he is the only data point. ## 4. Unreliable-narrator check (Sam / p5) -**Pre-registered misconception:** Sam will read the Clarion identity -translation layer as evidence that Clarion is a "secret monolith" by the +**Pre-registered misconception:** Sam will read the Loomweave identity +translation layer as evidence that Loomweave is a "secret monolith" by the doctrine's own failure test. **Outcome: REFINED, not CONFIRMED.** @@ -279,16 +279,16 @@ table ("the pattern I was looking for"), then pulled back after re-reading §5 and §6. His own words in the Unreliable-Narrator Note at the end of his journal: -> "The strongest version of my critique — that Clarion is a 'secret monolith' +> "The strongest version of my critique — that Loomweave is a 'secret monolith' > by the doctrine's own failure test — doesn't actually survive careful > reading. The failure test is about whether removing a sibling changes the > *meaning* of another product's *own* data. Filigree's issues are meaningful -> without Clarion. Wardline's scanner output is meaningful without Clarion. +> without Loomweave. Wardline's scanner output is meaningful without Loomweave. > The cross-references become unresolvable, but they don't become > *incoherent*. The doctrine draws this line deliberately, and I think the > line is defensible, even if I'd draw it in a slightly different place." -He refined the criticism to: "Clarion is structurally central to any workflow +He refined the criticism to: "Loomweave is structurally central to any workflow that crosses tool boundaries, even though the doctrine correctly notes that each product's *own* data remains coherent in isolation." @@ -313,9 +313,9 @@ Three pre-registered predictions. Auditing against Marcus's actual journal: | Prediction | Actual | Match? | |---|---|---| -| **Reading path:** Status table first in briefing, then loom.md §3–§5, skip §7/§8 | "First move: scan the page for a status table. Took about thirty seconds." Then loom.md §3, §4, §5, §6, skipped §7 and §8 explicitly ("Skipped, as intended"). | **YES, exact.** He read §6 which was not predicted but is adjacent to §5. Path otherwise pixel-perfect. | +| **Reading path:** Status table first in briefing, then weft.md §3–§5, skip §7/§8 | "First move: scan the page for a status table. Took about thirty seconds." Then weft.md §3, §4, §5, §6, skipped §7 and §8 explicitly ("Skipped, as intended"). | **YES, exact.** He read §6 which was not predicted but is adjacent to §5. Path otherwise pixel-perfect. | | **Key concern:** Gap between three-product framing and two-tools-actually-running reality | "The lede — the one-paragraph version at the top — reads 'three independent tools that enrich one another.' That's a three-product claim. The table tells me it's two-plus-one-on-paper-plus-one-concept." | **YES, exact.** Language is almost verbatim the predicted reaction. | -| **Verdict:** Cautiously interested, pass to senior engineer, will not dismiss, local-first positioning lands | Verdict file: "Cautiously interested. Passing to a senior engineer for deeper review of Clarion's detailed design." Notes local-first positioning approvingly ("tells me there's no surprise cloud dependency to audit"). | **YES, exact.** | +| **Verdict:** Cautiously interested, pass to senior engineer, will not dismiss, local-first positioning lands | Verdict file: "Cautiously interested. Passing to a senior engineer for deeper review of Loomweave's detailed design." Notes local-first positioning approvingly ("tells me there's no surprise cloud dependency to audit"). | **YES, exact.** | All three predictions matched. The control persona is calibrated. This gives us confidence in two things: @@ -341,7 +341,7 @@ directions. If they do, it is structural. **Outcome: CONVERGED, structurally.** Priya arrived at "direct Python import is startup-time coupling that the -failure test misses." Sam arrived at "Clarion-as-translation-layer is +failure test misses." Sam arrived at "Loomweave-as-translation-layer is structurally central to cross-tool workflows." Different surface claims; identical underlying structural concern. Both converged on "the §5 failure test is scoped too narrowly to catch what v0.1 actually does." @@ -397,11 +397,11 @@ this collision are two framings of the same doctrine gap. | # | Finding | Personas | Tier | Confidence | |---|---|---|---|---| -| 2.1 | Wardline+Filigree-via-Clarion triangle violates §4 | p1, p3, p5 (+p4 oblique) | Tier 1 text-surface | HIGH | +| 2.1 | Wardline+Filigree-via-Loomweave triangle violates §4 | p1, p3, p5 (+p4 oblique) | Tier 1 text-surface | HIGH | | 2.2 | `wardline.core.registry.REGISTRY` direct import | p1, p3, p4, p5 | Tier 1 text-surface | HIGH | | 2.3 | §5 failure test scoped too narrowly | p1, p3, p5 | Tier 2 affective/analytical | HIGH | | 2.4 | Doctrine outpaces implementation (untested enrichment) | all 5 | Tier 1 + Tier 3 | HIGH | -| 2.5 | Identity reconciliation centralised in Clarion | p1, p3, p5 | Tier 2 | MEDIUM-HIGH | +| 2.5 | Identity reconciliation centralised in Loomweave | p1, p3, p5 | Tier 2 | MEDIUM-HIGH | | 2.6 | EntityId stability / schema versioning | p3, p5 | Tier 3 institutional | MEDIUM | | 2.7 | `registry_backend` dependency inversion | p4 (+p1 partial) | Tier 1 text-surface | MEDIUM | | 2.8 | MCP failure behaviour undocumented | p1 | Tier 1 text-surface | LOW-MEDIUM | @@ -424,16 +424,16 @@ One gap the panel *did* reveal, not pre-registered: - **Contributor onboarding persona (not Dev).** Dev is a maintainer of a competing OSS tool evaluating whether to contribute. What the panel lacks - is someone evaluating "I have been asked to add a Python plugin to Clarion - and I do not yet have opinions about Loom as an ecosystem." Dev's Q5 and + is someone evaluating "I have been asked to add a Python plugin to Loomweave + and I do not yet have opinions about Weft as an ecosystem." Dev's Q5 and Q3 point at this gap directly: "What does 'designed, not yet built' mean for contribution entry points?" A less-adversarial contributor-oriented - reader would test whether `loom.md` is a useful read for someone who has + reader would test whether `weft.md` is a useful read for someone who has already decided to contribute, or whether it is primarily a doctrine for the project author. The config's "elspeth team member" explicit gap remains unfilled. The docs -repeatedly reference elspeth as Clarion's first customer. No panel persona +repeatedly reference elspeth as Loomweave's first customer. No panel persona represents that viewpoint. Findings 2.4 and 2.6 would benefit most from that reader's ground-truth perspective. @@ -456,7 +456,7 @@ What the documents do *not* yet do is hold up against their own data-flow table. Three of five readers, arriving from different angles, concluded that the §5 failure test is scoped narrowly enough to pass the two couplings that are structurally tightest in v0.1: the `wardline.core.registry.REGISTRY` -direct Python import, and the Wardline -> Clarion -> Filigree SARIF triangle. +direct Python import, and the Wardline -> Loomweave -> Filigree SARIF triangle. Your doctrine is honest about the former (the briefing's "what the suite needs" section names the REGISTRY_VERSION ask explicitly) and evasive about the latter ("eventually" is doing heavy lifting in the claim that @@ -490,9 +490,9 @@ strong enough to survive being this honest about its current scope. this edit. Minor. Cheap. High trust payoff. [Addresses 2.4] 3. **Add a "pairwise composability in v0.1" subsection** to `briefing.md` or - `loom.md` that enumerates each pair and tests it against §4 honestly. - Wardline+Filigree today is a triangle; say so. Wardline+Clarion is a pair; - say so. Clarion+Filigree is a pair; say so. This defuses the convergent + `weft.md` that enumerates each pair and tests it against §4 honestly. + Wardline+Filigree today is a triangle; say so. Wardline+Loomweave is a pair; + say so. Loomweave+Filigree is a pair; say so. This defuses the convergent concern. [Addresses 2.1] 4. **Document the `wardline.core.registry.REGISTRY` import contract**. @@ -501,17 +501,17 @@ strong enough to survive being this honest about its current scope. release-train gate). Four of five personas asked variants of this question. [Addresses 2.2] -5. **Add a contributor-facing "how Loom serves contributors" pointer** or a - `CONTRIBUTING.md`. Dev's journal reveals that `loom.md` reads as +5. **Add a contributor-facing "how Weft serves contributors" pointer** or a + `CONTRIBUTING.md`. Dev's journal reveals that `weft.md` reads as governance self-binding when viewed from the contributor seat. A one-line pointer to contributor docs — even if those docs are "not yet written, see `v0.1/README.md`" — repositions the doctrine correctly. [Addresses 6.2] -6. **Specify the finding-deduplication key contract** across Clarion +6. **Specify the finding-deduplication key contract** across Loomweave translator versions. Yasmin's Q1 is the most specific unanswered question in the panel and lives below doctrine level — but a single sentence in the doctrine ("stable deduplication identity across translator versions is a - Clarion release-train gate") would neutralise the concern. [Addresses 2.6] + Loomweave release-train gate") would neutralise the concern. [Addresses 2.6] --- @@ -519,9 +519,9 @@ strong enough to survive being this honest about its current scope. Worth recording what *did not* surface, because absences matter: -- **No persona challenged the four-product structure** (Clarion, Filigree, +- **No persona challenged the four-product structure** (Loomweave, Filigree, Wardline, Shuttle). The bounded-domain table in §2 was uniformly praised. -- **No persona found the "loom" metaphor unhelpful**, though Priya flagged it +- **No persona found the "weft" metaphor unhelpful**, though Priya flagged it as discount-worthy marketing. She did not retract that view, but it did not harden into a finding. - **No persona rejected "local-first, single-binary, git-committable state"** diff --git a/docs/implementation/v0.1-reviews/pre-restructure/design-review.md b/docs/implementation/v0.1-reviews/pre-restructure/design-review.md index de3ee11d..0a2eab60 100644 --- a/docs/implementation/v0.1-reviews/pre-restructure/design-review.md +++ b/docs/implementation/v0.1-reviews/pre-restructure/design-review.md @@ -1,6 +1,6 @@ -# Clarion v0.1 Design Review +# Loomweave v0.1 Design Review -**Original reviewed document (pre-restructure)**: `2026-04-17-clarion-v0.1-design.md` +**Original reviewed document (pre-restructure)**: `2026-04-17-loomweave-v0.1-design.md` **Historical note**: this review evaluates the pre-restructure single-file design. The current canonical docset lives in [../README.md](../README.md), with the resulting implementation-level reference in [../detailed-design.md](../detailed-design.md). This file is retained as supporting context, not as normative guidance. **Review date**: 2026-04-17 **Review method**: Eight parallel specialist reviews across solution architecture, systems dynamics, Python engineering, Rust engineering, security, integration architecture, reality check (symbol/path/API verification), and architecture-decision rigor. @@ -16,13 +16,13 @@ These are not design opinions; they are claims in the document that do not match The design uses "SARIF-lite" throughout (§Abstract line 25, §Principle 4 line 68, §9) as if Filigree's intake is SARIF-shaped. It is not. Filigree's actual intake at `src/filigree/dashboard_routes/files.py:294` takes a flat custom JSON format keyed on `scan_source` + a `findings` array of `{path, rule_id, message, severity, ...}`. Real SARIF uses `runs[].results[].locations[].physicalLocation` with driver/tool objects and fingerprints. Wardline separately emits genuine SARIF v2.1.0, but that is not what Filigree ingests. -**Consequence**: the suite's "shared finding-exchange protocol" as described does not exist. A Clarion-to-Filigree adapter has to translate between them. The design must either (a) state that Clarion emits Filigree's native intake schema and drop the "SARIF-lite" framing, (b) propose a SARIF-compatible extension to Filigree's intake endpoint as a prerequisite, or (c) position Clarion as emitting SARIF upstream of a suite-internal translator. Pick one and say it. +**Consequence**: the suite's "shared finding-exchange protocol" as described does not exist. A Loomweave-to-Filigree adapter has to translate between them. The design must either (a) state that Loomweave emits Filigree's native intake schema and drop the "SARIF-lite" framing, (b) propose a SARIF-compatible extension to Filigree's intake endpoint as a prerequisite, or (c) position Loomweave as emitting SARIF upstream of a suite-internal translator. Pick one and say it. ### 1.2 Anthropic model IDs use a notation not found in any real code in this workspace The config example in §5 uses hyphen-separated version numbers: `claude-haiku-4-5`, `claude-sonnet-4-6`, `claude-opus-4-7`. Real code in adjacent projects (Wardline fixtures, elspeth, Filigree tests) uses dot notation: `claude-sonnet-4.5`, `claude-opus-4.6`. `claude-opus-4-7` specifically has no evidence anywhere in the workspace. -**Consequence**: either the hyphen form is a future API change that hasn't landed yet, or the config example is wrong. This must be pinned against the actual Anthropic SDK version the implementation will use. Shipping with wrong model IDs means `clarion analyze` fails on every LLM call. +**Consequence**: either the hyphen form is a future API change that hasn't landed yet, or the config example is wrong. This must be pinned against the actual Anthropic SDK version the implementation will use. Shipping with wrong model IDs means `loomweave analyze` fails on every LLM call. ### 1.3 Plugin manifest silently omits Wardline groups 9, 12, 13 @@ -42,7 +42,7 @@ These are design decisions that, if shipped as currently written, will cause vis **Corroboration**: Architecture, Python, Integration, ADR reviews. -§3 specifies entity IDs as `{plugin_id}:{kind}:{file_path}::{qualified_name}` and explicitly defers rename tracking (`EntityAlias`) to v0.2. The design's own stated differentiator is the cross-tool graph — Filigree issues reference Clarion entity IDs, Wardline declarations reference them, guidance sheets match against them. Every file move, class rename, or `__init__.py` re-export resolution change silently detaches every reference. +§3 specifies entity IDs as `{plugin_id}:{kind}:{file_path}::{qualified_name}` and explicitly defers rename tracking (`EntityAlias`) to v0.2. The design's own stated differentiator is the cross-tool graph — Filigree issues reference Loomweave entity IDs, Wardline declarations reference them, guidance sheets match against them. Every file move, class rename, or `__init__.py` re-export resolution change silently detaches every reference. In a 425k-LOC Python codebase that is the validating first customer, renames and moves are weekly events. Python-side specifically: `from auth import TokenManager` where `TokenManager` is defined in `src/auth/tokens.py` and re-exported from `src/auth/__init__.py` — the naive resolver creates two IDs for the same entity. Hundreds of affected entities in a well-structured package. @@ -57,10 +57,10 @@ Option 1 is the cheapest and most defensible. Option 3 should not be chosen sile **Corroboration**: Architecture, Rust reviews. -§4 claims WAL mode "supports concurrent reads during writes" and says `clarion analyze` "holds the writer lock for the duration of the batch." The example run in §6 shows a 38-minute batch. These are incompatible: +§4 claims WAL mode "supports concurrent reads during writes" and says `loomweave analyze` "holds the writer lock for the duration of the batch." The example run in §6 shows a 38-minute batch. These are incompatible: - WAL grows unboundedly during a long writer; checkpointing cannot complete while readers pinned to the pre-analyze snapshot hold the old page. -- `clarion serve` writes to the summary cache during live consult sessions (§5) — these writes will block or timeout. +- `loomweave serve` writes to the summary cache during live consult sessions (§5) — these writes will block or timeout. - Multiple writer sources are unacknowledged: analyze ingesting entities/edges, serve writing summary-cache on consult misses, serve writing session state. **Mitigation**: specify a writer-actor model (single tokio task owning the write connection, bounded mpsc inbox), short per-N-files transactions rather than a single batch transaction, and explicit `busy_timeout` + checkpoint strategy. Alternative: analyze writes to a shadow DB and atomic-swaps on completion — simpler, and acceptable for single-user local-first deployments. @@ -88,7 +88,7 @@ Two related threats with severity 9/9 in the security analysis: - **Secrets exfiltration**: The design sends file content to Anthropic for summarization (§5, §6) with no pre-ingest secret scanner, no redaction pass, no per-file allow/deny beyond include/exclude globs. Any `.env`, test fixture, or committed API key enters Anthropic's API and persists in the summary cache and `runs//log.jsonl` (which the design keeps "for audit"). - **Prompt injection**: Adversarial docstrings/comments in source become attacker-controlled field values in the structured briefing schema. Schema validation doesn't help — the schema validates shape, not semantic content. An LLM-proposed guidance sheet (auto-promoted via the `propose_guidance` capability) then persists attacker text into every future prompt (prompt-cache poisoning). -**Mitigation**: a pre-ingest secret-scanner pass (`detect-secrets` or equivalent) with findings blocking LLM dispatch on unredacted hits, and explicit prompt-injection handling (untrusted-content delimiters, or an "untrusted" role boundary in the prompt structure). Both belong in v0.1, not deferred — the first real user running `clarion analyze` on a repo with a committed `.env` leaks to Anthropic silently. +**Mitigation**: a pre-ingest secret-scanner pass (`detect-secrets` or equivalent) with findings blocking LLM dispatch on unredacted hits, and explicit prompt-injection handling (untrusted-content delimiters, or an "untrusted" role boundary in the prompt structure). Both belong in v0.1, not deferred — the first real user running `loomweave analyze` on a repo with a committed `.env` leaks to Anthropic silently. ### 2.5 Provider abstraction is at the wrong layer (HIGH) @@ -102,11 +102,11 @@ Two related threats with severity 9/9 in the security analysis: **Corroboration**: Security, Integration reviews. -§9 binds the HTTP read API to loopback with no auth by default. §1 says Wardline "runs at commit cadence, pulling current entity state and declared topology from Clarion's HTTP read API." If Wardline runs in CI (the typical commit-cadence posture), loopback-only doesn't fit. The token-auth path is marked "opt-in" but not designed — no format, no rotation, no scoping. +§9 binds the HTTP read API to loopback with no auth by default. §1 says Wardline "runs at commit cadence, pulling current entity state and declared topology from Loomweave's HTTP read API." If Wardline runs in CI (the typical commit-cadence posture), loopback-only doesn't fit. The token-auth path is marked "opt-in" but not designed — no format, no rotation, no scoping. Additionally, loopback is not a security boundary on modern dev hosts: shared Docker host networks, devcontainers, browser DNS-rebinding against a fixed port, and compromised IDE extensions all reach 127.0.0.1. Treating it as secure is a category error. -**Mitigation**: design token auth in v0.1 even if opt-in remains the default. Specify the token format, where it's stored (OS keychain preferred, `.clarion/token` with `0600` mode as fallback), and how Wardline picks it up in CI. +**Mitigation**: design token auth in v0.1 even if opt-in remains the default. Specify the token format, where it's stored (OS keychain preferred, `.loomweave/token` with `0600` mode as fallback), and how Wardline picks it up in CI. --- @@ -120,7 +120,7 @@ These decisions are currently buried in prose or presented as foregone. They are | ADR-002 | Plugin transport: LSP-style subprocess JSON-RPC | Invoked by analogy to LSP/tree-sitter; Wasm, dylib, embedded-Python not considered | Becomes the third-party plugin contract | P0 | | ADR-003 | Entity ID scheme + rename handling | Path-embedded, `EntityAlias` deferred | Cross-tool identity depends on it; see §2.1 above | P0 | | ADR-004 | SARIF-lite vs SARIF vs native | Named; doesn't match Filigree reality | Cross-tool interop; see §1.1 | P0 | -| ADR-005 | `.clarion/` git-committable including SQLite DB | Stated as feature; conflict with LLM-derived secrets in DB unaddressed | Operational; affects every user | P1 | +| ADR-005 | `.loomweave/` git-committable including SQLite DB | Stated as feature; conflict with LLM-derived secrets in DB unaddressed | Operational; affects every user | P1 | | ADR-006 | Clustering algorithm (Leiden/Louvain specifically) | Named without comparison | Expensive LLM synthesis (Phase 6 Opus calls) rides on cluster quality | P1 | | ADR-007 | Summary cache key design and invalidation | Specified but alternatives not considered | Guidance edits invalidate broad cache swathes; cost impact | P1 | | ADR-008 | Filigree file-registry displacement | In a table, not called out as breaking change | Cross-product coordination; affects existing integrations | P1 | @@ -152,13 +152,13 @@ Manual-authored and LLM-proposed guidance sheets have no lifecycle coupling to t ### 4.3 Finding stock has no drain -Every `clarion analyze` run emits findings. There is no feedback from Filigree's triage state back to Clarion's emission policy. A rule that produces 500 suppressed findings with no promotions in 90 days is noise for this project, but Clarion's next run emits the same rule at the same priority. +Every `loomweave analyze` run emits findings. There is no feedback from Filigree's triage state back to Loomweave's emission policy. A rule that produces 500 suppressed findings with no promotions in 90 days is noise for this project, but Loomweave's next run emits the same rule at the same priority. -**Mitigation**: Filigree triage outcomes flow back as a per-rule priority modifier. Simple: if rule suppression rate > N% over M days, emit `CLA-INFRA-RULE-LOW-VALUE` and suggest a configuration change. +**Mitigation**: Filigree triage outcomes flow back as a per-rule priority modifier. Simple: if rule suppression rate > N% over M days, emit `LMWV-INFRA-RULE-LOW-VALUE` and suggest a configuration change. ### 4.4 Exploration elimination may induce LLM capability atrophy -The stated goal of Principle 2 is to pre-compute structural answers so agents don't have to spawn explore subagents. The second-order risk is that agents trained (by usage pattern) to trust Clarion never develop exploration strategies for cases where the catalog is wrong or incomplete — e.g., latent race conditions visible in runtime traces but invisible to static analysis. +The stated goal of Principle 2 is to pre-compute structural answers so agents don't have to spawn explore subagents. The second-order risk is that agents trained (by usage pattern) to trust Loomweave never develop exploration strategies for cases where the catalog is wrong or incomplete — e.g., latent race conditions visible in runtime traces but invisible to static analysis. **Mitigation**: the briefing schema should include a `knowledge_basis` field marking each briefing with the class of evidence it rests on (`static_only`, `runtime_informed`, `human_verified`). LLMs consuming the briefing can use this to decide whether to look further. Single-field schema addition with significant downstream calibration value. @@ -171,8 +171,8 @@ From the Python engineering review. These are plugin-side concerns that compound 1. **Parser dispatch (tree-sitter vs LibCST) is undefined.** Which parser owns which task? LibCST parse failures — what's the fallback? Use `libcst.native` (Rust backend) if LibCST is in the hot path. 2. **Decorator-as-DSL detection is naive.** Direct name match only works for the simple case. Decorator factories (`@app.route("/health")`), stacked decorators (order matters for Wardline semantics), class decorators, and aliases (`validates = validates_shape`) need explicit policy. 3. **Call graph precision is overstated.** AST-only analysis produces reliable `calls` edges for direct same-scope calls, approximate (name-match) for method calls, and nothing for dynamic dispatch. The manifest's `calls: true` capability should carry a `confidence_basis: "ast_match"` qualifier. -4. **`TYPE_CHECKING` block imports are unmentioned.** Treating them as runtime `imports` edges produces false `CLA-PY-STRUCTURE-001` circular-import findings in any typed codebase. -5. **Plugin packaging and interpreter isolation.** `pip install clarion-plugin-python` into an unknown environment risks dependency conflicts with the analyzed project. Recommend `pipx` for isolation; define `plugins.toml` schema (`executable`, `python_version`). +4. **`TYPE_CHECKING` block imports are unmentioned.** Treating them as runtime `imports` edges produces false `LMWV-PY-STRUCTURE-001` circular-import findings in any typed codebase. +5. **Plugin packaging and interpreter isolation.** `pip install loomweave-plugin-python` into an unknown environment risks dependency conflicts with the analyzed project. Recommend `pipx` for isolation; define `plugins.toml` schema (`executable`, `python_version`). 6. **Serial-or-parallel posture.** "May thread internally" is not a posture. Commit to serial for v0.1 (defensible) or specify the parallelism mechanism (partitioned `analyze_file_batch` RPC, or `multiprocessing.Pool` inside the plugin). --- @@ -196,22 +196,22 @@ From the Rust engineering review. These are implementation-stack choices that be Not everything in the spec needs to change. The following are load-bearing strengths worth protecting during revision: - **Principle 3 (plugin-owns-ontology, core-owns-algorithms)** is the most important structural decision in the document. Any pressure to add language-specific logic to the core for convenience should be resisted. -- **Principle 5 (observe vs enforce) is a strict boundary** that keeps Clarion and Wardline from merging into one bloated tool. Protect it — and see §8 below for one place it already leaks. +- **Principle 5 (observe vs enforce) is a strict boundary** that keeps Loomweave and Wardline from merging into one bloated tool. Protect it — and see §8 below for one place it already leaks. - **Structured briefings with a fixed schema** (§3) is the right LLM-consumption shape. Prose summaries would lose the entire composability story. - **Guidance fingerprint as cache key** invalidates exactly the affected summaries when guidance changes — non-obvious and correct. - **No-silent-fallback posture** in the failure model (§6): every failure emits a finding. This is rare in tools of this class. - **Explicit non-goals list** (§10) is defensible and scopes the tool appropriately. - **Pre-computed exploration-query MCP tools** (§8): `find_entry_points`, `find_http_routes`, `find_data_models` family operationalize Principle 2 in a way agents can actually use. -- **Entity-ID-on-Filigree-issues boundary**: the decision that Clarion owns file/entity identity and Filigree owns workflow/lifecycle is a real architectural commitment, not a compromise — which is exactly why §2.1 (ID stability) matters so much. +- **Entity-ID-on-Filigree-issues boundary**: the decision that Loomweave owns file/entity identity and Filigree owns workflow/lifecycle is a real architectural commitment, not a compromise — which is exactly why §2.1 (ID stability) matters so much. - **Blake3 for fingerprints, provenance columns on every summary, RecordingProvider for tests**: all correct implementation-level bets. --- ## 8. Observe-vs-Enforce Boundary Leak -Principle 5 states that Clarion's plugin detects *that* an annotation is present; Wardline determines *whether* the annotated code satisfies its declared semantic. The plugin manifest in §2 hardcodes Wardline's annotation names (`@validates_shape`, `@integral_writer`, etc.) and group numbering. This couples Clarion's release cadence to Wardline's vocabulary: adding a new Wardline annotation requires a Clarion plugin release. +Principle 5 states that Loomweave's plugin detects *that* an annotation is present; Wardline determines *whether* the annotated code satisfies its declared semantic. The plugin manifest in §2 hardcodes Wardline's annotation names (`@validates_shape`, `@integral_writer`, etc.) and group numbering. This couples Loomweave's release cadence to Wardline's vocabulary: adding a new Wardline annotation requires a Loomweave plugin release. -**Mitigation**: Wardline ships an annotation-descriptor file (JSON/YAML) that Clarion plugins consume. Clarion detects "the annotation Wardline cares about is present" without hardcoding which ones those are. Preserves Principle 5 and inverts the vocabulary coupling so the faster-shipping tool (Clarion) isn't gated by the slower one (Wardline). +**Mitigation**: Wardline ships an annotation-descriptor file (JSON/YAML) that Loomweave plugins consume. Loomweave detects "the annotation Wardline cares about is present" without hardcoding which ones those are. Preserves Principle 5 and inverts the vocabulary coupling so the faster-shipping tool (Loomweave) isn't gated by the slower one (Wardline). --- @@ -220,10 +220,10 @@ Principle 5 states that Clarion's plugin detects *that* an annotation is present In order: 1. **Correct factual errors** (§1 above). One pass through the document; small edits. -2. **Resolve the SARIF-lite confusion** — either translate at the Clarion/Filigree boundary and drop the SARIF framing, or propose a SARIF extension to Filigree's intake. This is a decision, not an edit. +2. **Resolve the SARIF-lite confusion** — either translate at the Loomweave/Filigree boundary and drop the SARIF framing, or propose a SARIF extension to Filigree's intake. This is a decision, not an edit. 3. **Decide the entity ID scheme** — either content-addressed + symbolic, or promote `EntityAlias` into v0.1. This is the single decision most likely to prevent downstream rework. 4. **Author ADR-001 (core language) and ADR-002 (plugin transport)**. Even if the answers don't change, the act of comparing alternatives exposes blind spots. Do this before the implementation plan. -5. **Add a `Security` section** covering secret-scanning pre-ingest, prompt-injection handling, `.clarion/` commit posture, HTTP auth for Wardline, API-key file-mode requirements. +5. **Add a `Security` section** covering secret-scanning pre-ingest, prompt-injection handling, `.loomweave/` commit posture, HTTP auth for Wardline, API-key file-mode requirements. 6. **Decide the SQLite concurrency model** (writer-actor vs shadow-DB-swap) and document it in §4. 7. **Fill in the Python plugin gaps**: import resolution mechanism, parser dispatch, `TYPE_CHECKING` handling, parallelism posture. 8. **Write the "Rust stack" addendum** (one page) naming the crate choices so implementation doesn't re-derive them. diff --git a/docs/implementation/v0.1-reviews/pre-restructure/integration-recon.md b/docs/implementation/v0.1-reviews/pre-restructure/integration-recon.md index 83816461..0a3155fe 100644 --- a/docs/implementation/v0.1-reviews/pre-restructure/integration-recon.md +++ b/docs/implementation/v0.1-reviews/pre-restructure/integration-recon.md @@ -1,27 +1,27 @@ -# Clarion v0.1 Integration Reconnaissance +# Loomweave v0.1 Integration Reconnaissance -**Original reviewed document (pre-restructure)**: `2026-04-17-clarion-v0.1-design.md` (Revision 2) -**Historical note**: this reconnaissance evaluates the pre-restructure single-file design at revision 2. The current canonical Clarion v0.1 docset lives in [../README.md](../README.md). This file is retained as supporting context, not as normative guidance. +**Original reviewed document (pre-restructure)**: `2026-04-17-loomweave-v0.1-design.md` (Revision 2) +**Historical note**: this reconnaissance evaluates the pre-restructure single-file design at revision 2. The current canonical Loomweave v0.1 docset lives in [../README.md](../README.md). This file is retained as supporting context, not as normative guidance. **Companion review**: [design-review.md](./design-review.md) **Recon date**: 2026-04-17 **Method**: Read-only static analysis of `/home/john/filigree` and `/home/john/wardline`, run in five parallel focused sweeps (scan-results intake, file registry, Wardline annotations, Wardline SARIF & entity model, cross-tool flows). Every claim in this document is file:line-cited or declared as an absence-verified negative. -**Verdict**: **Design revision is required before the implementation plan.** Two assertions are factually wrong (finding-field names, Wardline group 9/12/13 declaration path), one major architectural premise is unsupported by code today (Wardline as an HTTP consumer of Clarion), and the suite's cross-tool fabric is mostly aspirational — Clarion v0.1 will be *creating* the integration surface, not *joining* it. +**Verdict**: **Design revision is required before the implementation plan.** Two assertions are factually wrong (finding-field names, Wardline group 9/12/13 declaration path), one major architectural premise is unsupported by code today (Wardline as an HTTP consumer of Loomweave), and the suite's cross-tool fabric is mostly aspirational — Loomweave v0.1 will be *creating* the integration surface, not *joining* it. --- ## 1. Executive summary -Five findings change the Clarion design's integration posture the most: +Five findings change the Loomweave design's integration posture the most: -1. **Clarion's finding wire format as described will silently drop its richest data.** Filigree's per-finding extension slot is `metadata`, not `properties`; its line field is split as `line_start`/`line_end`, not `line`; its severity vocabulary is `{critical,high,medium,low,info}`, not `{INFO,WARN,ERROR,CRITICAL}`. `WARN` and `ERROR` are coerced to `info` with a warning in the response. Every `kind`, `confidence`, `confidence_basis`, `supports`, `supported_by`, `related_entities` field Clarion wants to emit disappears silently unless nested inside `metadata`. (§2.1, §3.1) +1. **Loomweave's finding wire format as described will silently drop its richest data.** Filigree's per-finding extension slot is `metadata`, not `properties`; its line field is split as `line_start`/`line_end`, not `line`; its severity vocabulary is `{critical,high,medium,low,info}`, not `{INFO,WARN,ERROR,CRITICAL}`. `WARN` and `ERROR` are coerced to `info` with a warning in the response. Every `kind`, `confidence`, `confidence_basis`, `supports`, `supported_by`, `related_entities` field Loomweave wants to emit disappears silently unless nested inside `metadata`. (§2.1, §3.1) 2. **Wardline groups 9, 12, 13 are decorator-declared, not manifest-declared.** All 17 Wardline groups are decorator-based; the design's Revision-2 claim that these three are "declared separately in `wardline.yaml`" is wrong. Wardline already ships the canonical descriptor as `wardline.core.registry.REGISTRY` — a frozen Python `MappingProxyType` over 42 canonical decorator names plus 1 legacy alias, with a `REGISTRY_VERSION` string. The v0.2 "Wardline annotation descriptor" inversion the design defers is a YAML-dump of an existing data structure, not net-new architecture. (§2.3, §3.3) -3. **The suite's cross-tool fabric does not exist in code yet.** Wardline has zero HTTP client code in its scanner path (`grep` for `requests|urllib|httpx|aiohttp|HTTPConnection` across `src/wardline` returns zero hits; `pyproject.toml:22` declares `dependencies = []`). Wardline does not post to Filigree today. Filigree has no `wardline` references anywhere in its source. No cross-tool fixtures, mocks, contract tests, or shared schemas exist. The design's "Wardline findings flow to Filigree via Wardline's own integration; Clarion doesn't mediate" is false today: Clarion will *install* that fabric, or Wardline ships a bridge, or the findings don't flow. (§2.5, §2.6, §2.7) +3. **The suite's cross-tool fabric does not exist in code yet.** Wardline has zero HTTP client code in its scanner path (`grep` for `requests|urllib|httpx|aiohttp|HTTPConnection` across `src/wardline` returns zero hits; `pyproject.toml:22` declares `dependencies = []`). Wardline does not post to Filigree today. Filigree has no `wardline` references anywhere in its source. No cross-tool fixtures, mocks, contract tests, or shared schemas exist. The design's "Wardline findings flow to Filigree via Wardline's own integration; Loomweave doesn't mediate" is false today: Loomweave will *install* that fabric, or Wardline ships a bridge, or the findings don't flow. (§2.5, §2.6, §2.7) -4. **Filigree's file-registry displacement is not a config-flag problem, it is schema surgery.** The `registry_backend: filigree|clarion` flag and `FILIGREE_FILE_REGISTRY_DISPLACED` error code exist only in the Clarion design — zero matches in Filigree's codebase. `file_id` is already a string, but four tables (`scan_findings`, `file_associations`, `file_events`, `observations`) hold NOT-NULL foreign keys to `file_records(id)`. Three auto-create paths (`POST /api/v1/scan-results`, `observe(file_path=…)`, `trigger_scan`) insert `file_records` rows that the design's displacement story does not touch. The HTTP-route surface (11 routes in `dashboard_routes/files.py`) parallels the MCP tools the design displaces but is not mentioned. (§2.2, §3.2) +4. **Filigree's file-registry displacement is not a config-flag problem, it is schema surgery.** The `registry_backend: filigree|loomweave` flag and `FILIGREE_FILE_REGISTRY_DISPLACED` error code exist only in the Loomweave design — zero matches in Filigree's codebase. `file_id` is already a string, but four tables (`scan_findings`, `file_associations`, `file_events`, `observations`) hold NOT-NULL foreign keys to `file_records(id)`. Three auto-create paths (`POST /api/v1/scan-results`, `observe(file_path=…)`, `trigger_scan`) insert `file_records` rows that the design's displacement story does not touch. The HTTP-route surface (11 routes in `dashboard_routes/files.py`) parallels the MCP tools the design displaces but is not mentioned. (§2.2, §3.2) -5. **The design underspecifies what Clarion ingests from Wardline.** Wardline's authoritative per-function state lives in `wardline.fingerprint.json` (annotation_hash, decorators, tier context), not `wardline.yaml`. The overlay schema already declares `boundaries[]` for Groups 1 and 17. A further six project-state JSON files (`wardline.exceptions.json`, `wardline.compliance.json`, `wardline.conformance.json`, `wardline.perimeter.baseline.json`, `wardline.manifest.baseline.json`, `wardline.retrospective-required.json`) carry real Wardline state the design does not name. §2 of the design proposes ingesting `wardline.yaml + overlays` only; that is a partial view. (§2.4, §4.4) +5. **The design underspecifies what Loomweave ingests from Wardline.** Wardline's authoritative per-function state lives in `wardline.fingerprint.json` (annotation_hash, decorators, tier context), not `wardline.yaml`. The overlay schema already declares `boundaries[]` for Groups 1 and 17. A further six project-state JSON files (`wardline.exceptions.json`, `wardline.compliance.json`, `wardline.conformance.json`, `wardline.perimeter.baseline.json`, `wardline.manifest.baseline.json`, `wardline.retrospective-required.json`) carry real Wardline state the design does not name. §2 of the design proposes ingesting `wardline.yaml + overlays` only; that is a partial view. (§2.4, §4.4) The pattern across these five findings: the design reads adjacent-tool behavior optimistically. Recon shows ground truth is less finished and less uniform. Two corrections are literal edits; three are design decisions that need to be made explicit before implementation. @@ -66,7 +66,7 @@ Any top-level finding key *outside* this enumerated set is **silently dropped** **Error response** (`dashboard_routes/common.py:46-51`): `{"error": {"message": "...", "code": "VALIDATION_ERROR", "details": {}}}`, HTTP 400. -**Success response** (`types/files.py:138-148`, `ScanIngestResult`): `{files_created, files_updated, findings_created, findings_updated, new_finding_ids, observations_created, observations_failed, warnings}`. Note `warnings` — Clarion must read this, not just the count, to detect severity coercion. +**Success response** (`types/files.py:138-148`, `ScanIngestResult`): `{files_created, files_updated, findings_created, findings_updated, new_finding_ids, observations_created, observations_failed, warnings}`. Note `warnings` — Loomweave must read this, not just the count, to detect severity coercion. **`scan_source` is free-form**. No enum, no registry. `POST .../scan-results` with `scan_source="anything"` is accepted. Existing in-tree scanners use `"codex"`, `"claude"`, `"claude-code"`, `"scanner"`, `"test-scanner"` (evidence: `tests/core/test_scans.py`). @@ -99,7 +99,7 @@ Hardcoded dict literal (not pydantic-derived). Cache: `max-age=3600`. **Does NOT | `add_file_association` | `:102-118` | `_handle_add_file_association:354-384` | `file_associations`; reads `file_records`, `issues` | | `get_issue_files` | `:91-101` | `_handle_get_issue_files:339-351` | `file_associations` JOIN `file_records` JOIN `issues` | -**`file_id` is already an opaque TEXT primary key.** `db_schema.py:117` declares `id TEXT PRIMARY KEY`. Generation (`db_issues.py:178-198`): `f"{prefix}-f-{uuid.uuid4().hex[:10]}"`. Project-configurable prefix via `.filigree/config.json`. **String-for-string substitution with Clarion entity IDs is feasible at the type level.** +**`file_id` is already an opaque TEXT primary key.** `db_schema.py:117` declares `id TEXT PRIMARY KEY`. Generation (`db_issues.py:178-198`): `f"{prefix}-f-{uuid.uuid4().hex[:10]}"`. Project-configurable prefix via `.filigree/config.json`. **String-for-string substitution with Loomweave entity IDs is feasible at the type level.** **Four NOT-NULL foreign keys point at `file_records(id)`** (`db_schema.py`): @@ -110,21 +110,21 @@ Hardcoded dict literal (not pydantic-derived). Cache: `max-age=3600`. **Does NOT **Three auto-create paths the design does not address**: -1. **`POST /api/v1/scan-results`** — calls `_upsert_file_record` at `db_files.py:430-453` before inserting findings. If Clarion keeps emitting findings via HTTP (which it plans to), every POST creates a shadow `file_records` row under the Filigree-native identity scheme, not Clarion's scheme. This contradicts "Clarion owns the file registry." -2. **`create_observation(file_path=…)`** — `db_observations.py:135-147` calls `register_file` to bind the observation. Clarion's plan to emit observations to Filigree auto-populates Filigree's registry. +1. **`POST /api/v1/scan-results`** — calls `_upsert_file_record` at `db_files.py:430-453` before inserting findings. If Loomweave keeps emitting findings via HTTP (which it plans to), every POST creates a shadow `file_records` row under the Filigree-native identity scheme, not Loomweave's scheme. This contradicts "Loomweave owns the file registry." +2. **`create_observation(file_path=…)`** — `db_observations.py:135-147` calls `register_file` to bind the observation. Loomweave's plan to emit observations to Filigree auto-populates Filigree's registry. 3. **`trigger_scan` / `trigger_scan_batch`** — `mcp_tools/scanners.py:422` and `:586` call `tracker.register_file(...)` to populate `scan_runs.file_ids`. -**`get_issue` default-embeds `get_issue_files`** (`mcp_tools/issues.py:368-370`, `include_files=True` by default). Under Clarion mode, every `get_issue` call surfaces "retained but opaque" `file_id`s that agents reading the default response see unexpectedly. The design's "retained" framing understates the user-visible leak. +**`get_issue` default-embeds `get_issue_files`** (`mcp_tools/issues.py:368-370`, `include_files=True` by default). Under Loomweave mode, every `get_issue` call surfaces "retained but opaque" `file_id`s that agents reading the default response see unexpectedly. The design's "retained" framing understates the user-visible leak. -**HTTP route surface parallels MCP tools**. `dashboard_routes/files.py` has 11 routes (`GET /files`, `GET /files/{file_id}`, `GET /files/{file_id}/findings`, `GET /files/{file_id}/timeline`, `POST /files/{file_id}/associations`, `GET /files/hotspots`, `GET /files/stats`, `GET /scan-runs`, `GET /files/_schema`, `PATCH /files/{file_id}/findings/{finding_id}`, `POST /v1/scan-results`). The design says MCP tools are removed for Clarion projects but is silent on the HTTP routes that reach the same data. The dashboard UI uses HTTP. +**HTTP route surface parallels MCP tools**. `dashboard_routes/files.py` has 11 routes (`GET /files`, `GET /files/{file_id}`, `GET /files/{file_id}/findings`, `GET /files/{file_id}/timeline`, `POST /files/{file_id}/associations`, `GET /files/hotspots`, `GET /files/stats`, `GET /scan-runs`, `GET /files/_schema`, `PATCH /files/{file_id}/findings/{finding_id}`, `POST /v1/scan-results`). The design says MCP tools are removed for Loomweave projects but is silent on the HTTP routes that reach the same data. The dashboard UI uses HTTP. **`registry_backend` flag and `FILIGREE_FILE_REGISTRY_DISPLACED` error code do not exist**. `grep` across `/home/john/filigree` returns zero matches for each. Both are proposed net-new additions. **Surgery estimate for pluggable registry**: ~5–8 hot files (`db_files.py`, `mcp_tools/files.py`, `mcp_tools/scanners.py`, `mcp_tools/issues.py`, `dashboard_routes/files.py`, `dashboard_routes/issues.py`, `db_observations.py`, plus `db_schema.py`/`migrations.py` for FK rework). ~17 test files reference `file_id` directly (hundreds of call sites). There is no `FileRegistryProtocol` today; `FilesMixin` is composed into the monolithic `FiligreeDB` class via MRO (`core.py:344`). The design's "config flag" framing underbills the work. -**Zero existing cross-project callers**. Neither Wardline nor any Clarion code calls the displaced MCP tools. Blast radius is entirely Filigree-internal (tests, dashboard, `get_issue` default-embed, scanner trigger's `scan_runs.file_ids` column). +**Zero existing cross-project callers**. Neither Wardline nor any Loomweave code calls the displaced MCP tools. Blast radius is entirely Filigree-internal (tests, dashboard, `get_issue` default-embed, scanner trigger's `scan_runs.file_ids` column). -**JSONL migration substrate exists**. `db_meta.py:524-536` exports/imports `file_record`, `scan_finding`, `file_association`, `file_event` record types. `_resolve_imported_file_id` at `db_meta.py:466-513` handles ID/path conflict resolution. Clarion's backfill command (`clarion migrate filigree-files`) has a natural entry point here. +**JSONL migration substrate exists**. `db_meta.py:524-536` exports/imports `file_record`, `scan_finding`, `file_association`, `file_event` record types. `_resolve_imported_file_id` at `db_meta.py:466-513` handles ID/path conflict resolution. Loomweave's backfill command (`loomweave migrate filigree-files`) has a natural entry point here. ### 2.3 Wardline's real annotation vocabulary (Question 3) @@ -152,15 +152,15 @@ Hardcoded dict literal (not pydantic-derived). Cache: `max-age=3600`. **Does NOT - **Group 16 (Generic Trust Boundary, 2 + 1 alias)**: `trust_boundary` (dual-form), `data_flow` (factory), `tier_transition` (legacy alias → `trust_boundary`) — `boundaries.py:74,135,108` - **Group 17 (Restoration Boundaries, 1)**: `restoration_boundary` — factory, `restoration.py:30` -**Overlay schema declares `boundaries[]` for Groups 1 and 17** (`overlay.schema.json:16-168`). These supplement source-level decorators for those two groups — a manifest-declaration path the design partly captures under "Clarion ingests `wardline.yaml` + overlays" but does not tie to specific groups. +**Overlay schema declares `boundaries[]` for Groups 1 and 17** (`overlay.schema.json:16-168`). These supplement source-level decorators for those two groups — a manifest-declaration path the design partly captures under "Loomweave ingests `wardline.yaml` + overlays" but does not tie to specific groups. **Existing descriptor file**: **Yes — `core/registry.py:REGISTRY` is the catalog.** Pure data (`MappingProxyType` with frozen `RegistryEntry` rows). `REGISTRY_VERSION` string at `registry.py:10`. No YAML export exists today, but dumping `REGISTRY` to YAML is trivial. The v0.2 "annotation descriptor" inversion is an export step, not net-new architecture. -**Wardline's own scanner does NOT handle class decorators** — `scanner/discovery.py:_walk_functions` only visits `FunctionDef`/`AsyncFunctionDef`. Clarion's design promises "Class decorator (`@register\nclass C:`) — Recorded identically to function decorators", which is **more permissive than Wardline itself**. Clarion should clarify that class-decoration data beyond what Wardline emits is Clarion-side augmentation, not Wardline-authoritative. +**Wardline's own scanner does NOT handle class decorators** — `scanner/discovery.py:_walk_functions` only visits `FunctionDef`/`AsyncFunctionDef`. Loomweave's design promises "Class decorator (`@register\nclass C:`) — Recorded identically to function decorators", which is **more permissive than Wardline itself**. Loomweave should clarify that class-decoration data beyond what Wardline emits is Loomweave-side augmentation, not Wardline-authoritative. -**Detection cases Wardline handles**: direct named, factory, stacked, aliased (`alias.asname`), dotted single-level (`@wardline.x`). **Does NOT handle**: arbitrary dotted chains (`a.b.c`), star imports (refuses with warning at `discovery.py:157-159`), dynamic imports (warns at `:215-274`), metaclass-based decoration, lambdas/subscripts as decorators. Clarion's policy table should state it matches or exceeds Wardline's handling, and name the non-handled cases as shared blind spots. +**Detection cases Wardline handles**: direct named, factory, stacked, aliased (`alias.asname`), dotted single-level (`@wardline.x`). **Does NOT handle**: arbitrary dotted chains (`a.b.c`), star imports (refuses with warning at `discovery.py:157-159`), dynamic imports (warns at `:215-274`), metaclass-based decoration, lambdas/subscripts as decorators. Loomweave's policy table should state it matches or exceeds Wardline's handling, and name the non-handled cases as shared blind spots. -**`tier_transition` is a legacy alias of `trust_boundary`** via `LEGACY_DECORATOR_ALIASES` (`registry.py:12-14`). Clarion keyed only on source-level name misses this identity. The `LEGACY_DECORATOR_ALIASES` table is the sanctioned extension point. +**`tier_transition` is a legacy alias of `trust_boundary`** via `LEGACY_DECORATOR_ALIASES` (`registry.py:12-14`). Loomweave keyed only on source-level name misses this identity. The `LEGACY_DECORATOR_ALIASES` table is the sanctioned extension point. ### 2.4 Wardline's SARIF emitter (Question 4) @@ -191,10 +191,10 @@ Hardcoded dict literal (not pydantic-derived). Cache: `max-age=3600`. **Does NOT **Tier vocabulary**: `INTEGRAL / ASSURED / GUARDED / EXTERNAL_RAW` (wardline.yaml configurable). **This does NOT match the design's glossary** which names tiers as `T1 (trusted assertion) / T2 (semantically validated) / T3 (guarded) / T4 (raw external) plus UNKNOWN / MIXED`. Either Wardline's names or the design's names are correct; they are not both. Design correction needed. -**What Wardline would gain or lose by pulling from Clarion**: +**What Wardline would gain or lose by pulling from Loomweave**: -- **Gain**: AST/decorator-discovery deduplication; consistency of tier reporting between Clarion and Wardline; potentially faster incremental runs. -- **Lose**: ability to run without Clarion (currently zero HTTP dependencies, `dependencies = []` in `pyproject.toml:22`); ability to reason about work-in-progress code pre-commit; deterministic `inputHash` identity (today `_compute_input_hash` reads file bytes directly at `scan.py:103-151`). +- **Gain**: AST/decorator-discovery deduplication; consistency of tier reporting between Loomweave and Wardline; potentially faster incremental runs. +- **Lose**: ability to run without Loomweave (currently zero HTTP dependencies, `dependencies = []` in `pyproject.toml:22`); ability to reason about work-in-progress code pre-commit; deterministic `inputHash` identity (today `_compute_input_hash` reads file bytes directly at `scan.py:103-151`). - **Magnitude of refactor**: significant. `ScanEngine`, `ScanContext`, `ProjectIndex`, and every rule under `scanner/rules/` bind directly to in-memory AST structures — no `EntitySource` protocol exists. Honest label: "non-trivial architectural change." ### 2.5 Cross-tool data model overlaps (Question 5) @@ -203,11 +203,11 @@ Hardcoded dict literal (not pydantic-derived). Cache: `max-age=3600`. **Does NOT **Wardline owns (today)**: per-function `FingerprintEntry{qualified_name, module, decorators, annotation_hash, tier_context}` persisted in `wardline.fingerprint.json`; per-exception `ExceptionEntry` in `wardline.exceptions.json`; manifest-declared `BoundaryEntry{function, transition, from_tier, to_tier, restored_tier}` from overlays; compliance ledger in `wardline.compliance.json`; conformance gates in `wardline.conformance.json`; SARIF baseline in `wardline.sarif.baseline.json`; retrospective-required state marker. **No SQLite anywhere** (`grep -r "CREATE TABLE" /home/john/wardline/src` returns 0). -**Clarion design owns**: entities, edges, findings, guidance sheets, file records, subsystems, summaries. +**Loomweave design owns**: entities, edges, findings, guidance sheets, file records, subsystems, summaries. **Overlaps**: -| Data point | Filigree | Wardline | Clarion design | +| Data point | Filigree | Wardline | Loomweave design | |---|---|---|---| | File path | `file_records.path` | `artifactLocation.uri` (transient) | File entity `core:file:{hash}@{path}` | | File language | `file_records.language` | Computed per-scan | Plugin-emitted file-level tag | @@ -217,14 +217,14 @@ Hardcoded dict literal (not pydantic-derived). Cache: `max-age=3600`. **Does NOT | Content hash | — | — | Blake3 `content_hash` on entities | | Annotation hash | — | `FingerprintEntry.annotation_hash` (SHA-256 over `ast.dump`) | Not named as such | | Finding severity | `{critical,high,medium,low,info}` | `{ERROR,WARNING,SUPPRESS}` | `{INFO,WARN,ERROR,CRITICAL}` for defects, `NONE` for facts | -| Finding rule_id | free-text string | `PY-WL-*`, `SCN-*`, `SUP-*`, `TOOL-ERROR` enum | Namespaced `CLA-*`, `WL-*`, `COV-*` | +| Finding rule_id | free-text string | `PY-WL-*`, `SCN-*`, `SUP-*`, `TOOL-ERROR` enum | Namespaced `LMWV-*`, `WL-*`, `COV-*` | | Finding status | `{open,acknowledged,fixed,false_positive,unseen_in_latest}` | Carried via `result.suppressions[]` + `wardline.excepted` | `{open,acknowledged,suppressed,promoted_to_issue}` | -**Three distinct severity vocabularies across the suite** with no translation table in code. **Three distinct rule-ID namespace conventions.** **Two distinct content-hash schemes** (Wardline SHA-256 over `ast.dump`, Clarion Blake3 over content). All of these silently diverge. +**Three distinct severity vocabularies across the suite** with no translation table in code. **Three distinct rule-ID namespace conventions.** **Two distinct content-hash schemes** (Wardline SHA-256 over `ast.dump`, Loomweave Blake3 over content). All of these silently diverge. **The suite currently deduplicates nowhere**. Filigree's `file_records` is its authoritative file list; Wardline's `FingerprintEntry` is its authoritative function list; they do not cross-reference. -**Where the suite accepts drift**: the `wardline.exceptions.json` register uses a free-text `location` string like `src/wardline/scanner/engine.py::ScanEngine._scan_file` (double-colon separated). This is a third identity scheme, different from Wardline's own `qualname` and from Clarion's planned ID. +**Where the suite accepts drift**: the `wardline.exceptions.json` register uses a free-text `location` string like `src/wardline/scanner/engine.py::ScanEngine._scan_file` (double-colon separated). This is a third identity scheme, different from Wardline's own `qualname` and from Loomweave's planned ID. ### 2.6 Undocumented information flows (Question 6) @@ -235,7 +235,7 @@ Hardcoded dict literal (not pydantic-derived). Cache: `max-age=3600`. **Does NOT - `pyproject.toml:22` declares `dependencies = []`. - CI (`.github/workflows/ci.yml:68-75`) uploads SARIF to **GitHub Security** via `github/codeql-action/upload-sarif@v3`, not to Filigree. -**The Clarion design's "Wardline findings flow to Filigree via Wardline's own integration; Clarion doesn't mediate" is false today.** If this flow is required by v0.1 acceptance, someone builds it. +**The Loomweave design's "Wardline findings flow to Filigree via Wardline's own integration; Loomweave doesn't mediate" is false today.** If this flow is required by v0.1 acceptance, someone builds it. **Filigree → Wardline**: none. Zero Filigree references in Wardline source beyond boilerplate `CLAUDE.md`/`AGENTS.md`. Filigree does not read `wardline.yaml`, `wardline.fingerprint.json`, or SARIF output. @@ -243,15 +243,15 @@ Hardcoded dict literal (not pydantic-derived). Cache: `max-age=3600`. **Does NOT - Filigree: one call in `install_support/doctor.py:620-650` running `git status --porcelain` for a dirty-tree warning. **No persistence of git data.** - Wardline: `bar/evidence_exec.py` (`git rev-parse:66`, `git show:76`, `git log:224-230`, `git archive:313`) and `bar/runner.py:487` (`git diff-tree`). Used transiently for BAR evidence bundles. **Not persisted into scanner graph.** -- Clarion's planned git ingest (last_modified, churn_count, authors) duplicates nothing persistent today. Wardline's BAR subprocess layer is a potential reuse substrate but requires coordination. +- Loomweave's planned git ingest (last_modified, churn_count, authors) duplicates nothing persistent today. Wardline's BAR subprocess layer is a potential reuse substrate but requires coordination. -**Observations are MCP-only in Filigree**. No HTTP endpoint exists for observation creation (`grep` confirms no route under `dashboard_routes/` for observations beyond list/get). The design repeatedly says "Clarion emits observations to Filigree" but is silent on the transport — Clarion must ship an MCP client that speaks `filigree.mcp`, or Filigree must add an `/api/v1/observations` HTTP route. +**Observations are MCP-only in Filigree**. No HTTP endpoint exists for observation creation (`grep` confirms no route under `dashboard_routes/` for observations beyond list/get). The design repeatedly says "Loomweave emits observations to Filigree" but is silent on the transport — Loomweave must ship an MCP client that speaks `filigree.mcp`, or Filigree must add an `/api/v1/observations` HTTP route. -**Filigree's `.filigree/scanners/*.toml` extensibility surface** (`src/filigree/scanners.py`): Filigree-side `trigger_scan` launches registered scanners by name. If human operators should be able to trigger-scan Clarion from the Filigree dashboard, Clarion ships a `clarion.toml` scanner registration. The design does not name this. +**Filigree's `.filigree/scanners/*.toml` extensibility surface** (`src/filigree/scanners.py`): Filigree-side `trigger_scan` launches registered scanners by name. If human operators should be able to trigger-scan Loomweave from the Filigree dashboard, Loomweave ships a `loomweave.toml` scanner registration. The design does not name this. -**Commit-ref handling**: Wardline SARIF includes `wardline.commitRef` with `-dirty` suffix when tree is modified (real example: `f71ac90aac6df328bf94325cecab59bd92f72f51-dirty`). Clarion's "commit cadence" vocabulary does not specify whether it handles dirty trees, requires clean commits, or accepts either. +**Commit-ref handling**: Wardline SARIF includes `wardline.commitRef` with `-dirty` suffix when tree is modified (real example: `f71ac90aac6df328bf94325cecab59bd92f72f51-dirty`). Loomweave's "commit cadence" vocabulary does not specify whether it handles dirty trees, requires clean commits, or accepts either. -**Filigree's `register_file` side channel in `get_issue`**: `include_files=True` default at `mcp_tools/issues.py:368-370` means every `get_issue` call returns file associations. Under `registry_backend: clarion`, those file_ids are opaque Clarion entity IDs seen by agents reading the default response — a cross-tool flow the design does not acknowledge. +**Filigree's `register_file` side channel in `get_issue`**: `include_files=True` default at `mcp_tools/issues.py:368-370` means every `get_issue` call returns file associations. Under `registry_backend: loomweave`, those file_ids are opaque Loomweave entity IDs seen by agents reading the default response — a cross-tool flow the design does not acknowledge. **`AGENTS.md` / ADR cross-references**: Wardline ADRs reference specific Filigree issue IDs by string (e.g., `wardline-63255c8d5a` in `ADR-006-sarif-suppress-as-native-suppression.md:280-281`). This is a documented-but-not-programmatic cross-tool coordination path. The design's glossary covers `filigree_issue_id` on Findings but not this authoring-time reference convention. @@ -267,19 +267,19 @@ Hardcoded dict literal (not pydantic-derived). Cache: `max-age=3600`. **Does NOT **Filigree's canonical test scanner is Codex**, not Wardline. `scan_source="codex"` appears 28+ times across `tests/core/test_scans.py`. -**What would need to exist** to test Clarion ↔ Filigree ↔ Wardline end-to-end: +**What would need to exist** to test Loomweave ↔ Filigree ↔ Wardline end-to-end: -1. **A mock Filigree HTTP server** (acceptance criterion 12.8.1 already lists "A wardline mock client successfully consumes the HTTP read API" as a Clarion requirement — its inverse, "a mock Filigree successfully accepts Clarion POSTs," is not named). -2. **A Wardline SARIF corpus** — at least one file per annotation group, one per rule, for Clarion's plugin detection tests. -3. **A Filigree scan-results reference payload** — Clarion's emitter must round-trip through the real validation. Record `scripts/scan_utils.py:post_to_api` as a dependency, or inline equivalent test harness. -4. **A schema-compatibility contract test** — Filigree's `GET /api/files/_schema` should be pinned by Clarion's integration test (protocol drift detection). -5. **A Wardline `REGISTRY_VERSION` pin** — Clarion plugin detects version skew and emits `CLA-INFRA-WARDLINE-REGISTRY-STALE` (new rule). +1. **A mock Filigree HTTP server** (acceptance criterion 12.8.1 already lists "A wardline mock client successfully consumes the HTTP read API" as a Loomweave requirement — its inverse, "a mock Filigree successfully accepts Loomweave POSTs," is not named). +2. **A Wardline SARIF corpus** — at least one file per annotation group, one per rule, for Loomweave's plugin detection tests. +3. **A Filigree scan-results reference payload** — Loomweave's emitter must round-trip through the real validation. Record `scripts/scan_utils.py:post_to_api` as a dependency, or inline equivalent test harness. +4. **A schema-compatibility contract test** — Filigree's `GET /api/files/_schema` should be pinned by Loomweave's integration test (protocol drift detection). +5. **A Wardline `REGISTRY_VERSION` pin** — Loomweave plugin detects version skew and emits `LMWV-INFRA-WARDLINE-REGISTRY-STALE` (new rule). -None of these exist. Clarion v0.1 creates all of them. +None of these exist. Loomweave v0.1 creates all of them. --- -## 3. Corrections required to the Clarion design +## 3. Corrections required to the Loomweave design Each item quotes the design text, cites reality, and recommends an edit. Ordered by severity. @@ -287,7 +287,7 @@ Each item quotes the design text, cites reality, and recommends an edit. Ordered **Design text** (§3, line ~442; §9 line ~1487; Glossary line ~1757): > the wire format between tools is `{scan_source, findings: [{path, line?, rule_id, severity, message, properties?}]}` -> Clarion's richer fields … travel as fields inside a `properties` bag on each finding +> Loomweave's richer fields … travel as fields inside a `properties` bag on each finding **Reality**: Filigree's extension slot is `metadata`, not `properties`. The line field is `line_start` + `line_end`, not `line`. Top-level keys outside the known set are silently dropped (`db_files.py:501-525`). Evidence: `_validate_scan_findings` at `db_files.py:386-428`, `_upsert_finding` at `:455-556`. @@ -306,8 +306,8 @@ Each item quotes the design text, cites reality, and recommends an edit. Ordered **Recommended edit**: -- Define a severity mapping table in §3 or §9: `{INFO→info, WARN→medium, ERROR→high, CRITICAL→critical, NONE→info (with kind=fact in metadata)}` (or similar). Explicitly state that emissions use Filigree's wire vocabulary, not Clarion's internal type names. -- Add that Clarion must inspect `response.warnings[]` to detect severity coercion. +- Define a severity mapping table in §3 or §9: `{INFO→info, WARN→medium, ERROR→high, CRITICAL→critical, NONE→info (with kind=fact in metadata)}` (or similar). Explicitly state that emissions use Filigree's wire vocabulary, not Loomweave's internal type names. +- Add that Loomweave must inspect `response.warnings[]` to detect severity coercion. ### 3.3 Wardline groups 9/12/13 manifest-declaration (factual, CRITICAL) @@ -334,46 +334,46 @@ Each item quotes the design text, cites reality, and recommends an edit. Ordered ### 3.5 Wardline findings flow to Filigree today (factual, HIGH) **Design text** (§9 Wardline integration table): -> Wardline enforcer → Filigree | Wardline's own integration | Wardline findings flow to filigree; Clarion doesn't mediate +> Wardline enforcer → Filigree | Wardline's own integration | Wardline findings flow to filigree; Loomweave doesn't mediate **Reality**: Wardline has zero HTTP client code in its scanner path. Zero dependencies in `pyproject.toml:22`. Zero Filigree references. The CI pipeline uploads SARIF to **GitHub Security**, not Filigree. **This integration does not exist today.** -**Recommended edit**: Either (a) state explicitly that a Wardline→Filigree bridge is a precondition not yet satisfied, and list its owner (Wardline? Clarion? a separate adapter?); or (b) drop the claim and state that Wardline findings do not reach Filigree in v0.1 except via a yet-to-be-built translator. +**Recommended edit**: Either (a) state explicitly that a Wardline→Filigree bridge is a precondition not yet satisfied, and list its owner (Wardline? Loomweave? a separate adapter?); or (b) drop the claim and state that Wardline findings do not reach Filigree in v0.1 except via a yet-to-be-built translator. ### 3.6 Registry-backend flag does not exist (status clarity, HIGH) **Design text** (§9): -> Filigree ships a `registry_backend: filigree | clarion` config flag. +> Filigree ships a `registry_backend: filigree | loomweave` config flag. **Reality**: `grep` for `registry_backend` across `/home/john/filigree` returns zero matches. This is a prerequisite not yet satisfied. Four NOT-NULL FK constraints in `db_schema.py` (at `:131`, `:161`, `:198`, `:214`) all point at `file_records(id)`. Three auto-create paths insert rows regardless of backend. Surgery estimate: ~5–8 hot files plus SQLite FK rework. -**Recommended edit**: Reframe §9 to state that `registry_backend` is a Filigree-side change Clarion *depends on*, not a Filigree feature it *uses*. Add a concrete Filigree-side work item (design + implementation) as an explicit dependency in the implementation plan. Name the three auto-create paths that must be routed through the pluggable backend. +**Recommended edit**: Reframe §9 to state that `registry_backend` is a Filigree-side change Loomweave *depends on*, not a Filigree feature it *uses*. Add a concrete Filigree-side work item (design + implementation) as an explicit dependency in the implementation plan. Name the three auto-create paths that must be routed through the pluggable backend. ### 3.7 MCP-only observation channel (status clarity, MEDIUM) **Design text** (§9, §8): -> Clarion emits observations to Filigree. +> Loomweave emits observations to Filigree. -**Reality**: Filigree's observation API is MCP-only. No HTTP endpoint exists. `mcp_tools/observations.py` is the only creation path. Clarion must ship an MCP client (stdio transport to Filigree's MCP server) to emit observations. +**Reality**: Filigree's observation API is MCP-only. No HTTP endpoint exists. `mcp_tools/observations.py` is the only creation path. Loomweave must ship an MCP client (stdio transport to Filigree's MCP server) to emit observations. -**Recommended edit**: §8 or §9 must state that Clarion ships an MCP client for Filigree (bidirectional: Clarion uses Filigree's MCP tools for observation emission, Clarion's own MCP server is a separate instance for agent consumption). Name the MCP transport (stdio subprocess of `filigree mcp` vs TCP) and auth posture. +**Recommended edit**: §8 or §9 must state that Loomweave ships an MCP client for Filigree (bidirectional: Loomweave uses Filigree's MCP tools for observation emission, Loomweave's own MCP server is a separate instance for agent consumption). Name the MCP transport (stdio subprocess of `filigree mcp` vs TCP) and auth posture. ### 3.8 HTTP routes parallel to displaced MCP tools (completeness, MEDIUM) **Design text** (§9): -> Removed (for projects using Clarion): `register_file`, `list_files`, `get_file`, `get_file_timeline` MCP tools. +> Removed (for projects using Loomweave): `register_file`, `list_files`, `get_file`, `get_file_timeline` MCP tools. **Reality**: `dashboard_routes/files.py` exposes 11 HTTP routes that parallel these MCP tools, including `GET /files`, `GET /files/{id}`, `GET /files/{id}/timeline`, `GET /files/hotspots`, `GET /files/stats`. The dashboard UI uses these. Displacement at the MCP layer without HTTP-layer action creates silent inconsistency. -**Recommended edit**: Either (a) state that the HTTP routes are also displaced/re-routed under `registry_backend: clarion`, or (b) state explicitly that MCP displacement is cosmetic and the dashboard continues to show Filigree-native file records. Pick one. +**Recommended edit**: Either (a) state that the HTTP routes are also displaced/re-routed under `registry_backend: loomweave`, or (b) state explicitly that MCP displacement is cosmetic and the dashboard continues to show Filigree-native file records. Pick one. ### 3.9 `get_issue` default-embed leak (completeness, MEDIUM) **Design text** (§9): "retained with opaque entity IDs." -**Reality**: `mcp_tools/issues.py:368-370` has `include_files=True` by default; every `get_issue` call returns file associations via `get_issue_files`. Under `registry_backend: clarion`, agents reading the default issue response suddenly see opaque entity IDs in a field they did not request. +**Reality**: `mcp_tools/issues.py:368-370` has `include_files=True` by default; every `get_issue` call returns file associations via `get_issue_files`. Under `registry_backend: loomweave`, agents reading the default issue response suddenly see opaque entity IDs in a field they did not request. -**Recommended edit**: Note this in §9: "Because `get_issue_files` is default-embedded by `get_issue`, the 'retained but opaque' framing applies to any agent reading an issue. Clarion must document that `file_id` on issue-detail responses is a Clarion entity ID string, not a Filigree-native ID." +**Recommended edit**: Note this in §9: "Because `get_issue_files` is default-embedded by `get_issue`, the 'retained but opaque' framing applies to any agent reading an issue. Loomweave must document that `file_id` on issue-detail responses is a Loomweave entity ID string, not a Filigree-native ID." ### 3.10 `_schema` does not enumerate scan_source (factual, LOW) @@ -391,13 +391,13 @@ These are not corrections to existing text; they are missing sections or decisio ### 4.1 Wardline SARIF `wardline.*` property-bag translation layer -Wardline's SARIF output carries 35 run-level and 9 result-level `wardline.*` extension keys (`wardline-sarif-extensions.schema.json`). Examples: `wardline.controlLaw` (states: `normal|alternate|direct`), `wardline.taintState`, `wardline.excepted`, `wardline.enclosingTier`, `wardline.governanceEvents`. Any bridge that translates Wardline SARIF into Filigree's flat intake either preserves these into `metadata` (Clarion must standardise the nesting) or loses fidelity. +Wardline's SARIF output carries 35 run-level and 9 result-level `wardline.*` extension keys (`wardline-sarif-extensions.schema.json`). Examples: `wardline.controlLaw` (states: `normal|alternate|direct`), `wardline.taintState`, `wardline.excepted`, `wardline.enclosingTier`, `wardline.governanceEvents`. Any bridge that translates Wardline SARIF into Filigree's flat intake either preserves these into `metadata` (Loomweave must standardise the nesting) or loses fidelity. **Design action**: name who owns the SARIF→Filigree translator, where it lives, and whether the `wardline.*` keys are preserved in `metadata.wardline_properties.*` or flattened. ### 4.2 Severity and rule-ID mapping tables -Three severity vocabularies, three rule-ID namespace conventions, zero translation tables in code. Design §3 names "CLA-PY-001 | WL-001 | COV-001" but does not define how Wardline's `PY-WL-001-GOVERNED-DEFAULT` (hyphenated, longer) round-trips through Filigree's free-text `rule_id` column while remaining parseable back to the original. +Three severity vocabularies, three rule-ID namespace conventions, zero translation tables in code. Design §3 names "LMWV-PY-001 | WL-001 | COV-001" but does not define how Wardline's `PY-WL-001-GOVERNED-DEFAULT` (hyphenated, longer) round-trips through Filigree's free-text `rule_id` column while remaining parseable back to the original. **Design action**: publish a severity-mapping matrix and a rule-ID round-trip policy in §9. @@ -405,7 +405,7 @@ Three severity vocabularies, three rule-ID namespace conventions, zero translati Beyond `wardline.yaml`, Wardline persists authoritative state in: -- `wardline.fingerprint.json` — per-function `{qualified_name, module, decorators, annotation_hash, tier_context}`. **Most aligned with Clarion's entity model.** +- `wardline.fingerprint.json` — per-function `{qualified_name, module, decorators, annotation_hash, tier_context}`. **Most aligned with Loomweave's entity model.** - `wardline.exceptions.json` — exception register with expiry. - `wardline.compliance.json` — compliance ledger. - `wardline.conformance.json` — conformance gates. @@ -413,27 +413,27 @@ Beyond `wardline.yaml`, Wardline persists authoritative state in: - `wardline.manifest.baseline.json` — manifest fingerprint. - `wardline.retrospective-required.json` — retrospective state marker. -§2 says "Clarion ingests `wardline.yaml` + overlays." **Omitting `wardline.fingerprint.json` means Clarion's Wardline-derived guidance misses the computed per-function state.** Omitting the exceptions register means Clarion does not know which entities have active excepted findings. +§2 says "Loomweave ingests `wardline.yaml` + overlays." **Omitting `wardline.fingerprint.json` means Loomweave's Wardline-derived guidance misses the computed per-function state.** Omitting the exceptions register means Loomweave does not know which entities have active excepted findings. -**Design action**: §2 and §7 should name which of these files Clarion ingests. At minimum, `wardline.fingerprint.json` should be in scope; overlays for boundaries[] (groups 1 and 17) are already in scope if "overlays" means the overlay files in general. The other five need an explicit in/out decision. +**Design action**: §2 and §7 should name which of these files Loomweave ingests. At minimum, `wardline.fingerprint.json` should be in scope; overlays for boundaries[] (groups 1 and 17) are already in scope if "overlays" means the overlay files in general. The other five need an explicit in/out decision. ### 4.4 `REGISTRY_VERSION` compatibility mechanism -Wardline's `core/registry.py:10` carries a `REGISTRY_VERSION` string. Clarion's mirror (or its direct import) can detect version skew. The design's v0.2 "Wardline annotation descriptor" does not name this. Today, a new Wardline annotation requires a Clarion plugin release; a version-skew detector would at least emit `CLA-INFRA-WARDLINE-REGISTRY-STALE` when Wardline bumps and Clarion lags. +Wardline's `core/registry.py:10` carries a `REGISTRY_VERSION` string. Loomweave's mirror (or its direct import) can detect version skew. The design's v0.2 "Wardline annotation descriptor" does not name this. Today, a new Wardline annotation requires a Loomweave plugin release; a version-skew detector would at least emit `LMWV-INFRA-WARDLINE-REGISTRY-STALE` when Wardline bumps and Loomweave lags. -**Design action**: specify version-skew detection as a v0.1 mechanism, independent of whether the annotation descriptor is a v0.2 file. Clarion reads `wardline.core.registry.REGISTRY_VERSION` at plugin startup (direct Python import is feasible; `REGISTRY` is a `MappingProxyType` with no heavy deps) and emits a finding on mismatch. +**Design action**: specify version-skew detection as a v0.1 mechanism, independent of whether the annotation descriptor is a v0.2 file. Loomweave reads `wardline.core.registry.REGISTRY_VERSION` at plugin startup (direct Python import is feasible; `REGISTRY` is a `MappingProxyType` with no heavy deps) and emits a finding on mismatch. ### 4.5 Wardline's legacy decorator aliases -`LEGACY_DECORATOR_ALIASES = MappingProxyType({"tier_transition": "trust_boundary"})` at `core/registry.py:12-14`. Clarion's plugin keyed only on source-level name would double-count. Wardline already resolves via this table. +`LEGACY_DECORATOR_ALIASES = MappingProxyType({"tier_transition": "trust_boundary"})` at `core/registry.py:12-14`. Loomweave's plugin keyed only on source-level name would double-count. Wardline already resolves via this table. **Design action**: §2 Python plugin specifics table on decorator detection should add a row: "Legacy aliases (`tier_transition`) — resolved via Wardline's `LEGACY_DECORATOR_ALIASES`; canonical name recorded in `decorated_by.properties.canonical_name`." ### 4.6 Class decoration goes beyond what Wardline itself detects -Wardline's `scanner/discovery.py:_walk_functions` only visits `FunctionDef`/`AsyncFunctionDef`. Class-level `@validates_shape` is ignored by Wardline's own scanner. Clarion's design promises to detect class decorators "identically to function decorators." This is a policy decision — Clarion goes beyond Wardline's own coverage. +Wardline's `scanner/discovery.py:_walk_functions` only visits `FunctionDef`/`AsyncFunctionDef`. Class-level `@validates_shape` is ignored by Wardline's own scanner. Loomweave's design promises to detect class decorators "identically to function decorators." This is a policy decision — Loomweave goes beyond Wardline's own coverage. -**Design action**: §2 should note that class-level decoration is Clarion-side augmentation, not Wardline-authoritative. Wardline-side rules do not enforce class-level annotations. Clarion's emitted findings for class-level decoration must carry a `confidence_basis: "clarion_augmentation"` tag distinguishing them from Wardline-authoritative claims. +**Design action**: §2 should note that class-level decoration is Loomweave-side augmentation, not Wardline-authoritative. Wardline-side rules do not enforce class-level annotations. Loomweave's emitted findings for class-level decoration must carry a `confidence_basis: "loomweave_augmentation"` tag distinguishing them from Wardline-authoritative claims. ### 4.7 Auto-create paths in Filigree @@ -443,63 +443,63 @@ Three auto-create paths in Filigree insert `file_records` rows regardless of sou 2. `create_observation(file_path=…)` → `register_file` call (`db_observations.py:135-147`). 3. `trigger_scan` → `tracker.register_file(...)` (`mcp_tools/scanners.py:422, 586`). -Under `registry_backend: clarion`, these paths either continue to write `file_records` (now a shadow table) or must route through a new `RegistryProtocol`. The design's "displace the registry" story elides all three. +Under `registry_backend: loomweave`, these paths either continue to write `file_records` (now a shadow table) or must route through a new `RegistryProtocol`. The design's "displace the registry" story elides all three. -**Design action**: §9 must name each auto-create path and specify the behaviour under displacement. Preferred: keep `file_records` as a lightweight shadow index (path → opaque_id mapping) and let Clarion be authoritative; do not claim to "remove" the Filigree table. +**Design action**: §9 must name each auto-create path and specify the behaviour under displacement. Preferred: keep `file_records` as a lightweight shadow index (path → opaque_id mapping) and let Loomweave be authoritative; do not claim to "remove" the Filigree table. ### 4.8 Observation transport (MCP vs HTTP) -Filigree's observation API is MCP-only. Clarion's "emits observations to Filigree" claim implies an MCP client on Clarion's side. +Filigree's observation API is MCP-only. Loomweave's "emits observations to Filigree" claim implies an MCP client on Loomweave's side. -**Design action**: name the MCP transport (Clarion spawns `filigree mcp` as a subprocess? Clarion uses an existing MCP host?), the auth posture (does Filigree's MCP server need a token?), and the failure mode (what happens when Filigree's MCP is unreachable during `clarion analyze`?). +**Design action**: name the MCP transport (Loomweave spawns `filigree mcp` as a subprocess? Loomweave uses an existing MCP host?), the auth posture (does Filigree's MCP server need a token?), and the failure mode (what happens when Filigree's MCP is unreachable during `loomweave analyze`?). ### 4.9 Scanner registration for operator-triggered scans -Filigree's `.filigree/scanners/*.toml` extensibility lets the dashboard `trigger_scan` a named scanner. If a user wants to trigger Clarion from the Filigree dashboard — a natural affordance given the shared MCP/observation model — Clarion must register a `clarion.toml`. +Filigree's `.filigree/scanners/*.toml` extensibility lets the dashboard `trigger_scan` a named scanner. If a user wants to trigger Loomweave from the Filigree dashboard — a natural affordance given the shared MCP/observation model — Loomweave must register a `loomweave.toml`. **Design action**: decide whether operator-triggered scans via Filigree's dashboard are in v0.1 scope. If yes, specify the TOML contents (executable, args, output-path, scan_source identifier). If no, note it as explicitly deferred so the absence isn't read as an oversight. ### 4.10 `scan_run_id` lifecycle -Filigree tracks `scan_runs` rows with state transitions. `POST /api/v1/scan-results` with `scan_run_id` set AND `complete_scan_run=true` attempts to close the run; unknown `scan_run_id` logs a warning and continues. Clarion's `run_id` (§3 Finding.source.run_id) is a separate concept. +Filigree tracks `scan_runs` rows with state transitions. `POST /api/v1/scan-results` with `scan_run_id` set AND `complete_scan_run=true` attempts to close the run; unknown `scan_run_id` logs a warning and continues. Loomweave's `run_id` (§3 Finding.source.run_id) is a separate concept. -**Design action**: decide whether Clarion's `run_id` maps onto Filigree's `scan_run_id` (requires `create_scan_run` call before emission) or whether Clarion posts without a `scan_run_id` and declines the run-lifecycle feature. Either is fine; silence produces drift. +**Design action**: decide whether Loomweave's `run_id` maps onto Filigree's `scan_run_id` (requires `create_scan_run` call before emission) or whether Loomweave posts without a `scan_run_id` and declines the run-lifecycle feature. Either is fine; silence produces drift. ### 4.11 Dedup key collision under same-rule-same-line findings -Filigree's finding dedup is `(file_id, scan_source, rule_id, coalesce(line_start, -1))`. Clarion's per-entity findings may re-post the same `(path, rule_id)` across runs with different `line_start` values (because entities move within a file). The current dedup collapses cross-run history at the same line; an entity renamed and moved inside a file loses its triage state. +Filigree's finding dedup is `(file_id, scan_source, rule_id, coalesce(line_start, -1))`. Loomweave's per-entity findings may re-post the same `(path, rule_id)` across runs with different `line_start` values (because entities move within a file). The current dedup collapses cross-run history at the same line; an entity renamed and moved inside a file loses its triage state. -**Design action**: specify Clarion's dedup key policy — either (a) accept Filigree's dedup and live with the coarseness, (b) synthesise `rule_id` to include the entity ID (e.g., `CLA-PY-STRUCTURE-001:python:class:auth.tokens.TokenManager`), (c) push dedup server-side into Filigree as a new feature. +**Design action**: specify Loomweave's dedup key policy — either (a) accept Filigree's dedup and live with the coarseness, (b) synthesise `rule_id` to include the entity ID (e.g., `LMWV-PY-STRUCTURE-001:python:class:auth.tokens.TokenManager`), (c) push dedup server-side into Filigree as a new feature. ### 4.12 Integration test fixtures and mocks -Acceptance criterion 12.8.1 names "a wardline mock client successfully consumes the HTTP read API." Its inverse — "a mock Filigree accepts Clarion's POSTs with richer `metadata` nested fields" — is not named. Nor is a Wardline SARIF corpus for Clarion plugin detection tests. +Acceptance criterion 12.8.1 names "a wardline mock client successfully consumes the HTTP read API." Its inverse — "a mock Filigree accepts Loomweave's POSTs with richer `metadata` nested fields" — is not named. Nor is a Wardline SARIF corpus for Loomweave plugin detection tests. **Design action**: §12 Testing should add fixture requirements: (a) mock Filigree HTTP server (axum/wiremock in Rust); (b) Wardline SARIF corpus (a committed slice of real Wardline output); (c) `REGISTRY_VERSION` pinning test; (d) schema-compatibility test pinning `GET /api/files/_schema`. ### 4.13 Commit-ref dirty handling -Wardline SARIF includes `wardline.commitRef` with `-dirty` suffix when the tree is modified. Clarion's commit-cadence vocabulary is silent. +Wardline SARIF includes `wardline.commitRef` with `-dirty` suffix when the tree is modified. Loomweave's commit-cadence vocabulary is silent. -**Design action**: specify Clarion's policy: does `clarion analyze` refuse to run on dirty trees, record the dirty marker on the run, or strip it? +**Design action**: specify Loomweave's policy: does `loomweave analyze` refuse to run on dirty trees, record the dirty marker on the run, or strip it? ### 4.14 BAR subsystem awareness -Wardline's BAR subsystem (`src/wardline/bar/`) already has an LLM-reviewer pipeline using `litellm`/`anthropic`, producing `bar_evidence.json` artifacts. It is adjacent to but independent of Clarion's LLM orchestration. +Wardline's BAR subsystem (`src/wardline/bar/`) already has an LLM-reviewer pipeline using `litellm`/`anthropic`, producing `bar_evidence.json` artifacts. It is adjacent to but independent of Loomweave's LLM orchestration. **Design action**: not necessarily v0.1 scope, but the design should acknowledge BAR exists to avoid later surprise. At minimum, non-goals (§10) should list "BAR-style reviewer sessions" so the separation is explicit. ### 4.15 Identity scheme alignment for Wardline qualnames -Wardline's `qualname` is the nested-class-method dotted form (e.g., `ScanEngine._scan_file`). Clarion's design specifies `canonical_qualified_name` with Python package path and `src/` stripping (e.g., `wardline.scanner.engine.ScanEngine._scan_file`). These are **different strings**. +Wardline's `qualname` is the nested-class-method dotted form (e.g., `ScanEngine._scan_file`). Loomweave's design specifies `canonical_qualified_name` with Python package path and `src/` stripping (e.g., `wardline.scanner.engine.ScanEngine._scan_file`). These are **different strings**. -**Design action**: §3 must specify the Wardline-identity mapping. Either Clarion ingests `fingerprint.json` qualnames and resolves them to Clarion entity IDs via `module_file_map`, or Clarion exposes a `wardline_qualname` property on entities and accepts non-unique joins. +**Design action**: §3 must specify the Wardline-identity mapping. Either Loomweave ingests `fingerprint.json` qualnames and resolves them to Loomweave entity IDs via `module_file_map`, or Loomweave exposes a `wardline_qualname` property on entities and accepts non-unique joins. ### 4.16 Filigree CHANGELOG-signalled breaking changes Filigree's CHANGELOG (`CHANGELOG.md:91`) explicitly uses "Breaking (API)" markers and has had recent changes to the scan-results endpoint shape (`create_issues` → `create_observations` in v2.0.0 Unreleased). Filigree does not treat `/api/v1/…` as a strong stability contract; it breaks and tags. -**Design action**: §9 should note Filigree's breaking-change posture and specify Clarion's policy on tracking Filigree's CHANGELOG (CI-time schema-compatibility test is the minimum). +**Design action**: §9 should note Filigree's breaking-change posture and specify Loomweave's policy on tracking Filigree's CHANGELOG (CI-time schema-compatibility test is the minimum). --- @@ -511,7 +511,7 @@ Filigree's CHANGELOG (`CHANGELOG.md:91`) explicitly uses "Breaking (API)" marker - **Filigree MCP tool inventory and handlers** — read in full: `mcp_tools/files.py` (entire file); `mcp_server.py:155-163`. - **Four FK constraints on `file_records(id)`** — `db_schema.py:131, 161, 198, 214`. - **Three auto-create paths** — traced from each ingress point to `_upsert_file_record`. -- **Absence of `registry_backend`, `FILIGREE_FILE_REGISTRY_DISPLACED`, `clarion` references in Filigree** — `grep` across full tree. +- **Absence of `registry_backend`, `FILIGREE_FILE_REGISTRY_DISPLACED`, `loomweave` references in Filigree** — `grep` across full tree. - **Absence of Wardline references in Filigree**, and **absence of Filigree references in Wardline scanner path** — `grep` across both trees. - **Wardline decorator inventory (42 + 1)** — read `core/registry.py:55-237`, `decorators/__init__.py:46-89`, and each decorator module. Cross-checked against `docs/spec/wardline-01-07-annotation-vocabulary.md:13-32` and corpus specimens. - **Wardline SARIF shape and property-bag extensions** — read `scanner/sarif.py` in full; verified against `wardline.sarif.baseline.json` (883 KB, 663 results); cross-checked extension schema at `wardline-sarif-extensions.schema.json`. @@ -521,8 +521,8 @@ Filigree's CHANGELOG (`CHANGELOG.md:91`) explicitly uses "Breaking (API)" marker ### 5.2 What was inferred (labelled) - **"Surgery estimate ~5–8 hot files" for `registry_backend`** — based on call-site counts and FK topology. I did not sketch an actual `RegistryProtocol` refactor; the estimate could be lower with a clean protocol or higher if `ObservationsMixin` coupling proves sticky. -- **"Wardline accepting Clarion as entity source is a significant refactor"** — inferred from reading `ScanEngine`, `ScanContext`, `ProjectIndex` structures; not from attempting the refactor. -- **"`output_schema` is bare in code, factory in spec"** — inferred from reading `schema.py:53` (no `*args` capture) and spec text at `wardline-02-A-python-binding.md:199`. May be intentional (the spec describes a forward-compatible factory form); Clarion should accept both. +- **"Wardline accepting Loomweave as entity source is a significant refactor"** — inferred from reading `ScanEngine`, `ScanContext`, `ProjectIndex` structures; not from attempting the refactor. +- **"`output_schema` is bare in code, factory in spec"** — inferred from reading `schema.py:53` (no `*args` capture) and spec text at `wardline-02-A-python-binding.md:199`. May be intentional (the spec describes a forward-compatible factory form); Loomweave should accept both. - **"SARIF property-bag translation is non-trivial"** — 35 + 9 = 44 keys with interlocking semantics. Inferred complexity from cross-references in `sarif.py`, not from attempting the translation. - **"Wardline findings flow to Filigree via Wardline's own integration — false as of today"** — verified by absence of code. If an out-of-tree bridge exists (user's own scripts, a downstream project), I could not detect it. @@ -536,9 +536,9 @@ Filigree's CHANGELOG (`CHANGELOG.md:91`) explicitly uses "Breaking (API)" marker ### 5.4 Rough-edge items -- Filigree's severity coercion (permissive, warn-on-unknown) and Wardline's severity enum (`ERROR/WARNING/SUPPRESS`) **disagree on posture** — Filigree wants to keep data flowing; Wardline wants strict validation. Clarion's posture should be stated explicitly. -- Filigree treats `scan_source` as free-form, which lets Clarion and Wardline coexist on the endpoint without coordination, but also lets two scanners silently claim the same name. This is "ambient tribal convention," not enforced protocol. -- The three persistent identity schemes (Wardline's `qualname`, Wardline's exception-register `location` string, Clarion's planned `canonical_qualified_name`) are **three different answers to "what is this code element?"** — unifying them is real work, not incidental. +- Filigree's severity coercion (permissive, warn-on-unknown) and Wardline's severity enum (`ERROR/WARNING/SUPPRESS`) **disagree on posture** — Filigree wants to keep data flowing; Wardline wants strict validation. Loomweave's posture should be stated explicitly. +- Filigree treats `scan_source` as free-form, which lets Loomweave and Wardline coexist on the endpoint without coordination, but also lets two scanners silently claim the same name. This is "ambient tribal convention," not enforced protocol. +- The three persistent identity schemes (Wardline's `qualname`, Wardline's exception-register `location` string, Loomweave's planned `canonical_qualified_name`) are **three different answers to "what is this code element?"** — unifying them is real work, not incidental. --- diff --git a/docs/implementation/v0.1-scope-plans/v0.1-scope-commitments.md b/docs/implementation/v0.1-scope-plans/v0.1-scope-commitments.md index f2bd2ae4..bd583ae4 100644 --- a/docs/implementation/v0.1-scope-plans/v0.1-scope-commitments.md +++ b/docs/implementation/v0.1-scope-plans/v0.1-scope-commitments.md @@ -1,20 +1,20 @@ -# Clarion v0.1 — Scope Commitment Memo +# Loomweave v0.1 — Scope Commitment Memo **Status**: DECIDED **Date opened**: 2026-04-18 **Date locked**: 2026-04-18 -**Decision owner**: John Morrissey (primary dev of Clarion, Filigree, Wardline) +**Decision owner**: John Morrissey (primary dev of Loomweave, Filigree, Wardline) **Gates**: authorship of 9 P0 ADRs; end of v0.1 design-freeze; start of Rust implementation ## Context note -The "cross-repo lead time" framing in Q2's original wording assumed Filigree was externally maintained. It is not: the same author owns Clarion, Filigree, and Wardline. The federation doctrine is therefore internal discipline, not inter-team contract. Every Filigree-side and Wardline-side dependency is within-scope work, schedulable alongside Clarion's own rework. +The "cross-repo lead time" framing in Q2's original wording assumed Filigree was externally maintained. It is not: the same author owns Loomweave, Filigree, and Wardline. The federation doctrine is therefore internal discipline, not inter-team contract. Every Filigree-side and Wardline-side dependency is within-scope work, schedulable alongside Loomweave's own rework. --- ## Purpose -The 10-agent review panel (`docs/clarion/1.0/reviews/panel-2026-04-17/`) converged on three decisions that only the project author can make. Every P0 ADR, every cost-model claim, and every doctrine asterisk in `loom.md` §5 depends on these commitments being made explicitly rather than left implicit. This memo is the commitment surface — once the three decisions are recorded here, the subsequent work is mechanical. +The 10-agent review panel (`docs/loomweave/1.0/reviews/panel-2026-04-17/`) converged on three decisions that only the project author can make. Every P0 ADR, every cost-model claim, and every doctrine asterisk in `weft.md` §5 depends on these commitments being made explicitly rather than left implicit. This memo is the commitment surface — once the three decisions are recorded here, the subsequent work is mechanical. **Read first**: `../reviews/panel-2026-04-17/00-executive-synthesis.md` §6. @@ -32,7 +32,7 @@ The 10-agent review panel (`docs/clarion/1.0/reviews/panel-2026-04-17/`) converg ## Q1 — v0.1 shipping scope -**The question**: does Clarion v0.1 ship the minimum viable catalog, or the full fabric-integrating shape the briefing currently describes? +**The question**: does Loomweave v0.1 ship the minimum viable catalog, or the full fabric-integrating shape the briefing currently describes? **Why it gates everything**: `06-architecture-critique.md` §1 calls v0.1 "roughly six products in one release." `08-debt.md` C1-C5 are almost all scope-driven — five critical debt items drop to non-critical under minimal scope. The answer determines which P0 ADRs need to exist *now* vs can defer. @@ -44,7 +44,7 @@ The 10-agent review panel (`docs/clarion/1.0/reviews/panel-2026-04-17/`) converg **Unblocks**: ADR-006 (clustering) + ADR-007 (cache key) + ADR-018 (identity reconciliation) become implementation-now. ADR-011 (writer-actor), ADR-013 (secret scanner), ADR-014 (registry backend), ADR-015 (Wardline emission), ADR-016 (observation transport), ADR-017 (severity/dedup) all become "v0.2 design target" ADRs — authored but not blocking. -**Doctrine impact**: the `loom.md` §5 broadening can explicitly name the Wardline→Filigree triangle as a v0.2 asterisk. `briefing.md` needs a rewrite of the "what Clarion v0.1 ships" paragraph to name the deferrals. +**Doctrine impact**: the `weft.md` §5 broadening can explicitly name the Wardline→Filigree triangle as a v0.2 asterisk. `briefing.md` needs a rewrite of the "what Loomweave v0.1 ships" paragraph to name the deferrals. **Risk**: v0.1 is less of a headline. First-customer validation with elspeth is more likely to succeed because the surface area is smaller. @@ -67,25 +67,25 @@ The 10-agent review panel (`docs/clarion/1.0/reviews/panel-2026-04-17/`) converg **Effective v0.1 deferrals**: Wardline→Filigree SARIF bridge, observation HTTP transport, summary-cache beyond a simple in-memory cache, HTTP write API. -**Rationale**: Minimal-core keeps scope tractable and gives the unvalidated cost/perf numbers (`NFR-COST-01`, `NFR-COST-02`, `NFR-PERF-01`) room to mature against real elspeth data rather than acting as acceptance gates. `registry_backend` is retained because the Filigree-side change is within the same author's scope (Q2) and the doctrine cost of deferring it — pretending Clarion is "weaving the fabric" when the fabric-ownership claim is on an asterisk — is higher than the implementation cost of shipping it. +**Rationale**: Minimal-core keeps scope tractable and gives the unvalidated cost/perf numbers (`NFR-COST-01`, `NFR-COST-02`, `NFR-PERF-01`) room to mature against real elspeth data rather than acting as acceptance gates. `registry_backend` is retained because the Filigree-side change is within the same author's scope (Q2) and the doctrine cost of deferring it — pretending Loomweave is "weaving the fabric" when the fabric-ownership claim is on an asterisk — is higher than the implementation cost of shipping it. -**Open consequence**: Wardline→Filigree SARIF bridge deferred to v0.2 is now a *choice*, not a cross-team constraint. A native Wardline emitter is within-scope and would collapse the triangle finding the panel flagged in §3.1 of the executive synthesis. Worth considering as a parallel v0.1 item if the implementation cost is small; otherwise legitimately defers to v0.2 with the `loom.md` §5 asterisk retirement named. +**Open consequence**: Wardline→Filigree SARIF bridge deferred to v0.2 is now a *choice*, not a cross-team constraint. A native Wardline emitter is within-scope and would collapse the triangle finding the panel flagged in §3.1 of the executive synthesis. Worth considering as a parallel v0.1 item if the implementation cost is small; otherwise legitimately defers to v0.2 with the `weft.md` §5 asterisk retirement named. --- ## Q2 — Filigree `registry_backend` commitment -**The question**: is the pluggable `registry_backend` flag in Filigree a v0.1 prerequisite Clarion commits to delivering, or a v0.2 aspiration that v0.1 works around via shadow-registry mode? +**The question**: is the pluggable `registry_backend` flag in Filigree a v0.1 prerequisite Loomweave commits to delivering, or a v0.2 aspiration that v0.1 works around via shadow-registry mode? -**Why it gates the doctrine**: `loom.md` §4 requires pairwise composability. Right now, the briefing's "Clarion owns structural truth" language implies a Filigree-side change that does not yet exist. Until this is committed or deferred, the suite cannot honestly claim pairwise Wardline+Filigree composition. `11-doctrine-panel-synthesis.md` finding 2.7 and `08-debt.md` C2 converge on this. +**Why it gates the doctrine**: `weft.md` §4 requires pairwise composability. Right now, the briefing's "Loomweave owns structural truth" language implies a Filigree-side change that does not yet exist. Until this is committed or deferred, the suite cannot honestly claim pairwise Wardline+Filigree composition. `11-doctrine-panel-synthesis.md` finding 2.7 and `08-debt.md` C2 converge on this. ### Option A — v0.1 commitment -**What it means**: Clarion v0.1 depends on Filigree shipping `registry_backend: clarion` support. Filigree-side work is a prerequisite of Clarion's first release. +**What it means**: Loomweave v0.1 depends on Filigree shipping `registry_backend: loomweave` support. Filigree-side work is a prerequisite of Loomweave's first release. **Required actions**: 1. Open a conversation with Filigree maintainers this week. Scope: protocol shape, rollout, schema-compat guarantees. -2. Author a joint Filigree-side ADR or spec. Clarion cannot own it unilaterally. +2. Author a joint Filigree-side ADR or spec. Loomweave cannot own it unilaterally. 3. Accept cross-repo coordination overhead in the v0.1 schedule. **Lead time**: weeks. This is the longest-lead item in the plan. @@ -94,31 +94,31 @@ The 10-agent review panel (`docs/clarion/1.0/reviews/panel-2026-04-17/`) converg ### Option B — v0.2 aspiration, shadow mode for v0.1 -**What it means**: v0.1 ships with Clarion reading Filigree's existing registry without owning it ("shadow-registry" mode). The "Clarion owns structural truth" claim is scoped to v0.2. `briefing.md` and `loom.md` are edited to name the asterisk. +**What it means**: v0.1 ships with Loomweave reading Filigree's existing registry without owning it ("shadow-registry" mode). The "Loomweave owns structural truth" claim is scoped to v0.2. `briefing.md` and `weft.md` are edited to name the asterisk. **Required actions**: 1. Rewrite briefing.md's prerequisites paragraph and the "How they interact" data-flow row to describe v0.1 shadow mode honestly. -2. Rewrite `loom.md` §5 to name the shadow-mode asterisk and its retirement condition. +2. Rewrite `weft.md` §5 to name the shadow-mode asterisk and its retirement condition. 3. Author ADR-014 to describe the shadow-mode contract *and* the v0.2 retirement path. **Lead time**: 1–2 days of writing. -**Consequences**: Clarion is on its own schedule. The headline "Clarion is the work that weaves the fabric" (briefing.md §"What Clarion v0.1 ships") gets trimmed — the fabric-weaving is scoped to v0.2. +**Consequences**: Loomweave is on its own schedule. The headline "Loomweave is the work that weaves the fabric" (briefing.md §"What Loomweave v0.1 ships") gets trimmed — the fabric-weaving is scoped to v0.2. ### Decision - [x] **Option A — v0.1 commitment** - [ ] ~~Option B — v0.2 aspiration, shadow mode for v0.1~~ -**Rationale**: the "schedule a Filigree-maintainer conversation" lead-time argument assumed external ownership. Filigree is authored by the same person as Clarion, so the `registry_backend` implementation is a within-scope Filigree task that lands on Clarion's own schedule. The briefing's headline claim that Clarion "weaves the fabric" stays accurate, and the doctrine's pairwise-composability rule (`loom.md` §4) is honoured without an asterisk. +**Rationale**: the "schedule a Filigree-maintainer conversation" lead-time argument assumed external ownership. Filigree is authored by the same person as Loomweave, so the `registry_backend` implementation is a within-scope Filigree task that lands on Loomweave's own schedule. The briefing's headline claim that Loomweave "weaves the fabric" stays accurate, and the doctrine's pairwise-composability rule (`weft.md` §4) is honoured without an asterisk. **Filigree-side work required** (author to self): - Design `RegistryProtocol` trait / interface in Filigree. -- Implement `registry_backend: clarion` mode that delegates the file registry to Clarion's entity catalog. +- Implement `registry_backend: loomweave` mode that delegates the file registry to Loomweave's entity catalog. - Preserve shadow-registry fallback (`registry_backend: local`) as the default, so Filigree alone continues to work. - Ship schema-compatibility pin (`NFR-COMPAT-01`). -**Lead time**: author's schedule — treat as a 1–2 day Filigree-side task authored in parallel with Clarion's first sprint. +**Lead time**: author's schedule — treat as a 1–2 day Filigree-side task authored in parallel with Loomweave's first sprint. --- @@ -168,13 +168,13 @@ Plugin declares capabilities in its manifest (entity kinds, max memory, expected Ordered by critical-path; items in the same bullet group can run in parallel. -### Day 1 (Clarion-side writing, ~1 day) +### Day 1 (Loomweave-side writing, ~1 day) -1. **Update `loom.md` §5** to name the v0.1 asterisks the decisions created. Concretely: +1. **Update `weft.md` §5** to name the v0.1 asterisks the decisions created. Concretely: - If Wardline→Filigree bridge stays deferred to v0.2: name the asterisk, name the retirement condition (native Wardline emitter), and broaden the failure test to cover pipeline-stage coupling. - Name the plugin-hybrid model in §5 or §6 so the `wardline.core.registry.REGISTRY` direct-import coupling is explicitly in scope of the doctrine rather than beside it. - Estimated: 2–3 hours. -2. **Rewrite `briefing.md` v0.1 scope paragraph and data-flow table** to match the committed scope — i.e., name SARIF-bridge / observation-HTTP / HTTP-write-API as v0.2 deferrals and name `registry_backend` as a v0.1 Clarion+Filigree joint deliverable. Estimated: 1–2 hours. +2. **Rewrite `briefing.md` v0.1 scope paragraph and data-flow table** to match the committed scope — i.e., name SARIF-bridge / observation-HTTP / HTTP-write-API as v0.2 deferrals and name `registry_backend` as a v0.1 Loomweave+Filigree joint deliverable. Estimated: 1–2 hours. 3. **Collapse dual ADR tables** (system-design §12 + detailed-design §11) into one canonical home with cross-references. Fix the "ADR-005 through ADR-013" undercount while there. Estimated: 1 hour. 4. **Amend ADR-001** per `07-adr-review.md`: cite actual requirements (single-binary, subprocess supervision, SQLite ergonomics); drop the "not subject to alternatives analysis" phrase in system-design §12. Estimated: 30 minutes. @@ -186,10 +186,10 @@ Author the P0 ADRs. Two new numbers (021, 022) are allocated because existing AD - **ADR-007** — summary cache key (P0, v0.1 core) - **ADR-011** — writer-actor vs shadow-DB (P0, v0.1 core) - **ADR-013** — pre-ingest secret scanner policy (P0, v0.1 core) -- **ADR-014** — Filigree `registry_backend` protocol (P0, v0.1 — joint Clarion+Filigree) +- **ADR-014** — Filigree `registry_backend` protocol (P0, v0.1 — joint Loomweave+Filigree) - **ADR-015** — Wardline→Filigree emission path (P0). Default commitment: v0.2 deferral with written retirement condition. Promote to v0.1 only if the Wardline-native-emitter spike (action 8 below) shows it is cheap. - **ADR-016** — observation transport (P0; document v0.2 deferral) -- **ADR-017** — severity / dedup (P0; v0.1 scoped to Clarion-native findings; Wardline-sourced deferred with ADR-015) +- **ADR-017** — severity / dedup (P0; v0.1 scoped to Loomweave-native findings; Wardline-sourced deferred with ADR-015) - **ADR-018** — identity reconciliation (P0; v0.1 core) - **ADR-021** — plugin authority model (new; hybrid per Q3) - **ADR-022** — core/plugin ontology ownership boundary (new; highest-priority missing ADR per `07-adr-review.md`) @@ -204,7 +204,7 @@ Author the P0 ADRs. Two new numbers (021, 022) are allocated because existing AD ### Exit criterion for "ready for solution design" - [ ] This memo marked DECIDED (done) -- [ ] `loom.md` §5 updated +- [ ] `weft.md` §5 updated - [ ] `briefing.md` v0.1 scope updated - [ ] Dual ADR tables collapsed - [ ] ADR-001 amended @@ -221,13 +221,13 @@ Expected elapsed time: **3–5 focused days** of writing (Days 1–3 above), plu - [x] Q1 decided - [x] Q2 decided - [x] Q3 decided -- [x] `loom.md` §5 updated (Day 1, 2026-04-18) +- [x] `weft.md` §5 updated (Day 1, 2026-04-18) - [x] `briefing.md` v0.1 scope updated (Day 1, 2026-04-18) - [x] 11 ADRs authored (ADR-006, ADR-007, ADR-011, ADR-012, ADR-013, ADR-014, ADR-015, ADR-016, ADR-017, ADR-018, ADR-021, ADR-022 — 2026-04-18) — 12 ADRs total including the HTTP auth rewrite - [x] ADR-001 amended (Day 1, 2026-04-18) - [x] Dual ADR tables collapsed (system-design §12 canonical; detailed-design §11 navigation-only) (Day 1, 2026-04-18) - [x] HTTP auth default flipped (ADR-012 accepted; system-design §10 + detailed-design §7 + threat-model row updated — 2026-04-18) -- [ ] Cost-model spike completed — **deferred to post-implementation**; requires running Clarion core against elspeth-slice, which is not runnable until v0.1 core implementation reaches Phase-4 LLM dispatch. See "Validation" section below. +- [ ] Cost-model spike completed — **deferred to post-implementation**; requires running Loomweave core against elspeth-slice, which is not runnable until v0.1 core implementation reaches Phase-4 LLM dispatch. See "Validation" section below. - [x] Wardline-native-emitter spike decided (research-level; recorded in ADR-015 "Spike result" section — 2026-04-18). Outcome: v0.2 deferral confirmed; effort estimated at ~1 day focused work if later re-prioritised. **Committed for implementation on**: 2026-04-18 (ADR-writing sprint complete; C1 pending post-implementation). @@ -238,9 +238,9 @@ Expected elapsed time: **3–5 focused days** of writing (Days 1–3 above), plu ### NFR-COST-01 / NFR-COST-02 / NFR-PERF-01 — deferred to post-implementation -Block C1 was originally scoped as a pre-implementation spike. In practice it cannot run until the Clarion core has enough Phase-4/5/6 scaffolding to invoke the Anthropic SDK against real entities. The spike is therefore rescheduled as a post-minimum-viable-implementation validation task: +Block C1 was originally scoped as a pre-implementation spike. In practice it cannot run until the Loomweave core has enough Phase-4/5/6 scaffolding to invoke the Anthropic SDK against real entities. The spike is therefore rescheduled as a post-minimum-viable-implementation validation task: -- **Trigger**: when the Clarion core can `clarion analyze` a ten-file Python input through Phase 1 → Phase 4 and produce at least one Haiku-tier summary. +- **Trigger**: when the Loomweave core can `loomweave analyze` a ten-file Python input through Phase 1 → Phase 4 and produce at least one Haiku-tier summary. - **Input**: `elspeth-slice` — smallest viable subset (target ~50 files, representative of production density). - **Output**: concrete numbers for per-run cost, cache hit rate (after two runs — first populates cache, second measures hit), wall-clock time. - **Outcome**: validate or downgrade NFR-COST-01 ($15 ± 50%), NFR-COST-02 (≥95% cache hit), NFR-PERF-01 (≤60 min). Update `requirements.md` NFR rationale sections with the observed numbers; record methodology here. @@ -257,6 +257,6 @@ Named in ADR-011 as a v0.2 validation task. Specifically verifies the writer-act - `../reviews/panel-2026-04-17/00-executive-synthesis.md` — overall panel verdict (also preserves the scope-ambition, missing-ontology-ADR, and critical-debt findings from the retired architecture-critique, ADR-review, and debt working papers) - `../reviews/panel-2026-04-17/09-threat-model.md` — non-negotiable controls - `../reviews/panel-2026-04-17/11-doctrine-panel-synthesis.md` — convergent reader-panel findings -- `../../suite/loom.md` §4, §5 — composition law and failure test +- `../../suite/weft.md` §4, §5 — composition law and failure test > **Retired panel working papers**: the architecture-critique, ADR-review, and debt-catalogue documents referenced in earlier drafts of this memo were consolidated into `00-executive-synthesis.md` on 2026-04-18 and removed from `docs/`. Their findings survive in the exec-synthesis punch list and in the authored ADRs. diff --git a/docs/implementation/v1.0-cicd-readiness.md b/docs/implementation/v1.0-cicd-readiness.md index f5d923b7..ae575a5b 100644 --- a/docs/implementation/v1.0-cicd-readiness.md +++ b/docs/implementation/v1.0-cicd-readiness.md @@ -1,7 +1,7 @@ -# Clarion v1.0 CI/CD Readiness +# Loomweave v1.0 CI/CD Readiness **Date**: 2026-05-20 -**Scope**: GitHub Actions CI and release workflows for the Clarion v1.0 publish. +**Scope**: GitHub Actions CI and release workflows for the Loomweave v1.0 publish. **Decision basis**: ADR-023 tooling baseline, ADR-033 distribution path, and the v1.0 operator install surface. @@ -13,7 +13,7 @@ v1.0 operator install surface. ## Pipeline Map -Clarion has two release-relevant GitHub Actions workflows: +Loomweave has two release-relevant GitHub Actions workflows: - `.github/workflows/ci.yml` runs on pull requests and pushes to `main`. - `.github/workflows/release.yml` runs on `v*` tags and via manual dispatch for @@ -58,7 +58,7 @@ pytest plugins/python bash tests/e2e/sprint_1_walking_skeleton.sh CARGO_BUILD=0 bash tests/e2e/wp5_secret_scan.sh python scripts/check-github-release-governance.py \ - --repository tachyon-beep/clarion \ + --repository foundryside-dev/loomweave \ --branch main ``` @@ -94,7 +94,7 @@ It reads `GITHUB_TOKEN`/`GH_TOKEN`, or falls back to the current `gh auth token` - The Rust release matrix matches ADR-033's v1.0 supported platforms: `x86_64-unknown-linux-gnu`, `x86_64-apple-darwin`, and `aarch64-apple-darwin`. -- Each Rust archive is extracted and the packaged `clarion` binary is exercised +- Each Rust archive is extracted and the packaged `loomweave` binary is exercised with `--version` and `--help` before upload. - The Python plugin sdist is installed into a fresh virtual environment before upload, and the installed package version is checked. @@ -121,7 +121,7 @@ It reads `GITHUB_TOKEN`/`GH_TOKEN`, or falls back to the current `gh auth token` Snapshot on 2026-05-20: -- Release-prep PR: . +- Release-prep PR: . - PR branch: `RC1`; use the PR's current head commit at merge time as the exact release-prep source revision. - Base branch: `main`. diff --git a/docs/implementation/v1.0-tag-cut/README.md b/docs/implementation/v1.0-tag-cut/README.md index a31ce33b..b72122d8 100644 --- a/docs/implementation/v1.0-tag-cut/README.md +++ b/docs/implementation/v1.0-tag-cut/README.md @@ -1,9 +1,9 @@ -# Clarion v1.0.0 — Tag-Cut Readiness Archive +# Loomweave v1.0.0 — Tag-Cut Readiness Archive **Status**: Historical archive. `v1.0.0` was tagged on 2026-05-19, but the release did not publish artifacts; `v1.0.1` became the first published build. -This directory preserves the canonical pre-tag-cut artifacts for Clarion v1.0.0. +This directory preserves the canonical pre-tag-cut artifacts for Loomweave v1.0.0. It supersedes `docs/implementation/v0.1-publish/` (which was renamed in intent when the v0.1 → v1.0 rebrand landed but never moved on disk) for that historical tag-cut program. diff --git a/docs/implementation/v1.0-tag-cut/execution-plan.md b/docs/implementation/v1.0-tag-cut/execution-plan.md index e4127658..474db829 100644 --- a/docs/implementation/v1.0-tag-cut/execution-plan.md +++ b/docs/implementation/v1.0-tag-cut/execution-plan.md @@ -1,4 +1,4 @@ -# Clarion v1.0.0 — Tag-Cut Execution Plan +# Loomweave v1.0.0 — Tag-Cut Execution Plan **Date**: 2026-05-22 **Closes**: every gap in [`gap-register.md`](gap-register.md). @@ -58,7 +58,7 @@ Bundle these into a single PR titled `docs: pre-tag-cut v1.0 contract drift fixe |-----|------|--------| | DOC-01 | `CHANGELOG.md:60` | `UNAUTHORIZED` → `UNAUTHENTICATED` | | DOC-02 | `CHANGELOG.md:60` | Add `BATCH_TOO_LARGE` | -| DOC-05 | `docs/clarion/1.0/requirements.md:573` | `See:` line add `, ADR-034` | +| DOC-05 | `docs/loomweave/1.0/requirements.md:573` | `See:` line add `, ADR-034` | | DOC-08 | `docs/operator/secret-scanning.md:83` | drop `in v0.1` qualifier | | DOC-10 | `CHANGELOG.md` | adjust "(through ADR-034)" phrasing | @@ -68,9 +68,9 @@ Bundle: `docs: refresh v1.0 docs against ADR-034`. | Gap | File | Change | |-----|------|--------| -| DOC-03 | `docs/clarion/1.0/requirements.md:771-783` | Rewrite NFR-SEC-03 statement + verification for ADR-034 rules | -| DOC-04 | `docs/clarion/1.0/requirements.md:558-573` | Rewrite REQ-HTTP-03 statement + verification | -| DOC-06 | `docs/suite/loom.md:65-70`, `CHANGELOG.md:108` | "v0.1 asterisks" → "v1.0 asterisks"; "deferred to v0.2" → "deferred to v1.1" | +| DOC-03 | `docs/loomweave/1.0/requirements.md:771-783` | Rewrite NFR-SEC-03 statement + verification for ADR-034 rules | +| DOC-04 | `docs/loomweave/1.0/requirements.md:558-573` | Rewrite REQ-HTTP-03 statement + verification | +| DOC-06 | `docs/suite/weft.md:65-70`, `CHANGELOG.md:108` | "v0.1 asterisks" → "v1.0 asterisks"; "deferred to v0.2" → "deferred to v1.1" | | DOC-07 | `CLAUDE.md:144` | Rewrite HMAC paragraph; HMAC is preferred in 1.0 | | DOC-09 | `CHANGELOG.md` (known limitations) | Add Wardline REGISTRY asterisk entry | @@ -81,8 +81,8 @@ Bundle: `docs(operator): pre-tag-cut governance + storage docs`. | Gap | New file | Content | |-----|----------|---------| | GOV-03 | `docs/operator/v1.0-release-rollback.md` | Rollback / yank runbook (see gap register) | -| DOC-11 | append §Storage section to `README.md` and / or new `docs/clarion/1.0/operations.md` | Deployment constraints (NFS prohibition, no double-analyze, backup procedure) | -| SEC-02 | append section to `docs/operator/secret-scanning.md` and `docs/operator/clarion-http-read-api.md` | Loopback-no-token trust statement | +| DOC-11 | append §Storage section to `README.md` and / or new `docs/loomweave/1.0/operations.md` | Deployment constraints (NFS prohibition, no double-analyze, backup procedure) | +| SEC-02 | append section to `docs/operator/secret-scanning.md` and `docs/operator/loomweave-http-read-api.md` | Loopback-no-token trust statement | | SEC-03 | append to `docs/operator/secret-scanning.md` and CHANGELOG known-limits | Pre-WP5 catalogue upgrade requirement | ### Stream 1D — One-line code fixes (one PR, ~1 hr including tests) @@ -91,9 +91,9 @@ Bundle: `fix(v1.0): pre-tag correctness fixes`. | Gap | File | Change | |-----|------|--------| -| SEC-01 | `crates/clarion-storage/src/query.rs:296-302` | Fail-closed `entity_briefing_block_reason`; add malformed-JSON unit test | -| CI-02 | `crates/clarion-cli/src/http_read.rs:426-431` | Use a non-`InvalidPath` error code on body-parse failure; add a test exercising oversized body | -| SEC-02 (code half) | `crates/clarion-mcp/src/config.rs` and / or `crates/clarion-cli/src/serve.rs` startup | Emit explicit startup banner line when loopback-no-token mode is in effect | +| SEC-01 | `crates/loomweave-storage/src/query.rs:296-302` | Fail-closed `entity_briefing_block_reason`; add malformed-JSON unit test | +| CI-02 | `crates/loomweave-cli/src/http_read.rs:426-431` | Use a non-`InvalidPath` error code on body-parse failure; add a test exercising oversized body | +| SEC-02 (code half) | `crates/loomweave-mcp/src/config.rs` and / or `crates/loomweave-cli/src/serve.rs` startup | Emit explicit startup banner line when loopback-no-token mode is in effect | ### Stream 1E — Filigree issue creation (one batch, ~30 min) @@ -117,17 +117,17 @@ new gates wired. ### 2.1 — Storage cross-process safety (STO-01, ~2 hr) -PR: `fix(storage): cross-process lock for clarion analyze`. +PR: `fix(storage): cross-process lock for loomweave analyze`. -- Add `fs2 = "0.4"` to `crates/clarion-cli/Cargo.toml`. +- Add `fs2 = "0.4"` to `crates/loomweave-cli/Cargo.toml`. - At top of `analyze::run` (and `serve` write paths), open - `.clarion/clarion.lock`, call `try_lock_exclusive`. Hold for the + `.loomweave/loomweave.lock`, call `try_lock_exclusive`. Hold for the duration of the writer-actor lifetime. - Refuse a second concurrent invocation with a clear error message: - "another `clarion analyze` is in progress against this project". -- Add a test that spawns two `clarion analyze` subprocesses against the + "another `loomweave analyze` is in progress against this project". +- Add a test that spawns two `loomweave analyze` subprocesses against the same project root and asserts exactly one succeeds. -- Update `docs/clarion/1.0/operations.md` (or the README §Storage) +- Update `docs/loomweave/1.0/operations.md` (or the README §Storage) paragraph from Stream 1C to reference the fail-fast behaviour. ### 2.2 — Storage identity + integrity (STO-02, STO-04, ~1 hr) @@ -135,7 +135,7 @@ PR: `fix(storage): cross-process lock for clarion analyze`. PR: `fix(storage): application_id PRAGMA + e2e integrity check`. - Add `PRAGMA application_id = 0x434C524E` to - `crates/clarion-storage/src/pragma.rs::apply_write_pragmas`. + `crates/loomweave-storage/src/pragma.rs::apply_write_pragmas`. - On open, assert `application_id == 0` (then set) or `0x434C524E` (recognise); refuse any other value with a clear error. - Add `PRAGMA integrity_check` final assertion to @@ -170,7 +170,7 @@ PR: `ci: wire MCP surface, Phase 3, and tag-lineage gates`. PR: `ci: extend SLSA provenance to plugin sdist`. - Edit `release-subjects` step in `.github/workflows/release.yml:201-225` - to glob `clarion-*.tar.gz` + `clarion_plugin_python*.tar.gz`. + to glob `loomweave-*.tar.gz` + `loomweave_plugin_python*.tar.gz`. - Update release notes template to mention plugin-sdist provenance. ### 2.5 — Post-publish verification (CI-04, ~45 min, optional for v1.0) @@ -216,7 +216,7 @@ artifacts smoke-tested. ### 3.2 — Live governance dry-run (~15 min) - Run `scripts/check-github-release-governance.py --repository - tachyon-beep/clarion --branch main` locally with a token that can + foundryside-dev/loomweave --branch main` locally with a token that can read the policy settings. Confirm exit 0. ### 3.3 — Merge the gap-closure PRs to main (~15 min) @@ -254,15 +254,15 @@ artifacts smoke-tested. - The final pre-tag commit on `main`: ```bash - echo "$(git rev-parse HEAD)" > crates/clarion-storage/migrations/published_build.txt - git add crates/clarion-storage/migrations/published_build.txt + echo "$(git rev-parse HEAD)" > crates/loomweave-storage/migrations/published_build.txt + git add crates/loomweave-storage/migrations/published_build.txt git commit -m "release: mark v1.0.0 as published-build baseline" git push origin main ``` ### 3.8 — Cut the tag (~5 min) -- `git tag -a v1.0.0 -m "Clarion v1.0.0"` +- `git tag -a v1.0.0 -m "Loomweave v1.0.0"` - `git push origin v1.0.0` - The release workflow fires on the push. Watch the `verify` → `release-governance` → `build-rust` / `build-plugin` → `release` → @@ -270,7 +270,7 @@ artifacts smoke-tested. ### 3.9 — Public artifact smoke (~30 min) -- Once the release is public, install Clarion from the +- Once the release is public, install Loomweave from the GitHub-Release-hosted assets on a fresh host (or one of the external-operator smoke VMs). - Verify cosign signatures via `cosign verify-blob` with the diff --git a/docs/implementation/v1.0-tag-cut/filigree-issue-bodies.md b/docs/implementation/v1.0-tag-cut/filigree-issue-bodies.md index fd7a9488..20846484 100644 --- a/docs/implementation/v1.0-tag-cut/filigree-issue-bodies.md +++ b/docs/implementation/v1.0-tag-cut/filigree-issue-bodies.md @@ -51,20 +51,20 @@ v1.1 backlog items get `release:v1.1` + the same category labels. ### DOC-03 — NFR-SEC-03 stale post-ADR-034 - **title**: `[v1.0 blocker] Refresh NFR-SEC-03 for ADR-034 authenticated-non-loopback rule` - **priority**: P1, **type**: docs, **labels**: `release:v1.0`, `category:docs`, `adr:034` -- **body**: Closes DOC-03. Rewrite at `docs/clarion/1.0/requirements.md:771-783`. +- **body**: Closes DOC-03. Rewrite at `docs/loomweave/1.0/requirements.md:771-783`. ### DOC-04 — REQ-HTTP-03 stale post-ADR-034 - **title**: `[v1.0 blocker] Refresh REQ-HTTP-03 for ADR-034 authenticated-non-loopback rule` - **priority**: P1, **type**: docs, **labels**: `release:v1.0`, `category:docs`, `adr:034` -- **body**: Closes DOC-04. Rewrite at `docs/clarion/1.0/requirements.md:558-573`. +- **body**: Closes DOC-04. Rewrite at `docs/loomweave/1.0/requirements.md:558-573`. ### DOC-05 — REQ-HTTP-03 See line missing ADR-034 - **title**: `[v1.0] Add ADR-034 to REQ-HTTP-03 See line` - **priority**: P2, **type**: docs, **labels**: `release:v1.0`, `category:docs` - **body**: Closes DOC-05. One-line edit. -### DOC-06 — loom.md "v0.1 asterisks" labels -- **title**: `[v1.0 blocker] Rename loom.md "v0.1 asterisks" to "v1.0 asterisks"; CHANGELOG "deferred to v0.2" → "v1.1"` +### DOC-06 — weft.md "v0.1 asterisks" labels +- **title**: `[v1.0 blocker] Rename weft.md "v0.1 asterisks" to "v1.0 asterisks"; CHANGELOG "deferred to v0.2" → "v1.1"` - **priority**: P1, **type**: docs, **labels**: `release:v1.0`, `category:docs` - **body**: Closes DOC-06. @@ -91,12 +91,12 @@ v1.1 backlog items get `release:v1.1` + the same category labels. ### DOC-11 — Storage operator constraints in README - **title**: `[v1.0 blocker] Document storage deployment constraints (NFS, double-analyze, backup)` - **priority**: P1, **type**: docs, **labels**: `release:v1.0`, `category:docs`, `category:storage` -- **body**: Closes DOC-11. New `docs/clarion/1.0/operations.md` or README §Storage. +- **body**: Closes DOC-11. New `docs/loomweave/1.0/operations.md` or README §Storage. ### SEC-01 — entity_briefing_block_reason fail-open - **title**: `[v1.0 blocker] Fail-closed: entity_briefing_block_reason on malformed properties JSON` - **priority**: P1, **type**: bug, **labels**: `release:v1.0`, `category:security`, `crate:storage` -- **body**: Closes SEC-01. One-line code change at `crates/clarion-storage/src/query.rs:296-302` plus malformed-JSON unit test. +- **body**: Closes SEC-01. One-line code change at `crates/loomweave-storage/src/query.rs:296-302` plus malformed-JSON unit test. ### SEC-02 — Loopback-no-token trust assumption - **title**: `[v1.0 blocker] Document loopback-no-token trust + emit startup banner` @@ -104,7 +104,7 @@ v1.1 backlog items get `release:v1.1` + the same category labels. - **body**: Closes SEC-02. Operator-doc additions + startup-banner warning. ### SEC-03 — Pre-WP5 catalogue upgrade requirement -- **title**: `[v1.0 blocker] Document pre-WP5 .clarion/ upgrade requirement (re-analyze needed)` +- **title**: `[v1.0 blocker] Document pre-WP5 .loomweave/ upgrade requirement (re-analyze needed)` - **priority**: P1, **type**: docs, **labels**: `release:v1.0`, `category:security`, `category:docs` - **body**: Closes SEC-03. @@ -116,7 +116,7 @@ v1.1 backlog items get `release:v1.1` + the same category labels. ### CI-02 — Federation error code on body parse failure - **title**: `[v1.0 blocker] Fix HMAC body-parse error code (currently returns InvalidPath)` - **priority**: P1, **type**: bug, **labels**: `release:v1.0`, `category:federation`, `crate:cli` -- **body**: Closes CI-02. Edit `crates/clarion-cli/src/http_read.rs:426-431`. +- **body**: Closes CI-02. Edit `crates/loomweave-cli/src/http_read.rs:426-431`. ### CI-03 — SLSA coverage for Python sdist - **title**: `[v1.0 blocker] Extend SLSA provenance to Python plugin sdist` @@ -129,7 +129,7 @@ v1.1 backlog items get `release:v1.1` + the same category labels. - **body**: Closes CI-04. Optional for v1.0; defer to v1.1 if time-constrained. ### STO-01 — Cross-process lock -- **title**: `[v1.0 blocker] fs2 advisory lock for clarion analyze (prevent concurrent corruption)` +- **title**: `[v1.0 blocker] fs2 advisory lock for loomweave analyze (prevent concurrent corruption)` - **priority**: P1, **type**: bug, **labels**: `release:v1.0`, `category:storage`, `crate:cli`, `crate:storage` - **body**: Closes STO-01. Add fs2 + lock + test. @@ -169,7 +169,7 @@ v1.1 backlog items get `release:v1.1` + the same category labels. (File these before tag-cut so post-1.0 work is tracked.) -### V11-ARCH-01 — Extract clarion-core::errors shared error vocabulary +### V11-ARCH-01 — Extract loomweave-core::errors shared error vocabulary - priority: P2, type: feature, labels: `release:v1.1`, `category:architecture` - body: deep-dive-arch v1.1 priority #1. Closes the MCP/HTTP error-code drift smell. @@ -181,7 +181,7 @@ v1.1 backlog items get `release:v1.1` + the same category labels. - priority: P2, type: feature, labels: `release:v1.1`, `category:architecture`, `crate:core` - body: deep-dive-arch v1.1 priority #3. `openrouter.rs` / `cli_provider.rs` (Codex + Claude on shared base) / `prompts.rs`. -### V11-ARCH-04 — Split clarion-mcp/src/lib.rs into tools/ subdir +### V11-ARCH-04 — Split loomweave-mcp/src/lib.rs into tools/ subdir - priority: P3, type: feature, labels: `release:v1.1`, `category:architecture`, `crate:mcp` - body: deep-dive-arch v1.1 priority #4. @@ -205,7 +205,7 @@ v1.1 backlog items get `release:v1.1` + the same category labels. - priority: P2, type: feature, labels: `release:v1.1`, `category:storage` - body: deep-dive-db. Schema-additive 0002_*; refine recovery WHERE-clause. -### V11-STO-02 — clarion db backup subcommand +### V11-STO-02 — loomweave db backup subcommand - priority: P2, type: feature, labels: `release:v1.1`, `category:storage`, `crate:cli` - body: deep-dive-db. `rusqlite::backup::Backup`-based. @@ -227,7 +227,7 @@ v1.1 backlog items get `release:v1.1` + the same category labels. ### V11-STO-07 — ReaderPool eager validation - priority: P3, type: feature, labels: `release:v1.1`, `category:storage` -- body: `clarion serve` boot should fail-fast on bad DB. +- body: `loomweave serve` boot should fail-fast on bad DB. ### V11-STO-08 — briefing_blocked typed column - priority: P3, type: feature, labels: `release:v1.1`, `category:storage` diff --git a/docs/implementation/v1.0-tag-cut/gap-register.md b/docs/implementation/v1.0-tag-cut/gap-register.md index c9e668ac..5a1a4910 100644 --- a/docs/implementation/v1.0-tag-cut/gap-register.md +++ b/docs/implementation/v1.0-tag-cut/gap-register.md @@ -1,4 +1,4 @@ -# Clarion v1.0.0 Tag-Cut Gap Register +# Loomweave v1.0.0 Tag-Cut Gap Register **Date**: 2026-05-22 **Branch**: `RC1` at `4dd7b63` @@ -75,7 +75,7 @@ blocker count is unchanged.) - **Runbook**: [`docs/operator/v1.0-release-governance.md`](../../operator/v1.0-release-governance.md) - **Effort**: 1 hr operator. - **Exit criterion**: `scripts/check-github-release-governance.py - --repository tachyon-beep/clarion --branch main` exits 0. + --repository foundryside-dev/loomweave --branch main` exits 0. #### GOV-02 (Critical) — No tag protection rule @@ -136,8 +136,8 @@ blocker count is unchanged.) - **Origin**: arch-2026-05-20 R3; deep-dive-docs C1. - **Evidence**: `CHANGELOG.md:60` lists `UNAUTHORIZED`. Authoritative sources (`docs/federation/contracts.md:82`, `82,202,276`, - `docs/clarion/adr/ADR-014:126`, `docs/clarion/adr/ADR-034:45,87,145`, - implementation tests at `crates/clarion-cli/tests/serve.rs:1457,1495,1547,1579,1614`) + `docs/loomweave/adr/ADR-014:126`, `docs/loomweave/adr/ADR-034:45,87,145`, + implementation tests at `crates/loomweave-cli/tests/serve.rs:1457,1495,1547,1579,1614`) all use `UNAUTHENTICATED`. - **Fix**: `Edit` `UNAUTHORIZED` → `UNAUTHENTICATED` in `CHANGELOG.md:60`. - **Effort**: 1 min. @@ -155,9 +155,9 @@ blocker count is unchanged.) #### DOC-03 (High) — NFR-SEC-03 is stale post-ADR-034 - **Origin**: deep-dive-docs H2 (net-new). -- **Evidence**: `docs/clarion/1.0/requirements.md:771-783` says non-loopback +- **Evidence**: `docs/loomweave/1.0/requirements.md:771-783` says non-loopback "When enabled, startup logs a warning that the endpoint is - unauthenticated and must be protected outside Clarion." `ADR-034:43` + unauthenticated and must be protected outside Loomweave." `ADR-034:43` says "Non-loopback binds require both `allow_non_loopback: true` and a resolved HMAC identity secret or legacy bearer token; either alone is insufficient." Per CLAUDE.md precedence (ADR > requirements), the @@ -170,7 +170,7 @@ blocker count is unchanged.) #### DOC-04 (High) — REQ-HTTP-03 is stale post-ADR-034 - **Origin**: deep-dive-docs H2. -- **Evidence**: `docs/clarion/1.0/requirements.md:558-573` still describes +- **Evidence**: `docs/loomweave/1.0/requirements.md:558-573` still describes the HTTP API as "unauthenticated and loopback-only by default" and the verification as "unauthenticated-surface warning". Same drift as DOC-03 but on the routing side. @@ -188,10 +188,10 @@ blocker count is unchanged.) - **Fix**: `Edit` to add `, ADR-034`. - **Effort**: 1 min. -#### DOC-06 (High) — loom.md still labels asterisks as "v0.1" +#### DOC-06 (High) — weft.md still labels asterisks as "v0.1" - **Origin**: deep-dive-docs H3. -- **Evidence**: `docs/suite/loom.md:65,69` heads "v0.1 asterisks" and says +- **Evidence**: `docs/suite/weft.md:65,69` heads "v0.1 asterisks" and says "The asterisk ships with v0.1 and retires in v0.2." CLAUDE.md:64 and the CHANGELOG say both asterisks persist into v1.0 and retire post-release. @@ -206,8 +206,8 @@ blocker count is unchanged.) - **Evidence**: `CLAUDE.md:144` "HMAC inbound auth (C-4) — bearer is the 1.0 wire surface; HMAC is forward-compatible and tracked for post-1.0 hardening." Code reads `identity_token_env` and enforces - `X-Loom-Component: clarion:` today - (`crates/clarion-cli/src/http_read.rs:129-130,184,373-374`); ADR-034 + `X-Weft-Component: loomweave:` today + (`crates/loomweave-cli/src/http_read.rs:129-130,184,373-374`); ADR-034 marks HMAC as the preferred mechanism; CHANGELOG:115-117 documents it shipping. - **Fix**: Update CLAUDE.md HMAC paragraph: HMAC ships in v1.0 per @@ -225,15 +225,15 @@ blocker count is unchanged.) - **Fix**: Drop the version qualifier or change to "in v1.0". - **Effort**: 1 min. -#### DOC-09 (Medium) — loom.md asterisk 2 (Wardline REGISTRY) absent from CHANGELOG +#### DOC-09 (Medium) — weft.md asterisk 2 (Wardline REGISTRY) absent from CHANGELOG - **Origin**: deep-dive-docs M1. -- **Evidence**: `loom.md:70` names the Wardline REGISTRY import as +- **Evidence**: `weft.md:70` names the Wardline REGISTRY import as asterisk 2 with no retirement condition citation. CHANGELOG "Known v1.0 limitations" (lines 105-117) does not mention it. - **Fix**: Add an entry to CHANGELOG "Known limitations": "The Python plugin imports `wardline.core.registry.REGISTRY` at startup - (loom.md §5 asterisk 2). Retirement condition: Wardline ships a + (weft.md §5 asterisk 2). Retirement condition: Wardline ships a stable runtime probe API." - **Effort**: 5 min. @@ -254,11 +254,11 @@ blocker count is unchanged.) because the cross-process race (STO-01) will surface as operator confusion without operator-facing guidance. - **Evidence**: There is no operator-facing doc that says (a) do not - put `.clarion/` on NFS, (b) do not run two `clarion analyze` + put `.loomweave/` on NFS, (b) do not run two `loomweave analyze` simultaneously, (c) backup procedure is "stop analyze → `PRAGMA wal_checkpoint(TRUNCATE)` → file copy". - **Fix**: Add a §Storage paragraph to top-level README or a new - `docs/clarion/1.0/operations.md` covering deployment constraints. + `docs/loomweave/1.0/operations.md` covering deployment constraints. - **Effort**: 30 min. --- @@ -268,7 +268,7 @@ blocker count is unchanged.) #### SEC-01 (Critical) — `entity_briefing_block_reason` fail-open on malformed JSON - **Origin**: deep-dive-security T-11 (net-new). -- **Evidence**: `crates/clarion-storage/src/query.rs:296-302` +- **Evidence**: `crates/loomweave-storage/src/query.rs:296-302` `entity_briefing_block_reason` returns `None` (= unblocked) when `serde_json::from_str(properties_json)` fails. A plugin emitting malformed `properties` JSON silently disables the WP5 briefing @@ -282,32 +282,32 @@ blocker count is unchanged.) #### SEC-02 (High) — Loopback-no-token trust assumption not documented - **Origin**: deep-dive-security T-9. -- **Evidence**: `crates/clarion-cli/src/http_read.rs:384-386` admits any +- **Evidence**: `crates/loomweave-cli/src/http_read.rs:384-386` admits any request when both `identity_secret` and `auth_token` are `None`. - `validate_auth_trust` (`crates/clarion-mcp/src/config.rs:307-345`) + `validate_auth_trust` (`crates/loomweave-mcp/src/config.rs:307-345`) only refuses *non-loopback* binds. On a shared developer host or CI runner, any local process can read the entire (non-blocked) catalogue. - **Fix**: (a) Add explicit operator-doc section to - `docs/operator/secret-scanning.md` and `docs/operator/clarion-http-read-api.md` + `docs/operator/secret-scanning.md` and `docs/operator/loomweave-http-read-api.md` describing the loopback-without-token trust assumption. (b) Add a startup-banner line when loopback-no-token mode is in effect: "HTTP API serving on loopback without authentication; any local process can read the catalogue." - **Effort**: 20 min docs + 20 min code. -#### SEC-03 (High) — Legacy `.clarion/` upgrade requirement undocumented +#### SEC-03 (High) — Legacy `.loomweave/` upgrade requirement undocumented - **Origin**: deep-dive-security T-11 (sub-point). - **Evidence**: `entity_briefing_block_reason` reads `properties.briefing_blocked`. Pre-WP5 binaries never wrote that - property; a 1.0 binary opening a pre-WP5 `.clarion/clarion.db` will + property; a 1.0 binary opening a pre-WP5 `.loomweave/loomweave.db` will serve the entire catalogue without refusal because every row's `briefing_blocked` is structurally absent. - **Fix**: Document the upgrade requirement in `docs/operator/secret-scanning.md` and in the CHANGELOG "Known limitations": "Upgrading from a pre-WP5 binary requires - `clarion analyze` re-run before any HTTP API serves the catalogue." + `loomweave analyze` re-run before any HTTP API serves the catalogue." - **Effort**: 10 min. --- @@ -335,7 +335,7 @@ blocker count is unchanged.) - **Origin**: deep-dive-arch (architecture critic) + deep-dive-security (corroborating). -- **Evidence**: `crates/clarion-cli/src/http_read.rs:426-431` — if +- **Evidence**: `crates/loomweave-cli/src/http_read.rs:426-431` — if `to_bytes(body, HTTP_BODY_LIMIT_BYTES)` fails inside HMAC middleware, the response returns `ErrorCode::InvalidPath` with message "request body is invalid". A federation client pattern-matching on @@ -349,12 +349,12 @@ blocker count is unchanged.) - **Origin**: deep-dive-pipeline. - **Evidence**: `release-subjects` in `.github/workflows/release.yml:201-225` - globs only `clarion-*.tar.gz` (Rust archives) into the SLSA provenance + globs only `loomweave-*.tar.gz` (Rust archives) into the SLSA provenance subjects. The Python plugin sdist has cosign signing but no SLSA attestation file. A user installing via `pipx install` from the release URL has no `slsa-verifier` path. - **Fix**: Either (a) extend the glob to include - `clarion_plugin_python*.tar.gz` and append to the existing provenance + `loomweave_plugin_python*.tar.gz` and append to the existing provenance file, or (b) document the gap explicitly in the release notes and in `docs/operator/v1.0-release-governance.md`. - **Effort**: 30 min for option (a); 5 min for option (b). @@ -377,30 +377,30 @@ blocker count is unchanged.) ### Category 5 — Storage / SQLite discipline -#### STO-01 (Critical) — No cross-process lock; second `clarion analyze` corrupts run state +#### STO-01 (Critical) — No cross-process lock; second `loomweave analyze` corrupts run state - **Origin**: deep-dive-db (highest-priority finding). -- **Evidence**: `crates/clarion-cli/src/run_lifecycle.rs:19-25` +- **Evidence**: `crates/loomweave-cli/src/run_lifecycle.rs:19-25` unconditionally executes `UPDATE runs SET status='failed' WHERE - status='running'` at the top of every `clarion analyze`, then opens + status='running'` at the top of every `loomweave analyze`, then opens the writer-actor connection. There is no `fs2::FileExt::try_lock_exclusive()` - on `.clarion/clarion.lock` or the DB file. A second concurrent - `clarion analyze` flips the live run's status to `failed` while the + on `.loomweave/loomweave.lock` or the DB file. A second concurrent + `loomweave analyze` flips the live run's status to `failed` while the first writer holds an open connection mid-batch. -- **Fix**: Add `fs2 = "0.4"` to `clarion-cli/Cargo.toml`. At the top +- **Fix**: Add `fs2 = "0.4"` to `loomweave-cli/Cargo.toml`. At the top of `analyze::run` (and `serve` write paths), acquire - `File::open(".clarion/clarion.lock")?.try_lock_exclusive()`. Hold for - writer-actor lifetime. Fail fast with "another clarion analyze is in + `File::open(".loomweave/loomweave.lock")?.try_lock_exclusive()`. Hold for + writer-actor lifetime. Fail fast with "another loomweave analyze is in progress against this project". - **Effort**: 1-2 hr code + test. #### STO-02 (High) — No `PRAGMA application_id` - **Origin**: deep-dive-db. -- **Evidence**: `crates/clarion-storage/src/pragma.rs` sets WAL, +- **Evidence**: `crates/loomweave-storage/src/pragma.rs` sets WAL, synchronous, busy, foreign-keys, but never `application_id` or `user_version`. The SQLite file has no identity marker; tooling - like `file(1)` or `sqlite3 .dbinfo` cannot distinguish a Clarion DB + like `file(1)` or `sqlite3 .dbinfo` cannot distinguish a Loomweave DB from any other SQLite file. - **Fix**: Add `PRAGMA application_id = 0x434C524E` ("CLRN") to `apply_write_pragmas`. On open, assert the application_id is 0 @@ -413,7 +413,7 @@ blocker count is unchanged.) - **Origin**: deep-dive-db; arch-2026-05-20 follow-up. - **Evidence**: ADR-024 migration retirement guard (`scripts/check-migration-retirement.py`) requires - `crates/clarion-storage/migrations/published_build.txt` to mark the + `crates/loomweave-storage/migrations/published_build.txt` to mark the v1.0 commit SHA as the baseline. The file does not exist. - **Fix**: Create the file at tag-cut time with the exact `v1.0.0` commit SHA. Block CI on its presence going forward. @@ -427,19 +427,19 @@ blocker count is unchanged.) - **Origin**: deep-dive-db. - **Evidence**: No `VACUUM INTO`, no `rusqlite::backup::Backup`, no `PRAGMA integrity_check` invocation in CI. A user who `cp`s - `.clarion/clarion.db` during a live `clarion analyze` gets a torn - copy because WAL pages live in `clarion.db-wal` separately. + `.loomweave/loomweave.db` during a live `loomweave analyze` gets a torn + copy because WAL pages live in `loomweave.db-wal` separately. - **Fix v1.0**: (a) Add `PRAGMA integrity_check` final assertion to `tests/e2e/sprint_1_walking_skeleton.sh`. (b) Document the supported backup procedure in DOC-11's README §Storage paragraph (shutdown → `PRAGMA wal_checkpoint(TRUNCATE)` → file copy). - (c) `clarion db backup` subcommand deferred to v1.1. + (c) `loomweave db backup` subcommand deferred to v1.1. - **Effort**: 30 min (parts a and b only). #### STO-05 (Medium) — `recover_preexisting_running_runs` has no liveness guard - **Origin**: deep-dive-db. -- **Evidence**: `crates/clarion-cli/src/run_lifecycle.rs:19-25` +- **Evidence**: `crates/loomweave-cli/src/run_lifecycle.rs:19-25` recovery sweep is `UPDATE runs SET status='failed' WHERE status='running'`. No PID column, no heartbeat, no startup-instance token. STO-01's fs2 lock is necessary but not sufficient; even @@ -460,7 +460,7 @@ blocker count is unchanged.) - **Evidence**: `tests/e2e/sprint_2_mcp_surface.sh` exists and exercises all 8 MCP navigation tools (`entity_at`, `find_entity`, `callers_of`, `execution_paths_from`, `summary`, `issues_for`, `neighborhood`, - `subsystem_members`) over stdio against a real `clarion analyze` + `subsystem_members`) over stdio against a real `loomweave analyze` output with the Python plugin venv. The MCP `serve.rs` integration test only covers `initialize`; the `storage_tools.rs` test exercises the underlying storage layer but not the MCP wire serialization. A @@ -499,16 +499,16 @@ blocker count is unchanged.) Filed as Filigree issues with `release:v1.1` label so they don't get lost. **Architecture refactors (deep-dive-arch v1.1 priority order):** -1. Extract `clarion-core::errors` shared error-code vocabulary. +1. Extract `loomweave-core::errors` shared error-code vocabulary. 2. Split `analyze.rs` → `analyze/phase3.rs` + `analyze/mapping.rs`. 3. Split `llm_provider.rs` per-provider. -4. Split `clarion-mcp/src/lib.rs` into `tools/` subdir. +4. Split `loomweave-mcp/src/lib.rs` into `tools/` subdir. 5. Split `plugin/host.rs` validation from transport. 6. Replace local HMAC-SHA256 with `hmac` + `subtle` crates. **Storage hardening (deep-dive-db):** 1. `runs.owner_pid` + `heartbeat_at` columns and refined recovery WHERE-clause. -2. `clarion db backup` subcommand via `rusqlite::backup::Backup`. +2. `loomweave db backup` subcommand via `rusqlite::backup::Backup`. 3. ~~`summary_cache.entity_id` FK via table-rebuild migration (confirmed bug — not intentional asymmetry).~~ **Closed in v1.0 (2026-05-24)**: landed in-place in migration `0001_initial_schema.sql` under @@ -559,7 +559,7 @@ All of: 2. Every Medium documentation gap is closed (operator-facing accuracy is non-negotiable on a release artifact). 3. `scripts/check-github-release-governance.py` exits 0 against live - `tachyon-beep/clarion`. + `foundryside-dev/loomweave`. 4. PR #12 (or its successor) is merged to `main` and is the parent of the `v1.0.0` tag commit. 5. `release.yml workflow_dispatch` dry-run from `main` produces all diff --git a/docs/clarion/1.0/README.md b/docs/loomweave/1.0/README.md similarity index 90% rename from docs/clarion/1.0/README.md rename to docs/loomweave/1.0/README.md index 53011a79..20d03d76 100644 --- a/docs/clarion/1.0/README.md +++ b/docs/loomweave/1.0/README.md @@ -1,6 +1,6 @@ -# Clarion v1.0 Docset +# Loomweave v1.0 Docset -This folder is the canonical Clarion v1.0 document set. +This folder is the canonical Loomweave v1.0 document set. ## Canonical design docs @@ -15,7 +15,7 @@ This folder is the canonical Clarion v1.0 document set. ## Reading order -- New reader: [../../suite/briefing.md](../../suite/briefing.md) -> [../../suite/loom.md](../../suite/loom.md) -> [requirements.md](./requirements.md) -> [system-design.md](./system-design.md) +- New reader: [../../suite/briefing.md](../../suite/briefing.md) -> [../../suite/weft.md](../../suite/weft.md) -> [requirements.md](./requirements.md) -> [system-design.md](./system-design.md) - Design reviewer (evaluating completeness, not yet implementing): new-reader path, then [detailed-design.md](./detailed-design.md) and [../adr/README.md](../adr/README.md). - Implementation work: [requirements.md](./requirements.md) -> [system-design.md](./system-design.md) -> [detailed-design.md](./detailed-design.md) -> [../adr/README.md](../adr/README.md) diff --git a/docs/clarion/1.0/detailed-design.md b/docs/loomweave/1.0/detailed-design.md similarity index 68% rename from docs/clarion/1.0/detailed-design.md rename to docs/loomweave/1.0/detailed-design.md index 8b81f6c2..af81752d 100644 --- a/docs/clarion/1.0/detailed-design.md +++ b/docs/loomweave/1.0/detailed-design.md @@ -1,6 +1,6 @@ -# Clarion v1.0 — Detailed Design Reference +# Loomweave v1.0 — Detailed Design Reference -**Status**: Baselined for v1.0 release (carried forward from the v0.1 post-ADR-sprint baseline) — Layer 3 of the Clarion v1.0 docset, implementation-level reference. Canonical home of the ADR backlog, SQL schema, Rust struct definitions, full YAML config example, exact rule-ID catalogues, wire-format mapping tables, and cross-tool prerequisite lists. +**Status**: Baselined for v1.0 release (carried forward from the v0.1 post-ADR-sprint baseline) — Layer 3 of the Loomweave v1.0 docset, implementation-level reference. Canonical home of the ADR backlog, SQL schema, Rust struct definitions, full YAML config example, exact rule-ID catalogues, wire-format mapping tables, and cross-tool prerequisite lists. **Baseline**: 2026-04-17 · **Last updated**: 2026-05-19 **Primary author**: qacona@gmail.com (with Claude) **First customer target**: `/home/john/elspeth` (~425k LOC Python) @@ -19,16 +19,16 @@ ### What this document is -This is Clarion v1.0's **detailed design reference** — implementation-level depth. Everything here is concrete: Rust struct definitions, the full SQL schema, the complete `clarion.yaml` example, the exhaustive Phase-7 rule catalogue with thresholds, the complete MCP tool list, severity mapping tables, the `scan_run_id` lifecycle, the dedup collision policy, SARIF property-bag translation rules, full Filigree/Wardline prerequisite lists (with specific file references and ADR citations), the testing strategy, the ADR backlog with full priorities, and the appendices (future direction, glossary, Rust stack). +This is Loomweave v1.0's **detailed design reference** — implementation-level depth. Everything here is concrete: Rust struct definitions, the full SQL schema, the complete `loomweave.yaml` example, the exhaustive Phase-7 rule catalogue with thresholds, the complete MCP tool list, severity mapping tables, the `scan_run_id` lifecycle, the dedup collision policy, SARIF property-bag translation rules, full Filigree/Wardline prerequisite lists (with specific file references and ADR citations), the testing strategy, the ADR backlog with full priorities, and the appendices (future direction, glossary, Rust stack). ### What moved up -Material covering *what Clarion does* (capabilities, quality attributes, non-goals) is in the **requirements** layer. Material covering *how Clarion is structured at architectural level* (component topology, data-model concepts, integration patterns, mid-level mechanisms, architectural trade-offs) is in the **system-design** layer. The abstract, design principles, process/UX topology, conceptual data model, core/plugin split narrative, integration posture, threat model, suite-bootstrap architecture, and explicit-deferrals / non-goals all live in those higher layers now. Nothing has been lost — moved, not deleted. +Material covering *what Loomweave does* (capabilities, quality attributes, non-goals) is in the **requirements** layer. Material covering *how Loomweave is structured at architectural level* (component topology, data-model concepts, integration patterns, mid-level mechanisms, architectural trade-offs) is in the **system-design** layer. The abstract, design principles, process/UX topology, conceptual data model, core/plugin split narrative, integration posture, threat model, suite-bootstrap architecture, and explicit-deferrals / non-goals all live in those higher layers now. Nothing has been lost — moved, not deleted. ### When to read what -- **Starting fresh on Clarion?** Read [../../suite/briefing.md](../../suite/briefing.md) + [../../suite/loom.md](../../suite/loom.md) first, then [requirements.md](./requirements.md) + [system-design.md](./system-design.md) in that order. This document fills in detail only when you're ready to implement or debug a specific subsystem. -- **Answering "what does Clarion guarantee?"** Requirements. +- **Starting fresh on Loomweave?** Read [../../suite/briefing.md](../../suite/briefing.md) + [../../suite/weft.md](../../suite/weft.md) first, then [requirements.md](./requirements.md) + [system-design.md](./system-design.md) in that order. This document fills in detail only when you're ready to implement or debug a specific subsystem. +- **Answering "what does Loomweave guarantee?"** Requirements. - **Answering "how is it structured?"** System design. - **Answering "what's the exact `busy_timeout`? What's the full Phase-7 rule ID list? Which ADR says what?"** Here. @@ -42,14 +42,14 @@ Sections 1-11 are implementation detail by subsystem, mirroring the natural orde ### Plugin packaging (v1.0) -Each plugin is a separately-installable Python package that provides an executable entry point matching the Clarion plugin protocol. Core finds plugins via `~/.config/clarion/plugins.toml` or via the project's `clarion.yaml`. +Each plugin is a separately-installable Python package that provides an executable entry point matching the Loomweave plugin protocol. Core finds plugins via `~/.config/loomweave/plugins.toml` or via the project's `loomweave.yaml`. -**Isolation**: recommended install path is `pipx install clarion-plugin-python`. Plain `pip install clarion-plugin-python` into an unknown active environment risks dependency conflicts with the analyzed project. `plugins.toml` records the plugin's `executable` path (typically a pipx-managed shim) and declared `python_version`; the core refuses to launch a plugin whose `python_version` mismatches the configured expectation. +**Isolation**: recommended install path is `pipx install loomweave-plugin-python`. Plain `pip install loomweave-plugin-python` into an unknown active environment risks dependency conflicts with the analyzed project. `plugins.toml` records the plugin's `executable` path (typically a pipx-managed shim) and declared `python_version`; the core refuses to launch a plugin whose `python_version` mismatches the configured expectation. ```toml -# ~/.config/clarion/plugins.toml +# ~/.config/loomweave/plugins.toml [plugins.python] -executable = "~/.local/pipx/venvs/clarion-plugin-python/bin/clarion-plugin-python" +executable = "~/.local/pipx/venvs/loomweave-plugin-python/bin/loomweave-plugin-python" python_version = ">=3.11,<3.14" version = "1.0" ``` @@ -90,11 +90,11 @@ capabilities: factual_findings: true rules: # abridged example; exact emitted catalogue lives in §5 and the authored ADR set - - { id: CLA-PY-STRUCTURE-001, description: "Circular import detected", severity: WARN } - - { id: CLA-PY-STRUCTURE-002, description: "Module-level side effect at import time", severity: INFO } - - { id: CLA-FACT-TODO, description: "TODO marker in comment", severity: INFO, kind: fact } - - { id: CLA-FACT-ENTRYPOINT, description: "CLI/HTTP entry point detected", severity: INFO, kind: fact } - - { id: CLA-FACT-DEAD-CODE-CANDIDATE, description: "Entity unreachable from the reachability root set over call+import edges (heuristic; confidence_basis: heuristic, confidence < 1). Conservative — counts all edge tiers, honours reflection/dynamic-dispatch barriers, excludes framework-magic kinds; honest signal-unavailable when no roots are emitted. Emitted by find_dead_code.", severity: NONE, kind: fact } + - { id: LMWV-PY-STRUCTURE-001, description: "Circular import detected", severity: WARN } + - { id: LMWV-PY-STRUCTURE-002, description: "Module-level side effect at import time", severity: INFO } + - { id: LMWV-FACT-TODO, description: "TODO marker in comment", severity: INFO, kind: fact } + - { id: LMWV-FACT-ENTRYPOINT, description: "CLI/HTTP entry point detected", severity: INFO, kind: fact } + - { id: LMWV-FACT-DEAD-CODE-CANDIDATE, description: "Entity unreachable from the reachability root set over call+import edges (heuristic; confidence_basis: heuristic, confidence < 1). Conservative — counts all edge tiers, honours reflection/dynamic-dispatch barriers, excludes framework-magic kinds; honest signal-unavailable when no roots are emitted. Emitted by find_dead_code.", severity: NONE, kind: fact } # Additional plugin-declared rules are omitted here for readability; see §5 for # the concrete emitted findings and ADR-017 / ADR-022 for naming rules. @@ -108,8 +108,8 @@ prompt_templates: ### Error handling posture -- Plugin crash during batch → core records files whose `analyze_file` responses have already crossed the bounded internal file-batch channel into the writer actor; unfinished files are represented by `CLA-INFRA-PLUGIN-CRASH` / plugin-specific findings and the run row ends `failed` with partial committed rows. Crash-loop circuit breaker (>3 crashes in 60s) halts the plugin permanently for the run. -- Plugin timeout on one file (default 30s, configurable per-plugin) → emit `CLA-INFRA-PLUGIN-TIMEOUT`; skip; continue. +- Plugin crash during batch → core records files whose `analyze_file` responses have already crossed the bounded internal file-batch channel into the writer actor; unfinished files are represented by `LMWV-INFRA-PLUGIN-CRASH` / plugin-specific findings and the run row ends `failed` with partial committed rows. Crash-loop circuit breaker (>3 crashes in 60s) halts the plugin permanently for the run. +- Plugin timeout on one file (default 30s, configurable per-plugin) → emit `LMWV-INFRA-PLUGIN-TIMEOUT`; skip; continue. - Plugin malformed JSON or framing error → core logs, skips the message; continues. Never crashes on plugin misbehaviour. - No silent fallbacks. Every skip/error produces a finding. - **stdout hygiene**: the plugin protocol reserves stdout for framed JSON-RPC. Plugin authors must redirect logging to stderr; a stray `print()` breaks the stream. This is documented as a plugin-author requirement and enforced by the reference Python client. @@ -141,9 +141,9 @@ v0.1 serial over files within one process. Parallelism deferred to v0.2 pending ### Observe-vs-enforce coupling (Principle 5) — v0.1 reality -The plugin manifest above names Wardline annotation groups directly. **v0.1 reality**: Wardline ships the canonical descriptor as `wardline.core.registry.REGISTRY` (`MappingProxyType` of 42 canonical decorator names with `REGISTRY_VERSION`). Clarion's Python plugin imports it directly at startup — not a new artifact, an existing one used in the right direction. The "hardcoded match" concern is resolved: when Wardline adds an annotation, it lands in `REGISTRY`; Clarion's plugin picks it up on next run. Version skew emits `CLA-INFRA-WARDLINE-REGISTRY-STALE`. See §9 for the direct-import contract. +The plugin manifest above names Wardline annotation groups directly. **v0.1 reality**: Wardline ships the canonical descriptor as `wardline.core.registry.REGISTRY` (`MappingProxyType` of 42 canonical decorator names with `REGISTRY_VERSION`). Loomweave's Python plugin imports it directly at startup — not a new artifact, an existing one used in the right direction. The "hardcoded match" concern is resolved: when Wardline adds an annotation, it lands in `REGISTRY`; Loomweave's plugin picks it up on next run. Version skew emits `LMWV-INFRA-WARDLINE-REGISTRY-STALE`. See §9 for the direct-import contract. -**v0.2 generalisation**: Wardline adds a `wardline annotations descriptor --format yaml` export for non-Python plugins (Java, Rust) that cannot import Python objects directly. This matters only when Clarion adds non-Python plugins; v0.1 ships Python-only and the direct import suffices. +**v0.2 generalisation**: Wardline adds a `wardline annotations descriptor --format yaml` export for non-Python plugins (Java, Rust) that cannot import Python objects directly. This matters only when Loomweave adds non-Python plugins; v0.1 ships Python-only and the direct import suffices. --- @@ -198,7 +198,7 @@ struct Entity { ### Canonical-name policy (Python) - **Definition site wins for emitted definitions**. `TokenManager` defined in `auth/tokens.py` has ID `python:class:auth.tokens.TokenManager`. The v1.0 plugin collapses `__init__.py` to the package module name, but it does not emit `alias_of` edges for package re-exports. -- **`src.` prefix stripped**. Projects using `src/` layout get `src.auth.tokens` → `auth.tokens` canonicalisation; policy configurable via `clarion.yaml:analysis.python.canonical_root` (default: auto-detect from `pyproject.toml` `[tool.setuptools.packages.find]` or `[project.optional-dependencies]`). +- **`src.` prefix stripped**. Projects using `src/` layout get `src.auth.tokens` → `auth.tokens` canonicalisation; policy configurable via `loomweave.yaml:analysis.python.canonical_root` (default: auto-detect from `pyproject.toml` `[tool.setuptools.packages.find]` or `[project.optional-dependencies]`). - **Test and script modules** keep their on-disk module path (no canonicalisation) because they lack a deterministic install path. ### Stability properties @@ -208,7 +208,7 @@ struct Entity { - Symbol renames → ID changes. Rename tracking via `EntityAlias` table is a v0.2 feature; the v0.1 posture handles the 80% case (file move without rename) without explicit alias tracking. - Same-named classes in different modules are distinguished by their canonical path (`auth.tokens.TokenManager` vs `billing.tokens.TokenManager`). -**Known v0.1 limitation**: symbol rename without file move (e.g., `class TokenManager` → `class JwtTokenManager`) detaches every cross-tool reference. Filigree issues tagged with the old ID become orphans until v0.2's `EntityAlias` ships. Document this prominently in operator-facing release notes; the mitigation for v0.1 users is to record renames as annotated commits and run `clarion analyze --repair-aliases ` (a v0.1 CLI command that inserts a manual alias row). +**Known v0.1 limitation**: symbol rename without file move (e.g., `class TokenManager` → `class JwtTokenManager`) detaches every cross-tool reference. Filigree issues tagged with the old ID become orphans until v0.2's `EntityAlias` ships. Document this prominently in operator-facing release notes; the mitigation for v0.1 users is to record renames as annotated commits and run `loomweave analyze --repair-aliases ` (a v0.1 CLI command that inserts a manual alias row). ### Edge (Rust) @@ -240,14 +240,14 @@ struct Finding { // Identity and provenance id: FindingId, provenance: Provenance { tool: String, tool_version: String, run_id: String }, - rule_id: String, // namespaced: "CLA-PY-STRUCTURE-001" | "PY-WL-001-GOVERNED-DEFAULT" | "COV-001" + rule_id: String, // namespaced: "LMWV-PY-STRUCTURE-001" | "PY-WL-001-GOVERNED-DEFAULT" | "COV-001" // Claim shape — internal representation. Coerced to Filigree's // severity vocabulary {critical,high,medium,low,info} on emit (see §7). kind: FindingKind, // defect | fact | classification | metric | suggestion severity: InternalSeverity, // INFO | WARN | ERROR | CRITICAL for defects; NONE for facts confidence: Option, // 0.0..=1.0; None = deterministic - confidence_basis: Option, // "ast_match" | "llm_inference" | "heuristic" | "dataflow" | "clarion_augmentation" + confidence_basis: Option, // "ast_match" | "llm_inference" | "heuristic" | "dataflow" | "loomweave_augmentation" // Subject entity_id: EntityId, @@ -262,7 +262,7 @@ struct Finding { supported_by: Vec, // Triage (inline for v0.1; separable later). Status vocabulary here is - // Clarion-internal; Filigree's own vocabulary is {open, acknowledged, + // Loomweave-internal; Filigree's own vocabulary is {open, acknowledged, // fixed, false_positive, unseen_in_latest} — mapping documented in §7. status: "open" | "acknowledged" | "suppressed" | "promoted_to_issue", suppression_reason: Option, @@ -285,7 +285,7 @@ enum FindingKind { ```json { - "scan_source": "clarion", + "scan_source": "loomweave", "scan_run_id": "run-2026-04-17-153002", "mark_unseen": false, "create_observations": false, @@ -293,7 +293,7 @@ enum FindingKind { "findings": [ { "path": "src/auth/tokens.py", - "rule_id": "CLA-PY-STRUCTURE-001", + "rule_id": "LMWV-PY-STRUCTURE-001", "message": "Circular import detected between auth.tokens and auth.sessions", "severity": "medium", "line_start": 12, @@ -303,7 +303,7 @@ enum FindingKind { "kind": "defect", "confidence": 0.95, "confidence_basis": "ast_match", - "clarion": { + "loomweave": { "entity_id": "python:class:auth.tokens::TokenManager", "related_entities": ["python:class:auth.sessions::SessionStore"], "supports": [], @@ -319,12 +319,12 @@ enum FindingKind { Key properties of the wire schema (verified against Filigree source — `dashboard_routes/files.py:294-330`, `db_files.py:386-556`): -- **Extension slot is `metadata`** (a dict), **not** `properties`. Any top-level finding key outside the enumerated set — `path`, `rule_id`, `message`, `severity`, `line_start`, `line_end`, `suggestion`, `language`, `metadata` — is **silently dropped**. Clarion's richer fields must nest under `metadata` to survive. +- **Extension slot is `metadata`** (a dict), **not** `properties`. Any top-level finding key outside the enumerated set — `path`, `rule_id`, `message`, `severity`, `line_start`, `line_end`, `suggestion`, `language`, `metadata` — is **silently dropped**. Loomweave's richer fields must nest under `metadata` to survive. - **Line fields are `line_start` + `line_end`**, not a single `line`. -- **Severity enum on the wire is `{critical, high, medium, low, info}`** — all lowercase. Unknown values are coerced to `"info"` and surfaced in the response's `warnings[]` array. Clarion's internal `{INFO, WARN, ERROR, CRITICAL}` vocabulary is mapped to this wire vocabulary on emit (see §7 mapping table). The internal value is preserved in `metadata.clarion.internal_severity` for round-trip. +- **Severity enum on the wire is `{critical, high, medium, low, info}`** — all lowercase. Unknown values are coerced to `"info"` and surfaced in the response's `warnings[]` array. Loomweave's internal `{INFO, WARN, ERROR, CRITICAL}` vocabulary is mapped to this wire vocabulary on emit (see §7 mapping table). The internal value is preserved in `metadata.loomweave.internal_severity` for round-trip. - **`scan_run_id` is optional**. If present and `complete_scan_run=true`, Filigree attempts to close the run; unknown IDs log a warning and continue. -- **Dedup key** on Filigree's side is `(file_id, scan_source, rule_id, coalesce(line_start, -1))` (`db_schema.py:156-157`). Re-posts with the same 4-tuple overwrite; see §7 for Clarion's dedup-collision policy when entities move within a file. -- **Clarion must inspect `response.warnings[]`** (not just the count) on every POST to detect silent severity coercion or unknown-key drops. +- **Dedup key** on Filigree's side is `(file_id, scan_source, rule_id, coalesce(line_start, -1))` (`db_schema.py:156-157`). Re-posts with the same 4-tuple overwrite; see §7 for Loomweave's dedup-collision policy when entities move within a file. +- **Loomweave must inspect `response.warnings[]`** (not just the count) on every POST to detect silent severity coercion or unknown-key drops. ### Entity Briefing (structured summary) @@ -407,9 +407,9 @@ struct RelationshipRef { **Vocabulary approach** (for patterns, antipatterns, risk tags): - Core ships base vocabulary. - Plugins extend with language-specific entries. -- Unknown tags from LLM accepted; logged as `CLA-FACT-VOCABULARY-CANDIDATE`; surfaced via `clarion vocabulary candidates` report for human review. +- Unknown tags from LLM accepted; logged as `LMWV-FACT-VOCABULARY-CANDIDATE`; surfaced via `loomweave vocabulary candidates` report for human review. -**Validation**: briefings are generated via Anthropic's structured-output feature; schema-validated on receipt; invalid responses trigger one retry then emit `CLA-INFRA-BRIEFING-INVALID`. +**Validation**: briefings are generated via Anthropic's structured-output feature; schema-validated on receipt; invalid responses trigger one retry then emit `LMWV-INFRA-BRIEFING-INVALID`. ### Guidance sheet entity @@ -534,23 +534,23 @@ struct WardlineMeta { ### Identity reconciliation across the suite -Three independent identity schemes exist across Clarion, Wardline, and Wardline's exception register: +Three independent identity schemes exist across Loomweave, Wardline, and Wardline's exception register: | Scheme | Example | Owner | Format | |---|---|---|---| -| Clarion `EntityId` | `python:class:auth.tokens::TokenManager` | Clarion | `{plugin_id}:{kind}:{canonical_qualified_name}` | +| Loomweave `EntityId` | `python:class:auth.tokens::TokenManager` | Loomweave | `{plugin_id}:{kind}:{canonical_qualified_name}` | | Wardline `qualname` | `TokenManager.verify` | Wardline `FingerprintEntry` | Nested class/method dotted form (NOT Python `__qualname__`; no `` suffix) | | Wardline exception-register `location` | `src/wardline/scanner/engine.py::ScanEngine._scan_file` | `wardline.exceptions.json` | `{file_path}::{qualname}` with double-colon separator | -None of these strings are byte-equal for the same underlying symbol. Clarion v0.1 reconciles them explicitly: +None of these strings are byte-equal for the same underlying symbol. Loomweave v0.1 reconciles them explicitly: -- **Ingest path from `wardline.fingerprint.json`**: for each `FingerprintEntry`, Clarion computes `(file_path, qualname) → EntityId` via Wardline's own `module_file_map` (available at scan time from `ScanContext`). The reverse mapping (`EntityId → wardline_qualname`) is recorded as an entity property so future Wardline-authored findings can be cross-referenced back to Clarion entities. -- **Ingest path from `wardline.exceptions.json`**: the `location` string is parsed (`split("::", 1)` yields `{file_path, qualname}`); same mapping rule applies. Exception entries that can't be resolved (e.g., file moved, symbol renamed) emit `CLA-INFRA-WARDLINE-EXCEPTION-UNRESOLVED` and persist as dangling records with `entity_id: null` so operators can fix the reference. -- **Ingest path from SARIF (`wardline.sarif.baseline.json`)**: SARIF `location.physicalLocation.artifactLocation.uri` is already POSIX-relative (Wardline's `_normalize_artifact_uri` at `sarif.py:233-246`); combined with `location.logicalLocations[].fullyQualifiedName` (when present) or `partialFingerprints` (when Wardline doesn't emit them, with Clarion's own fingerprint) to produce an `EntityId`. Unresolved SARIF results carry `metadata.clarion.unresolved: true` through translation. +- **Ingest path from `wardline.fingerprint.json`**: for each `FingerprintEntry`, Loomweave computes `(file_path, qualname) → EntityId` via Wardline's own `module_file_map` (available at scan time from `ScanContext`). The reverse mapping (`EntityId → wardline_qualname`) is recorded as an entity property so future Wardline-authored findings can be cross-referenced back to Loomweave entities. +- **Ingest path from `wardline.exceptions.json`**: the `location` string is parsed (`split("::", 1)` yields `{file_path, qualname}`); same mapping rule applies. Exception entries that can't be resolved (e.g., file moved, symbol renamed) emit `LMWV-INFRA-WARDLINE-EXCEPTION-UNRESOLVED` and persist as dangling records with `entity_id: null` so operators can fix the reference. +- **Ingest path from SARIF (`wardline.sarif.baseline.json`)**: SARIF `location.physicalLocation.artifactLocation.uri` is already POSIX-relative (Wardline's `_normalize_artifact_uri` at `sarif.py:233-246`); combined with `location.logicalLocations[].fullyQualifiedName` (when present) or `partialFingerprints` (when Wardline doesn't emit them, with Loomweave's own fingerprint) to produce an `EntityId`. Unresolved SARIF results carry `metadata.loomweave.unresolved: true` through translation. -Clarion does *not* push a unified identity scheme into Wardline. Wardline remains authoritative for its own qualnames; Clarion maintains a translation layer. This preserves Principle 3 (plugin owns ontology) — Wardline's identity scheme is its own concern; Clarion's concern is producing a reliable join. +Loomweave does *not* push a unified identity scheme into Wardline. Wardline remains authoritative for its own qualnames; Loomweave maintains a translation layer. This preserves Principle 3 (plugin owns ontology) — Wardline's identity scheme is its own concern; Loomweave's concern is producing a reliable join. -The v0.2 **Wardline annotation descriptor** (§9, promoted from earlier deferral) is an opportunity to also standardise qualname emission: if Wardline starts publishing qualnames in a descriptor alongside its REGISTRY, Clarion's reconciliation simplifies from a heuristic map to a direct lookup. +The v0.2 **Wardline annotation descriptor** (§9, promoted from earlier deferral) is an opportunity to also standardise qualname emission: if Wardline starts publishing qualnames in a descriptor alongside its REGISTRY, Loomweave's reconciliation simplifies from a heuristic map to a direct lookup. --- @@ -560,7 +560,7 @@ The v0.2 **Wardline annotation descriptor** (§9, promoted from earlier deferral - Graph algorithms (Leiden, weighted-components fallback, centrality, path-finding) run in Rust against in-memory projections; the store serves neighbour lookups. - Consult-mode queries are overwhelmingly one-hop (callers, callees, contains) — indexed range scans. -- WAL mode with a **writer-actor** (single owner of the write connection; see Concurrency below) permits `clarion serve` to keep answering reads while `clarion analyze` is ingesting and while consult-mode cache writes happen. +- WAL mode with a **writer-actor** (single owner of the write connection; see Concurrency below) permits `loomweave serve` to keep answering reads while `loomweave analyze` is ingesting and while consult-mode cache writes happen. - JSON1 handles plugin property bags without giving up query ergonomics. - FTS5 handles text search across names, summaries, content. - Single-file, mature, debuggable with `sqlite3` / Datasette / VSCode extensions. @@ -573,7 +573,7 @@ Alternatives considered and rejected for v0.1: ### Schema (outline) -This outline reflects migrations `0001`–`0007` under `crates/clarion-storage/migrations/`. +This outline reflects migrations `0001`–`0007` under `crates/loomweave-storage/migrations/`. The physical schema is **13 tables + 1 FTS5 virtual table (`entity_fts`) + 1 view (`guidance_sheets`)**. The runner's own bookkeeping table, `schema_migrations`, is deliberately not shown here (it is migration-runner internal, not part of the data model). @@ -817,7 +817,7 @@ SELECT id, name, FROM entities WHERE kind = 'guidance'; -- Wardline taint-fact store (SP9, ADR-036, migration 0003). Wardline-owned, --- per-entity; `wardline_json` is opaque to Clarion (stored/returned verbatim). +-- per-entity; `wardline_json` is opaque to Loomweave (stored/returned verbatim). -- `sei` (migration 0006) is the rename-stable lookup key (NULL on pre-SEI rows). CREATE TABLE wardline_taint_facts ( entity_id TEXT PRIMARY KEY REFERENCES entities(id) ON DELETE CASCADE, @@ -843,7 +843,7 @@ CREATE TABLE sei_prior_index ( -- decoupled from the cumulative `entities` table so a rename-carry never -- collides with the stale entity row. Orphaning is a status flip, not a delete. CREATE TABLE sei_bindings ( - sei TEXT PRIMARY KEY, -- clarion:eid: (opaque; consumers MUST NOT parse) + sei TEXT PRIMARY KEY, -- loomweave:eid: (opaque; consumers MUST NOT parse) current_locator TEXT, -- current address: the alive binding's entity id body_hash TEXT, -- entities.content_hash at last (re)bind signature TEXT, -- entities.signature at last (re)bind @@ -875,12 +875,12 @@ CREATE INDEX ix_sei_lineage_sei ON sei_lineage(sei); **Writer-actor model** (single writer task owns the sole write connection; all other tasks submit mutations through a bounded channel): - WAL mode: `PRAGMA journal_mode = WAL`, `synchronous = NORMAL`, `busy_timeout = 5000ms`, `wal_autocheckpoint = 1000` (default). -- `clarion analyze` and `clarion serve` each instantiate exactly one **writer actor** task (a `tokio::task` owning a dedicated `rusqlite::Connection`). All mutations route through a bounded `mpsc::Sender` with backpressure. There is no in-process write contention; there are no cross-process writers because `clarion analyze` and `clarion serve` don't run the same DB concurrently in v0.1 (see operational posture below). -- **Transaction scope**: `clarion analyze` commits on a rolling boundary of **N writes per transaction** (default `N=50`). This keeps the WAL bounded and lets SQLite checkpointing run between batches. Per ADR-041, v1.x `--resume` is idempotent same-run re-emit, not durable phase/file checkpoint recovery. A full-batch single transaction is explicitly not used. -- **Consult-mode writes** (summary cache, session state) during `clarion serve` are dispatched on the same writer actor; they interleave with analyze-time writes if a user starts `clarion analyze` against a running `clarion serve` (not recommended but survivable). Writes are applied in arrival order; no starvation because consult writes are tiny and sparse. +- `loomweave analyze` and `loomweave serve` each instantiate exactly one **writer actor** task (a `tokio::task` owning a dedicated `rusqlite::Connection`). All mutations route through a bounded `mpsc::Sender` with backpressure. There is no in-process write contention; there are no cross-process writers because `loomweave analyze` and `loomweave serve` don't run the same DB concurrently in v0.1 (see operational posture below). +- **Transaction scope**: `loomweave analyze` commits on a rolling boundary of **N writes per transaction** (default `N=50`). This keeps the WAL bounded and lets SQLite checkpointing run between batches. Per ADR-041, v1.x `--resume` is idempotent same-run re-emit, not durable phase/file checkpoint recovery. A full-batch single transaction is explicitly not used. +- **Consult-mode writes** (summary cache, session state) during `loomweave serve` are dispatched on the same writer actor; they interleave with analyze-time writes if a user starts `loomweave analyze` against a running `loomweave serve` (not recommended but survivable). Writes are applied in arrival order; no starvation because consult writes are tiny and sparse. - **Readers** (plugin processes, MCP tool calls, HTTP API handlers, the markdown renderer) open read-only `rusqlite` connections from a `deadpool-sqlite` pool (configurable max: default 16). WAL lets them read against the committed snapshot without blocking writers. -- **Checkpointing**: truncate-mode checkpoint issued after each 10 analyze-transactions or after `clarion analyze` completes, whichever comes first. -- **Operational posture (v0.1)**: running `clarion analyze` and `clarion serve` against the same `.clarion/clarion.db` simultaneously is supported but `clarion serve` will observe stale read-snapshots until the analyze finishes and checkpoint completes. `clarion serve` emits a `CLA-INFRA-STALE-SNAPSHOT` finding when this is detected. Users wanting zero-stale reads during long analyze runs should prefer the "shadow DB + atomic swap" pattern (analyze writes to `.clarion/clarion.db.new`, atomic rename on completion) — available via `clarion analyze --shadow-db` flag. +- **Checkpointing**: truncate-mode checkpoint issued after each 10 analyze-transactions or after `loomweave analyze` completes, whichever comes first. +- **Operational posture (v0.1)**: running `loomweave analyze` and `loomweave serve` against the same `.loomweave/loomweave.db` simultaneously is supported but `loomweave serve` will observe stale read-snapshots until the analyze finishes and checkpoint completes. `loomweave serve` emits a `LMWV-INFRA-STALE-SNAPSHOT` finding when this is detected. Users wanting zero-stale reads during long analyze runs should prefer the "shadow DB + atomic swap" pattern (analyze writes to `.loomweave/loomweave.db.new`, atomic rename on completion) — available via `loomweave analyze --shadow-db` flag. **Why not a single write transaction for the whole batch**: long transactions pin the WAL and prevent checkpoint; WAL growth is unbounded; readers pinned to the pre-analyse snapshot can't advance; SQLite `database is locked` errors surface to consult-mode writes. Per-batch transactions are the industry-standard posture for this workload. @@ -898,55 +898,55 @@ CREATE INDEX ix_sei_lineage_sei ON sei_lineage(sei); ### File layout ``` -/.clarion/ - clarion.db # main store (WAL files beside it) +/.loomweave/ + loomweave.db # main store (WAL files beside it) config.json # internal state: schema version, last run IDs - clarion.log # structured log + loomweave.log # structured log runs/ / - config.yaml # snapshot of clarion.yaml at run time + config.yaml # snapshot of loomweave.yaml at run time log.jsonl # per-run log stats.json # run statistics partial.json # not part of the v1.x resume contract (ADR-041) -~/.config/clarion/ # user-level +~/.config/loomweave/ # user-level providers.toml # API keys, model tier mappings plugins.toml # plugin registry defaults.yaml # default policy overrides ``` -`.clarion/` is checked into git (consistent with Filigree's pattern and with the "shared analysis state" principle). SQLite files can diff poorly, so v0.1 ships **two features** for multi-developer teams to handle the committed DB: +`.loomweave/` is checked into git (consistent with Filigree's pattern and with the "shared analysis state" principle). SQLite files can diff poorly, so v0.1 ships **two features** for multi-developer teams to handle the committed DB: -- `clarion db export --textual ` — emits a deterministic JSON tree: `entities.jsonl` (one entity per line, sorted by id), `edges.jsonl` (sorted by `(kind, from_id, to_id)`), `guidance.jsonl` (sorted by id), `findings.jsonl` (sorted by id). Summary cache is **excluded** (re-derivable on next run, and JSON-diffing thousands of LLM-generated briefings is not useful). Output is git-friendly: a one-entity change produces a one-line diff. -- `clarion db merge-helper --output merged.db` — applied as a Git merge driver or manually during conflict resolution. Strategy: textual export of each side, deterministic union of entities/edges (last-writer-wins on conflicts keyed by `updated_at`), guidance-sheet conflict surfaced with a `CONFLICT` marker per affected sheet (human must resolve), summary cache cleared (will rebuild). +- `loomweave db export --textual ` — emits a deterministic JSON tree: `entities.jsonl` (one entity per line, sorted by id), `edges.jsonl` (sorted by `(kind, from_id, to_id)`), `guidance.jsonl` (sorted by id), `findings.jsonl` (sorted by id). Summary cache is **excluded** (re-derivable on next run, and JSON-diffing thousands of LLM-generated briefings is not useful). Output is git-friendly: a one-entity change produces a one-line diff. +- `loomweave db merge-helper --output merged.db` — applied as a Git merge driver or manually during conflict resolution. Strategy: textual export of each side, deterministic union of entities/edges (last-writer-wins on conflicts keyed by `updated_at`), guidance-sheet conflict surfaced with a `CONFLICT` marker per affected sheet (human must resolve), summary cache cleared (will rebuild). -Users can opt out entirely: `clarion.yaml:storage.commit_db: false` excludes the DB from the commit and Clarion emits `clarion db sync push/pull` commands (v0.1: scp-based; v0.2: S3/git-lfs/HTTP), but the default is still commit-the-DB because the solo-developer and small-team cases benefit from having briefings versioned alongside the code they describe. +Users can opt out entirely: `loomweave.yaml:storage.commit_db: false` excludes the DB from the commit and Loomweave emits `loomweave db sync push/pull` commands (v0.1: scp-based; v0.2: S3/git-lfs/HTTP), but the default is still commit-the-DB because the solo-developer and small-team cases benefit from having briefings versioned alongside the code they describe. **Git merge-driver registration** (optional, recommended for teams): ``` # .gitattributes -.clarion/clarion.db merge=clarion-db +.loomweave/loomweave.db merge=loomweave-db # .git/config (or per-developer) -[merge "clarion-db"] - name = Clarion DB merger - driver = clarion db merge-helper --output %A %A %B %O +[merge "loomweave-db"] + name = Loomweave DB merger + driver = loomweave db merge-helper --output %A %A %B %O ``` -With the driver registered, conflicting runs from two developers produce a deterministic merged DB at commit time; without it, operators resolve manually via `clarion db export --textual` on both sides plus `clarion db import --textual `. +With the driver registered, conflicting runs from two developers produce a deterministic merged DB at commit time; without it, operators resolve manually via `loomweave db export --textual` on both sides plus `loomweave db import --textual `. -**Git-commit caveats for `.clarion/clarion.db`**: +**Git-commit caveats for `.loomweave/loomweave.db`**: - LLM-derived content (briefings, guidance body text) lives in the DB and is therefore committed. Content derived from source files redacted by the pre-ingest secret scanner never reaches the LLM in the first place, so briefings don't contain secret material. Briefings that *describe* security-sensitive code (e.g., "this module is the JWT verifier") are fine to commit — they're public documentation. -- `runs//log.jsonl` records raw LLM request/response bodies for audit. This log is **excluded** from git by default via `.clarion/.gitignore` (`runs/*/log.jsonl`) because those bodies may contain source excerpts that are fine to ship to Anthropic but not appropriate to commit to a public repo. Users opting in to commit run logs must accept that posture explicitly. -- Operational rollouts where the DB is private-not-shared (single-developer experiments, pre-publication audits) can set `clarion.yaml:storage.commit_db: false` and the DB is `.gitignore`'d instead. +- `runs//log.jsonl` records raw LLM request/response bodies for audit. This log is **excluded** from git by default via `.loomweave/.gitignore` (`runs/*/log.jsonl`) because those bodies may contain source excerpts that are fine to ship to Anthropic but not appropriate to commit to a public repo. Users opting in to commit run logs must accept that posture explicitly. +- Operational rollouts where the DB is private-not-shared (single-developer experiments, pre-publication audits) can set `loomweave.yaml:storage.commit_db: false` and the DB is `.gitignore`'d instead. ### Migration strategy - Migrations as numbered files embedded in the binary (`refinery` or similar). -- Applied on every `clarion analyze` / `clarion serve` startup. -- Never drop data without explicit `clarion db migrate --destructive`. +- Applied on every `loomweave analyze` / `loomweave serve` startup. +- Never drop data without explicit `loomweave db migrate --destructive`. - Schema version recorded in `config.json` and a `_schema_version` table. ### What the store does NOT hold @@ -954,13 +954,13 @@ With the driver registered, conflicting runs from two developers produce a deter - Raw source code (stored via reference; plugins read files on demand). - Compiled ASTs (plugins regenerate per session). - Raw LLM API responses (logged to `runs//log.jsonl` for audit, not the main store). -- Filigree issue content (Clarion holds only association IDs; Filigree is authoritative for issue data). +- Filigree issue content (Loomweave holds only association IDs; Filigree is authoritative for issue data). --- ## 4. Policy Engine — Config & Caching Internals -### Full `clarion.yaml` example +### Full `loomweave.yaml` example ```yaml version: 1 @@ -1032,9 +1032,9 @@ llm_policy: providers: anthropic: api_key_env: ANTHROPIC_API_KEY - # Tier mapping pinned against the anthropic Python SDK / Rust SDK that Clarion + # Tier mapping pinned against the anthropic Python SDK / Rust SDK that Loomweave # builds against. These IDs must be verified against the SDK's documented - # model-ID list on each Clarion release; a mismatch fails every LLM call. + # model-ID list on each Loomweave release; a mismatch fails every LLM call. # The implementation plan records the SDK version and CI guards the match. tier_mapping: haiku: claude-haiku-4-5 # latest Haiku 4.x as of 2026-04 @@ -1085,18 +1085,18 @@ Cache key: `(entity_id, content_hash, prompt_template_id, model_tier, guidance_f **Semantic staleness** — three known paths the key alone does not see: -1. **Graph-neighborhood drift**: if an entity's call-graph neighborhood shifts materially (e.g., it becomes a hot path) without its own text changing, its `risks` and `relationships` fields may be out of date. Invalidation trigger: store `caller_count` and `fan_out` in the summary cache row; flag cache entries whose neighborhood has shifted by more than 50% (configurable via `clarion.yaml:llm_policy.caching.neighborhood_drift_threshold`) as `stale_semantic: true`. Flagged entries are served with a header indicating staleness; the next `clarion analyze` refreshes them. +1. **Graph-neighborhood drift**: if an entity's call-graph neighborhood shifts materially (e.g., it becomes a hot path) without its own text changing, its `risks` and `relationships` fields may be out of date. Invalidation trigger: store `caller_count` and `fan_out` in the summary cache row; flag cache entries whose neighborhood has shifted by more than 50% (configurable via `loomweave.yaml:llm_policy.caching.neighborhood_drift_threshold`) as `stale_semantic: true`. Flagged entries are served with a header indicating staleness; the next `loomweave analyze` refreshes them. 2. **Model-identity drift**: a tier name (`sonnet`) mapping to a new concrete model version (`claude-sonnet-4-6` → `claude-sonnet-4-7`). The cache row already stores the concrete model; the tier resolver compares on write and treats a mismatch as a cache miss. No special handling needed — this is already correct. 3. **Guidance-worldview drift**: a guidance sheet whose text is unchanged but whose underlying assumptions have gone stale. No automated signal; surfaced via staleness review and git-churn-based findings. `reviewed_at` gives operators a handle. -**TTL backstop**: summary cache rows older than `clarion.yaml:llm_policy.caching.max_age_days` (default: 180 days) are invalidated unconditionally on next query. This bounds the time a silently-stale briefing can influence agents; 180 days is long enough that cache hit rates stay high during active development and short enough that stale models don't persist across a full Anthropic model generation. +**TTL backstop**: summary cache rows older than `loomweave.yaml:llm_policy.caching.max_age_days` (default: 180 days) are invalidated unconditionally on next query. This bounds the time a silently-stale briefing can influence agents; 180 days is long enough that cache hit rates stay high during active development and short enough that stale models don't persist across a full Anthropic model generation. **TTL interacts asymmetrically with stale pinned guidance**, which is a sharp edge worth naming: invalidating a briefing at day 180 forces a fresh LLM call — but that fresh call still composes the *same* stale `pinned: true` guidance sheet if the operator hasn't re-reviewed it. The briefing is fresh; its framing is not. v0.1 mitigations: -1. **Churn-triggered cache invalidation for guidance-stale entities**: when `CLA-FACT-GUIDANCE-CHURN-STALE` fires (stale pinned sheet on a high-churn entity), the summary cache rows whose `guidance_fingerprint` includes that sheet are invalidated eagerly, not at TTL. The operator now sees churn-stale findings *and* feels pressure to act because cache misses start accruing cost. -2. **Stale-guidance flag on briefings**: briefings whose composed guidance includes a sheet with a `CLA-FACT-GUIDANCE-CHURN-STALE` or `CLA-FACT-GUIDANCE-EXPIRED` finding against it carry `briefing_guidance_may_be_stale: true` in the response envelope. Agents consuming the briefing can downweight its `risks` / `patterns` claims accordingly. +1. **Churn-triggered cache invalidation for guidance-stale entities**: when `LMWV-FACT-GUIDANCE-CHURN-STALE` fires (stale pinned sheet on a high-churn entity), the summary cache rows whose `guidance_fingerprint` includes that sheet are invalidated eagerly, not at TTL. The operator now sees churn-stale findings *and* feels pressure to act because cache misses start accruing cost. +2. **Stale-guidance flag on briefings**: briefings whose composed guidance includes a sheet with a `LMWV-FACT-GUIDANCE-CHURN-STALE` or `LMWV-FACT-GUIDANCE-EXPIRED` finding against it carry `briefing_guidance_may_be_stale: true` in the response envelope. Agents consuming the briefing can downweight its `risks` / `patterns` claims accordingly. --- @@ -1104,19 +1104,19 @@ Cache key: `(entity_id, content_hash, prompt_template_id, model_tier, guidance_f ### Phase-7 structural findings (v0.1) -These rules combine signals Clarion uniquely holds — clusters from Phase 3, Wardline tier declarations from Wardline ingest, and the prior-run entity set — into findings that neither Wardline nor Filigree can compute alone: +These rules combine signals Loomweave uniquely holds — clusters from Phase 3, Wardline tier declarations from Wardline ingest, and the prior-run entity set — into findings that neither Wardline nor Filigree can compute alone: | Rule | Severity | Confidence basis | Signal | |---|---|---|---| -| `CLA-FACT-TIER-SUBSYSTEM-MIXING` | WARN | heuristic | A subsystem has members declared across disagreeing tiers (e.g., 11 members `INTEGRAL`, 3 `GUARDED`). Either a misclassification Wardline can't see or a latent tier boundary worth naming. Emitted against the subsystem entity with `related_entities` listing the outliers. | -| `CLA-FACT-ENTITY-DELETED` | INFO | deterministic | Entity present in the previous run's catalog is absent in this run. Compared against prior run's entity set at Phase-7 boundary. Emitted per deleted entity. Surfaces silently orphaned Filigree issues, silently-no-op guidance sheets, and persistent-until-TTL cache rows. | -| `CLA-FACT-SUBSYSTEM-TIER-UNANIMOUS` | INFO (fact) | deterministic | Subsystem members share a uniform declared tier. Useful positive signal for tier-consistency reports; cheap companion to the mixing rule. | -| `CLA-FACT-GUIDANCE-EXPIRED` | INFO | deterministic | A guidance sheet's `expires` instant is in the past. The read path already excludes expired sheets from composition; this surfaces the state operatively (the sheet is not deleted). Emitted once per expired sheet, anchored to the sheet, on every analyze (independent of deletions/SEI). | -| `CLA-FACT-GUIDANCE-CHURN-STALE` | WARN | heuristic (0.7) | A guidance sheet covers high-churn code: the aggregate `git_churn_count` over the sheet's matched entities meets the staleness threshold (asymmetric — 20 for `pinned` sheets, 50 otherwise). Anchored to the sheet with matched entities as `related_entities`. Inert until the churn-history pipeline populates `git_churn_count` (proxy for the design's true "churn since `authored_at`/`reviewed_at`" delta). | +| `LMWV-FACT-TIER-SUBSYSTEM-MIXING` | WARN | heuristic | A subsystem has members declared across disagreeing tiers (e.g., 11 members `INTEGRAL`, 3 `GUARDED`). Either a misclassification Wardline can't see or a latent tier boundary worth naming. Emitted against the subsystem entity with `related_entities` listing the outliers. | +| `LMWV-FACT-ENTITY-DELETED` | INFO | deterministic | Entity present in the previous run's catalog is absent in this run. Compared against prior run's entity set at Phase-7 boundary. Emitted per deleted entity. Surfaces silently orphaned Filigree issues, silently-no-op guidance sheets, and persistent-until-TTL cache rows. | +| `LMWV-FACT-SUBSYSTEM-TIER-UNANIMOUS` | INFO (fact) | deterministic | Subsystem members share a uniform declared tier. Useful positive signal for tier-consistency reports; cheap companion to the mixing rule. | +| `LMWV-FACT-GUIDANCE-EXPIRED` | INFO | deterministic | A guidance sheet's `expires` instant is in the past. The read path already excludes expired sheets from composition; this surfaces the state operatively (the sheet is not deleted). Emitted once per expired sheet, anchored to the sheet, on every analyze (independent of deletions/SEI). | +| `LMWV-FACT-GUIDANCE-CHURN-STALE` | WARN | heuristic (0.7) | A guidance sheet covers high-churn code: the aggregate `git_churn_count` over the sheet's matched entities meets the staleness threshold (asymmetric — 20 for `pinned` sheets, 50 otherwise). Anchored to the sheet with matched entities as `related_entities`. Inert until the churn-history pipeline populates `git_churn_count` (proxy for the design's true "churn since `authored_at`/`reviewed_at`" delta). | **Why these belong in Phase 7, not the plugin's Phase-1 emission**: the rules depend on clustering output (Phase 3) and prior-run state, which are core-side concerns. Emitting them from the plugin would require the plugin to know about subsystems and prior runs — violation of Principle 3. -**`CLA-FACT-TIER-SUBSYSTEM-MIXING` threshold**: default `min_outlier_count: 2` and `min_outlier_fraction: 0.1` (i.e., at least 2 outliers AND at least 10% of subsystem members). Configurable via `clarion.yaml:analysis.clustering.tier_mixing_thresholds`. Tuned to avoid flagging subsystems where a single outlier is legitimate boundary-infrastructure (e.g., an `EXTERNAL_RAW` parser adjacent to an otherwise-`INTEGRAL` validation subsystem). +**`LMWV-FACT-TIER-SUBSYSTEM-MIXING` threshold**: default `min_outlier_count: 2` and `min_outlier_fraction: 0.1` (i.e., at least 2 outliers AND at least 10% of subsystem members). Configurable via `loomweave.yaml:analysis.clustering.tier_mixing_thresholds`. Tuned to avoid flagging subsystems where a single outlier is legitimate boundary-infrastructure (e.g., an `EXTERNAL_RAW` parser adjacent to an otherwise-`INTEGRAL` validation subsystem). ### Pre-ingest secret-scanning findings (WP5) @@ -1124,19 +1124,19 @@ These core-emitted rules are the ADR-013 audit surface. ADR-013 remains canonica | Rule | Severity | Category | Description | Remediation | ADR | |---|---|---|---|---|---| -| `CLA-SEC-SECRET-DETECTED` | ERROR | security | Pre-ingest secret scanner detected a credential pattern in a file slated for LLM dispatch. | Remove the secret, rotate the credential, or whitelist via `.clarion/secrets-baseline.yaml` with a justification. | [ADR-013](../adr/ADR-013-pre-ingest-secret-scanner.md) | -| `CLA-SEC-UNREDACTED-SECRETS-ALLOWED` | ERROR | security | Operator invoked `--allow-unredacted-secrets`; file content reached the LLM provider with secrets intact. | Audit override usage via `filigree list --rule-id=CLA-SEC-UNREDACTED-SECRETS-ALLOWED --since 30d`. | [ADR-013](../adr/ADR-013-pre-ingest-secret-scanner.md) | -| `CLA-INFRA-SECRET-BASELINE-NO-JUSTIFICATION` | ERROR | infra | Baseline entry missing required `justification` field; entry not honoured. | Add a `justification` string explaining why the match is safe. | [ADR-013](../adr/ADR-013-pre-ingest-secret-scanner.md) | -| `CLA-INFRA-SECRET-BASELINE-MATCH` | INFO | infra | Baseline entry suppressed a scanner detection as an audit event. | None; informational and retained for `NFR-SEC-04` audit. | [ADR-013](../adr/ADR-013-pre-ingest-secret-scanner.md) | -| `CLA-INFRA-SECRET-OVERRIDE-UNCONFIRMED` | ERROR | infra | `--allow-unredacted-secrets` supplied without confirmation; run aborted before start. | Supply `--confirm-allow-unredacted-secrets=yes-i-understand` in non-TTY contexts or run interactively. | [ADR-013](../adr/ADR-013-pre-ingest-secret-scanner.md) | +| `LMWV-SEC-SECRET-DETECTED` | ERROR | security | Pre-ingest secret scanner detected a credential pattern in a file slated for LLM dispatch. | Remove the secret, rotate the credential, or whitelist via `.loomweave/secrets-baseline.yaml` with a justification. | [ADR-013](../adr/ADR-013-pre-ingest-secret-scanner.md) | +| `LMWV-SEC-UNREDACTED-SECRETS-ALLOWED` | ERROR | security | Operator invoked `--allow-unredacted-secrets`; file content reached the LLM provider with secrets intact. | Audit override usage via `filigree list --rule-id=LMWV-SEC-UNREDACTED-SECRETS-ALLOWED --since 30d`. | [ADR-013](../adr/ADR-013-pre-ingest-secret-scanner.md) | +| `LMWV-INFRA-SECRET-BASELINE-NO-JUSTIFICATION` | ERROR | infra | Baseline entry missing required `justification` field; entry not honoured. | Add a `justification` string explaining why the match is safe. | [ADR-013](../adr/ADR-013-pre-ingest-secret-scanner.md) | +| `LMWV-INFRA-SECRET-BASELINE-MATCH` | INFO | infra | Baseline entry suppressed a scanner detection as an audit event. | None; informational and retained for `NFR-SEC-04` audit. | [ADR-013](../adr/ADR-013-pre-ingest-secret-scanner.md) | +| `LMWV-INFRA-SECRET-OVERRIDE-UNCONFIRMED` | ERROR | infra | `--allow-unredacted-secrets` supplied without confirmation; run aborted before start. | Supply `--confirm-allow-unredacted-secrets=yes-i-understand` in non-TTY contexts or run interactively. | [ADR-013](../adr/ADR-013-pre-ingest-secret-scanner.md) | ### Entity-set diff (deletion detection) -At Phase 7, Clarion compares the current run's entity IDs against the prior run's set (read from the prior `run_id`'s stats; if no prior run exists, this phase is a no-op). For each entity ID present before and absent now: +At Phase 7, Loomweave compares the current run's entity IDs against the prior run's set (read from the prior `run_id`'s stats; if no prior run exists, this phase is a no-op). For each entity ID present before and absent now: -- Emit `CLA-FACT-ENTITY-DELETED` against a synthetic deletion marker (`core:deleted:{former_entity_id}`). +- Emit `LMWV-FACT-ENTITY-DELETED` against a synthetic deletion marker (`core:deleted:{former_entity_id}`). - Surface on Filigree the deletion so that Filigree issues carrying the orphan ID can be triaged. -- Guidance sheets with explicit-entity `match_rules` pointing at the deleted ID emit `CLA-FACT-GUIDANCE-ORPHAN` with confidence: deterministic and with a pointer to the deleted-entity finding. +- Guidance sheets with explicit-entity `match_rules` pointing at the deleted ID emit `LMWV-FACT-GUIDANCE-ORPHAN` with confidence: deterministic and with a pointer to the deleted-entity finding. - Summary cache rows for the deleted entity are invalidated (no new briefings can be produced; reads against the old ID return a deletion marker). Silent-fallback principle compliance: before Rev 4 this path was silent. Now every deletion produces two findings (the deletion itself and an orphan finding per affected guidance sheet) so operators cannot miss the transition. @@ -1145,22 +1145,22 @@ Silent-fallback principle compliance: before Rev 4 this path was silent. Now eve | Failure | Recovery | |---|---| -| Plugin parse error on file | `CLA-PY-PARSE-ERROR`; skip file; continue | -| Plugin timeout (default 30s) | `CLA-PY-TIMEOUT`; skip file; continue | -| Plugin process crash | Core restarts plugin; resumes at next file; `CLA-INFRA-PLUGIN-CRASH` | +| Plugin parse error on file | `LMWV-PY-PARSE-ERROR`; skip file; continue | +| Plugin timeout (default 30s) | `LMWV-PY-TIMEOUT`; skip file; continue | +| Plugin process crash | Core restarts plugin; resumes at next file; `LMWV-INFRA-PLUGIN-CRASH` | | LLM rate limit | Exponential backoff with jitter; retry up to `max_retries` | -| LLM non-transient error | `CLA-INFRA-LLM-ERROR`; skip entity; continue | -| Budget exceeded (`on_exceed: warn`) | Log warning; emit `CLA-INFRA-BUDGET-WARNING`; continue | -| Budget exceeded (`on_exceed: stop`) | Halt dispatch; emit `CLA-INFRA-BUDGET-EXCEEDED`; write partial manifest | -| Plugin crashes >10% of files | Abort; `CLA-INFRA-ANALYSIS-ABORTED`; run marked failed | +| LLM non-transient error | `LMWV-INFRA-LLM-ERROR`; skip entity; continue | +| Budget exceeded (`on_exceed: warn`) | Log warning; emit `LMWV-INFRA-BUDGET-WARNING`; continue | +| Budget exceeded (`on_exceed: stop`) | Halt dispatch; emit `LMWV-INFRA-BUDGET-EXCEEDED`; write partial manifest | +| Plugin crashes >10% of files | Abort; `LMWV-INFRA-ANALYSIS-ABORTED`; run marked failed | No silent fallbacks. Every failure produces a finding. ### Example run ``` -$ clarion analyze /home/john/elspeth - Reading config from /home/john/elspeth/clarion.yaml ✓ +$ loomweave analyze /home/john/elspeth + Reading config from /home/john/elspeth/loomweave.yaml ✓ Loading plugin: python (v0.1.0) ✓ Phase 0: discovery + dry-run Files: 1,126 Python files @@ -1175,8 +1175,8 @@ $ clarion analyze /home/john/elspeth Phase 6: subsystem (opus) [████████████████] 43/43 0:06:18 $4.91 Phase 7: cross-cutting (wardline ingest) ✓ (0.4s) Phase 8: emission - Catalog: .clarion/catalog.json (4.1 MB) - Markdown: .clarion/catalog/*.md (51 files) + Catalog: .loomweave/catalog.json (4.1 MB) + Markdown: .loomweave/catalog/*.md (51 files) Findings: 137 (127 facts, 10 defects) Filigree observations pushed: 2 Done in 0:38:12, total cost $11.37 @@ -1293,58 +1293,58 @@ enum ScopeLens { ## 7. Integrations — Implementation Detail -### Severity mapping (Clarion internal ↔ Filigree wire) +### Severity mapping (Loomweave internal ↔ Filigree wire) -Clarion's finding records use an internal `{INFO, WARN, ERROR, CRITICAL}` vocabulary for defects plus `NONE` for facts; Filigree's intake accepts `{critical, high, medium, low, info}`. Non-matching values are coerced to `"info"` with a warning in the response. The mapping: +Loomweave's finding records use an internal `{INFO, WARN, ERROR, CRITICAL}` vocabulary for defects plus `NONE` for facts; Filigree's intake accepts `{critical, high, medium, low, info}`. Non-matching values are coerced to `"info"` with a warning in the response. The mapping: -| Clarion internal | Filigree wire | Reverse (on read-back) | +| Loomweave internal | Filigree wire | Reverse (on read-back) | |---|---|---| | `CRITICAL` | `critical` | `CRITICAL` | | `ERROR` | `high` | `ERROR` | | `WARN` | `medium` | `WARN` | | `INFO` | `info` | `INFO` | -| `NONE` (facts) | `info` (with `metadata.clarion.kind = "fact"`) | `NONE` | +| `NONE` (facts) | `info` (with `metadata.loomweave.kind = "fact"`) | `NONE` | -The Clarion internal value is preserved in `metadata.clarion.internal_severity` for lossless round-trip. Clarion's read-back path consults `metadata.clarion.internal_severity` first and falls back to `severity` only if the metadata is absent (e.g., for findings ingested from Wardline or other sources that don't set Clarion-specific metadata). +The Loomweave internal value is preserved in `metadata.loomweave.internal_severity` for lossless round-trip. Loomweave's read-back path consults `metadata.loomweave.internal_severity` first and falls back to `severity` only if the metadata is absent (e.g., for findings ingested from Wardline or other sources that don't set Loomweave-specific metadata). ### Rule-ID round-trip policy Rule IDs are namespaced per-tool and free-form within a namespace: -- `CLA-PY-STRUCTURE-001`, `CLA-FACT-ENTRYPOINT`, `CLA-INFRA-*`, `CLA-SEC-*` (Clarion) +- `LMWV-PY-STRUCTURE-001`, `LMWV-FACT-ENTRYPOINT`, `LMWV-INFRA-*`, `LMWV-SEC-*` (Loomweave) - `PY-WL-001-GOVERNED-DEFAULT`, `SUP-001-*`, `SCN-*`, `TOOL-ERROR` (Wardline — verified against `scanner/rules/` and `wardline-sarif-extensions.schema.json`) - `COV-*` (future coverage scanner) - `SEC-*` (future security scanner) -Filigree's `rule_id` column is a free-text string — no enum enforcement. Clarion's and Wardline's rule IDs round-trip byte-for-byte. Clarion's consult tools re-namespace on display: findings with `scan_source="wardline"` surface as "Wardline/PY-WL-001-GOVERNED-DEFAULT"; findings with `scan_source="clarion"` surface unprefixed. The rule-ID prefix convention is a suite-wide social convention; there is no code enforcement. +Filigree's `rule_id` column is a free-text string — no enum enforcement. Loomweave's and Wardline's rule IDs round-trip byte-for-byte. Loomweave's consult tools re-namespace on display: findings with `scan_source="wardline"` surface as "Wardline/PY-WL-001-GOVERNED-DEFAULT"; findings with `scan_source="loomweave"` surface unprefixed. The rule-ID prefix convention is a suite-wide social convention; there is no code enforcement. ### `scan_run_id` lifecycle Filigree's `scan_run_id` is optional on ingest. If absent, findings are inserted without run-lifecycle tracking. If present and `complete_scan_run=true`, Filigree closes the run on receipt; unknown IDs log a warning and proceed. -**Clarion v0.1 policy**: Clarion's `run_id` (§2, `Source.run_id`) maps 1:1 onto Filigree's `scan_run_id`. Clarion calls Filigree's `create_scan_run` MCP tool at Phase 0 of `clarion analyze` before posting the first finding batch, and posts with `complete_scan_run=false` on intermediate batches, `complete_scan_run=true` on the final batch. On resume (`clarion analyze --resume`), Clarion re-uses the same `run_id` and posts batches with `mark_unseen=false` to avoid triggering Filigree's "no longer seen" transitions. Failure to create the scan run (e.g., Filigree unreachable) emits `CLA-INFRA-FILIGREE-UNREACHABLE` and Clarion continues posting without a `scan_run_id`, losing run-lifecycle coordination for that `clarion analyze` invocation. +**Loomweave v0.1 policy**: Loomweave's `run_id` (§2, `Source.run_id`) maps 1:1 onto Filigree's `scan_run_id`. Loomweave calls Filigree's `create_scan_run` MCP tool at Phase 0 of `loomweave analyze` before posting the first finding batch, and posts with `complete_scan_run=false` on intermediate batches, `complete_scan_run=true` on the final batch. On resume (`loomweave analyze --resume`), Loomweave re-uses the same `run_id` and posts batches with `mark_unseen=false` to avoid triggering Filigree's "no longer seen" transitions. Failure to create the scan run (e.g., Filigree unreachable) emits `LMWV-INFRA-FILIGREE-UNREACHABLE` and Loomweave continues posting without a `scan_run_id`, losing run-lifecycle coordination for that `loomweave analyze` invocation. ### Dedup collision when entities move within a file -Filigree's dedup key is `(file_id, scan_source, rule_id, coalesce(line_start, -1))`. Two consecutive `clarion analyze` runs emitting the same rule for the same entity at two different line numbers (because the entity moved in the file) produce **two findings**, not one. The older one eventually transitions to `unseen_in_latest` if `mark_unseen=true`; otherwise it persists. +Filigree's dedup key is `(file_id, scan_source, rule_id, coalesce(line_start, -1))`. Two consecutive `loomweave analyze` runs emitting the same rule for the same entity at two different line numbers (because the entity moved in the file) produce **two findings**, not one. The older one eventually transitions to `unseen_in_latest` if `mark_unseen=true`; otherwise it persists. -**Clarion v0.1 policy**: rule IDs are *not* synthesised with entity IDs (that would defeat Filigree's cross-run triage). Instead, Clarion posts with `mark_unseen=true` so that old-position findings for the same rule on the same file transition to `unseen_in_latest` when the new run reports them at a different line. Operators reading findings should expect the `unseen_in_latest` status to accumulate for entities that frequently move; `clarion analyze --prune-unseen` removes stale `unseen_in_latest` findings older than 30 days (configurable). +**Loomweave v0.1 policy**: rule IDs are *not* synthesised with entity IDs (that would defeat Filigree's cross-run triage). Instead, Loomweave posts with `mark_unseen=true` so that old-position findings for the same rule on the same file transition to `unseen_in_latest` when the new run reports them at a different line. Operators reading findings should expect the `unseen_in_latest` status to accumulate for entities that frequently move; `loomweave analyze --prune-unseen` removes stale `unseen_in_latest` findings older than 30 days (configurable). This is an acknowledged coarseness. A v0.2 improvement would push per-entity dedup into Filigree as a server-side option — see §9 Filigree prerequisites (future-state). ### Commit-ref and dirty-tree handling -Wardline SARIF includes `wardline.commitRef` with a `-dirty` suffix when the tree is modified. Clarion adopts the same convention: +Wardline SARIF includes `wardline.commitRef` with a `-dirty` suffix when the tree is modified. Loomweave adopts the same convention: -- `clarion analyze` records the current HEAD SHA plus a `-dirty` suffix if `git status --porcelain` returns non-empty. Recorded in `runs//stats.json` as `commit_ref`, propagated onto every emitted finding's `metadata.clarion.commit_ref`. -- Dirty-tree runs are permitted (operators frequently run against in-progress code) but emit `CLA-INFRA-DIRTY-TREE-RUN` at INFO severity for provenance. -- `clarion analyze --require-clean` refuses dirty runs; used in CI contexts where provenance must be strict. +- `loomweave analyze` records the current HEAD SHA plus a `-dirty` suffix if `git status --porcelain` returns non-empty. Recorded in `runs//stats.json` as `commit_ref`, propagated onto every emitted finding's `metadata.loomweave.commit_ref`. +- Dirty-tree runs are permitted (operators frequently run against in-progress code) but emit `LMWV-INFRA-DIRTY-TREE-RUN` at INFO severity for provenance. +- `loomweave analyze --require-clean` refuses dirty runs; used in CI contexts where provenance must be strict. ### SARIF → Filigree translator -External SARIF is a standing need for the suite regardless of Wardline's emission posture: Semgrep, CodeQL, Trivy, and other third-party scanners all emit SARIF; Filigree's native intake is not SARIF. A **SARIF → Filigree translator is a permanent part of Clarion's feature set**, not a Wardline-bridge workaround. +External SARIF is a standing need for the suite regardless of Wardline's emission posture: Semgrep, CodeQL, Trivy, and other third-party scanners all emit SARIF; Filigree's native intake is not SARIF. A **SARIF → Filigree translator is a permanent part of Loomweave's feature set**, not a Wardline-bridge workaround. -v0.1 ships `clarion sarif import [--scan-source ]` as a CLI: +v0.1 ships `loomweave sarif import [--scan-source ]` as a CLI: - Maps SARIF `result.locations[].physicalLocation` to Filigree `path` + `line_start` + `line_end`. - Maps SARIF `result.level` (`error` / `warning` / `note`) to Filigree severity via `{error→high, warning→medium, note→info}`. @@ -1353,10 +1353,10 @@ v0.1 ships `clarion sarif import [--scan-source ]` as a CLI: **Wardline-specific ownership evolves; the translator remains**: -- **v0.1**: Wardline emits SARIF to disk as it does today; operators run `clarion sarif import wardline.sarif --scan-source wardline` (manually or from Clarion's `clarion analyze` post-hook). This is "ownership of the Wardline adapter lives Clarion-side." -- **v0.2+**: Wardline gains a native `POST /api/v1/scan-results` emitter, removing the need for Clarion-side Wardline translation (see §10 ADR-015). **The translator itself stays** — it handles Semgrep, CodeQL, and every other SARIF-emitting tool the project might adopt. +- **v0.1**: Wardline emits SARIF to disk as it does today; operators run `loomweave sarif import wardline.sarif --scan-source wardline` (manually or from Loomweave's `loomweave analyze` post-hook). This is "ownership of the Wardline adapter lives Loomweave-side." +- **v0.2+**: Wardline gains a native `POST /api/v1/scan-results` emitter, removing the need for Loomweave-side Wardline translation (see §10 ADR-015). **The translator itself stays** — it handles Semgrep, CodeQL, and every other SARIF-emitting tool the project might adopt. -What moves in v0.2 is *who owns the Wardline-specific mapping*, not whether SARIF translation exists. Framing this correctly matters: operators expect a SARIF ingest path indefinitely, and removing `clarion sarif import` when Wardline gains a native emitter would break that expectation for every other SARIF source. +What moves in v0.2 is *who owns the Wardline-specific mapping*, not whether SARIF translation exists. Framing this correctly matters: operators expect a SARIF ingest path indefinitely, and removing `loomweave sarif import` when Wardline gains a native emitter would break that expectation for every other SARIF source. ### Wardline state files — in/out decision @@ -1366,14 +1366,14 @@ Wardline persists ten state files beyond `wardline.yaml`. Recon identified each; |---|---|---| | `wardline.yaml` | YES | Already in scope (manifest: tiers, boundaries, groups). | | Overlays matching `src/**/wardline.overlay.yaml` | YES | Already in scope; supplementary group-1 and group-17 boundary declarations. | -| `wardline.fingerprint.json` | YES | Authoritative per-function state — aligns directly with Clarion's entity model. | -| `wardline.exceptions.json` | YES | Excepted-finding register with expiry. Clarion tags affected entities with `wardline.excepted` so agents see "this has an active exception, don't flag further" as part of the briefing. | -| `wardline.compliance.json` | NO (v0.2) | Compliance ledger — relevant to governance reports, not yet to Clarion's catalog shape. | +| `wardline.fingerprint.json` | YES | Authoritative per-function state — aligns directly with Loomweave's entity model. | +| `wardline.exceptions.json` | YES | Excepted-finding register with expiry. Loomweave tags affected entities with `wardline.excepted` so agents see "this has an active exception, don't flag further" as part of the briefing. | +| `wardline.compliance.json` | NO (v0.2) | Compliance ledger — relevant to governance reports, not yet to Loomweave's catalog shape. | | `wardline.conformance.json` | NO (v0.2) | Conformance gates — similar reasoning. | | `wardline.perimeter.baseline.json` | NO (v0.2) | Perimeter fingerprint for Wardline's own scans. | | `wardline.manifest.baseline.json` | NO (v0.2) | Manifest fingerprint for Wardline's own scans. | -| `wardline.retrospective-required.json` | NO (v0.2) | Retrospective state marker — relevant to Wardline reporting, orthogonal to Clarion's briefings. | -| `wardline.sarif.baseline.json` | YES (read-only, for SARIF→Filigree translator) | 663-result baseline; the translator reads this to emit findings to Filigree, not to alter Clarion's catalog directly. | +| `wardline.retrospective-required.json` | NO (v0.2) | Retrospective state marker — relevant to Wardline reporting, orthogonal to Loomweave's briefings. | +| `wardline.sarif.baseline.json` | YES (read-only, for SARIF→Filigree translator) | 663-result baseline; the translator reads this to emit findings to Filigree, not to alter Loomweave's catalog directly. | ### HTTP Read API — full endpoint list @@ -1412,11 +1412,11 @@ Response: } ``` -**404 behaviour**: returns `resolution_confidence: "none"` with an empty `entity_id` rather than HTTP 404. Lets callers distinguish "Clarion doesn't know this" from "Clarion is down." +**404 behaviour**: returns `resolution_confidence: "none"` with an empty `entity_id` rather than HTTP 404. Lets callers distinguish "Loomweave doesn't know this" from "Loomweave is down." ### Authentication — registry-backend HTTP read API -ADR-014 supersedes ADR-012 for the Filigree `registry_backend: clarion` +ADR-014 supersedes ADR-012 for the Filigree `registry_backend: loomweave` HTTP read surface. The active contract is unauthenticated loopback-only by default, with non-loopback binds refused unless `serve.http.allow_non_loopback: true` and protected by an operator-managed @@ -1428,7 +1428,7 @@ not the implementation contract for `/api/v1/_capabilities` or **Default — loopback only**: -- `clarion serve` binds only to loopback addresses. +- `loomweave serve` binds only to loopback addresses. - HTTP layer does not require `Authorization` headers for the ADR-014 endpoint. - `/api/v1/_capabilities` reports `api_version` and `instance_id` so sibling clients can detect compatible deployments and project-instance drift. @@ -1437,39 +1437,39 @@ not the implementation contract for `/api/v1/_capabilities` or - Requires `serve.http.allow_non_loopback: true`. - Startup logs warn that the surface is unauthenticated. -- Operators must front Clarion with authenticated ingress or equivalent access +- Operators must front Loomweave with authenticated ingress or equivalent access control before exposing the endpoint beyond loopback. **How sibling tools pick up access in CI**: - Same-host sibling tools use the loopback endpoint. - CI or remote deployments that need non-loopback access provide protection - outside Clarion, then use `/api/v1/_capabilities` as the compatibility probe. + outside Loomweave, then use `/api/v1/_capabilities` as the compatibility probe. --- ## 8. Security — Operator Guidance -Some risks sit outside Clarion's code but inside the operator's responsibility. These belong in the team's onboarding doc, not in the tool's runtime defences: +Some risks sit outside Loomweave's code but inside the operator's responsibility. These belong in the team's onboarding doc, not in the tool's runtime defences: -- **Use project-scoped API keys, not personal ones, when `storage.commit_db: true`.** Briefings in `.clarion/clarion.db` were paid for by whoever ran `clarion analyze`. A teammate pulling your committed DB benefits from LLM calls your personal key paid for. Use an Anthropic project / org key, not your personal key, when committing the DB. -- **Protect non-loopback HTTP exposure outside Clarion.** If operators bind the +- **Use project-scoped API keys, not personal ones, when `storage.commit_db: true`.** Briefings in `.loomweave/loomweave.db` were paid for by whoever ran `loomweave analyze`. A teammate pulling your committed DB benefits from LLM calls your personal key paid for. Use an Anthropic project / org key, not your personal key, when committing the DB. +- **Protect non-loopback HTTP exposure outside Loomweave.** If operators bind the ADR-014 HTTP read API outside loopback, place an authenticated reverse proxy or equivalent access-control layer in front of it. -- **Review `.clarion/.gitignore` before first commit.** The default excludes `runs/*/log.jsonl` (raw LLM request/response bodies); if operators opt into committing run logs for audit, they accept that source excerpts sent to Anthropic ship to the repo. That's a choice, not an oversight — but it must be a deliberate one. +- **Review `.loomweave/.gitignore` before first commit.** The default excludes `runs/*/log.jsonl` (raw LLM request/response bodies); if operators opt into committing run logs for audit, they accept that source excerpts sent to Anthropic ship to the repo. That's a choice, not an oversight — but it must be a deliberate one. ### Audit-surface finding IDs Every security-relevant event emits a finding: -- `CLA-SEC-SECRET-DETECTED` — unredacted secret blocked LLM dispatch -- `CLA-SEC-UNREDACTED-SECRETS-ALLOWED` — operator overrode block with `--allow-unredacted-secrets` -- `CLA-INFRA-SECRET-BASELINE-NO-JUSTIFICATION` — baseline entry lacks the required review rationale -- `CLA-INFRA-SECRET-BASELINE-MATCH` — baseline entry suppressed a scanner hit -- `CLA-INFRA-SECRET-OVERRIDE-UNCONFIRMED` — override flag was not confirmed, so the run aborted before start -- `CLA-INFRA-HTTP-NON-LOOPBACK-UNAUTHENTICATED` — ADR-014 HTTP read API was explicitly bound outside loopback and must be protected outside Clarion -- `CLA-INFRA-BRIEFING-INVALID` — LLM returned schema-invalid content twice, possible injection -- `CLA-SEC-VOCABULARY-CANDIDATE-NOVEL` — novel `patterns`/`antipatterns` tag proposed by LLM (light signal; mostly harmless) +- `LMWV-SEC-SECRET-DETECTED` — unredacted secret blocked LLM dispatch +- `LMWV-SEC-UNREDACTED-SECRETS-ALLOWED` — operator overrode block with `--allow-unredacted-secrets` +- `LMWV-INFRA-SECRET-BASELINE-NO-JUSTIFICATION` — baseline entry lacks the required review rationale +- `LMWV-INFRA-SECRET-BASELINE-MATCH` — baseline entry suppressed a scanner hit +- `LMWV-INFRA-SECRET-OVERRIDE-UNCONFIRMED` — override flag was not confirmed, so the run aborted before start +- `LMWV-INFRA-HTTP-NON-LOOPBACK-UNAUTHENTICATED` — ADR-014 HTTP read API was explicitly bound outside loopback and must be protected outside Loomweave +- `LMWV-INFRA-BRIEFING-INVALID` — LLM returned schema-invalid content twice, possible injection +- `LMWV-SEC-VOCABULARY-CANDIDATE-NOVEL` — novel `patterns`/`antipatterns` tag proposed by LLM (light signal; mostly harmless) Findings feed Filigree via the normal exchange. A security-focused operator can `filigree list --label=security --since 7d` across all three tools. @@ -1477,58 +1477,58 @@ Findings feed Filigree via the normal exchange. A security-focused operator can ## 9. Suite Bootstrap — Prerequisites Detail -Clarion v0.1 is not joining an existing Loom fabric — it is the work that weaves the fabric for the v0.1 suite of Clarion + Filigree + Wardline. The system-design §11 describes Clarion's capability-probe and degraded-mode posture; this section names the specific changes Clarion asks of sibling products as prerequisites. Where Clarion can ship a workaround while the sibling tool catches up, the workaround is noted. +Loomweave v0.1 is not joining an existing Weft fabric — it is the work that weaves the fabric for the v0.1 suite of Loomweave + Filigree + Wardline. The system-design §11 describes Loomweave's capability-probe and degraded-mode posture; this section names the specific changes Loomweave asks of sibling products as prerequisites. Where Loomweave can ship a workaround while the sibling tool catches up, the workaround is noted. ### 9.1 Filigree prerequisites -**Required for Clarion v0.1 ship**: +**Required for Loomweave v0.1 ship**: -1. **`registry_backend` config flag + pluggable `RegistryProtocol`** (ADR-014). Default: `filigree` (current behaviour). Alternative: `clarion`. When set to `clarion`, all four `file_records(id)` foreign-key writes route through a `RegistryProtocol` that consults Clarion's HTTP read API for `file_id` resolution before falling back to local auto-create. Three auto-create paths (`POST /api/v1/scan-results`, `create_observation(file_path=…)`, `trigger_scan`) are the primary refactor surface. Surgery estimate from recon: ~5–8 files in Filigree + SQLite FK rework + test updates. **Clarion workaround if absent**: Clarion still emits findings via scan-results; Filigree auto-creates shadow `file_records` under Filigree-native rules; Clarion's "owns the file registry" claim is downgraded to "owns the entity catalog; Filigree shadows the file mapping." +1. **`registry_backend` config flag + pluggable `RegistryProtocol`** (ADR-014). Default: `filigree` (current behaviour). Alternative: `loomweave`. When set to `loomweave`, all four `file_records(id)` foreign-key writes route through a `RegistryProtocol` that consults Loomweave's HTTP read API for `file_id` resolution before falling back to local auto-create. Three auto-create paths (`POST /api/v1/scan-results`, `create_observation(file_path=…)`, `trigger_scan`) are the primary refactor surface. Surgery estimate from recon: ~5–8 files in Filigree + SQLite FK rework + test updates. **Loomweave workaround if absent**: Loomweave still emits findings via scan-results; Filigree auto-creates shadow `file_records` under Filigree-native rules; Loomweave's "owns the file registry" claim is downgraded to "owns the entity catalog; Filigree shadows the file mapping." -2. **[Deferred to v0.2 per ADR-016]** Observation-creation HTTP endpoint. v0.1 transport is Clarion spawning `filigree mcp` as a subprocess and using the existing `create_observation` MCP tool over stdio. v0.2 adds `POST /api/v1/observations` with a schema parallel to the MCP tool; Clarion's emit path switches to HTTP and the subprocess path retires. +2. **[Deferred to v0.2 per ADR-016]** Observation-creation HTTP endpoint. v0.1 transport is Loomweave spawning `filigree mcp` as a subprocess and using the existing `create_observation` MCP tool over stdio. v0.2 adds `POST /api/v1/observations` with a schema parallel to the MCP tool; Loomweave's emit path switches to HTTP and the subprocess path retires. -3. **`scan_source` coordination** (ADR-017 supplementary). Filigree's `scan_source` is free-form today; no registry. Adding a `valid_scan_sources` section to `GET /api/files/_schema` (or a `GET /api/v1/scan-sources` endpoint) lets Clarion detect name collisions and Wardline register itself. **Clarion workaround if absent**: hardcoded reserved names (`clarion`, `wardline`, `cov`, `sec`) documented in §7; collisions surface as duplicate finding IDs post-ingest. +3. **`scan_source` coordination** (ADR-017 supplementary). Filigree's `scan_source` is free-form today; no registry. Adding a `valid_scan_sources` section to `GET /api/files/_schema` (or a `GET /api/v1/scan-sources` endpoint) lets Loomweave detect name collisions and Wardline register itself. **Loomweave workaround if absent**: hardcoded reserved names (`loomweave`, `wardline`, `cov`, `sec`) documented in §7; collisions surface as duplicate finding IDs post-ingest. -4. **Schema-compatibility contract test** (ADR-017 supplementary). Filigree publishes a `tests/fixtures/scan_results_contract.json` representative payload alongside each release; Clarion's CI pins against the contract. Filigree's CHANGELOG (`CHANGELOG.md:91`) already flags `Breaking (API)` changes — a published contract turns social discipline into code enforcement. **Clarion workaround if absent**: Clarion's integration tests run against the current Filigree `main`, treating the `GET /api/files/_schema` output as the contract. +4. **Schema-compatibility contract test** (ADR-017 supplementary). Filigree publishes a `tests/fixtures/scan_results_contract.json` representative payload alongside each release; Loomweave's CI pins against the contract. Filigree's CHANGELOG (`CHANGELOG.md:91`) already flags `Breaking (API)` changes — a published contract turns social discipline into code enforcement. **Loomweave workaround if absent**: Loomweave's integration tests run against the current Filigree `main`, treating the `GET /api/files/_schema` output as the contract. -5. **`metadata` nesting convention published** (ADR-017 supplementary). Filigree preserves `metadata` dict contents verbatim but does not index them. Clarion's nesting (`metadata.clarion.*`, `metadata.wardline_properties.*`) is a social convention; publishing it in Filigree docs prevents future scanners from colliding. **Clarion workaround if absent**: convention lives only in Clarion's docs; enforcement is operator vigilance. +5. **`metadata` nesting convention published** (ADR-017 supplementary). Filigree preserves `metadata` dict contents verbatim but does not index them. Loomweave's nesting (`metadata.loomweave.*`, `metadata.wardline_properties.*`) is a social convention; publishing it in Filigree docs prevents future scanners from colliding. **Loomweave workaround if absent**: convention lives only in Loomweave's docs; enforcement is operator vigilance. **Nice-to-have (v0.2+)**: -- **`POST /api/v1/observations` HTTP endpoint** (ADR-016 retirement trigger) — when Filigree ships this, Clarion's `filigree mcp` subprocess-spawn path retires; Clarion emit switches to HTTP. -- **Server-side per-entity dedup** for scan-results (supersedes Clarion's `mark_unseen` workaround in §7). Adds an optional `entity_id` extension field to Filigree's dedup key. -- **Native SARIF ingest endpoint** (`POST /api/v1/sarif-results`) — makes Clarion's SARIF→Filigree translator unnecessary. Non-trivial work in Filigree (SARIF is large). -- **`filigree --clarion-compat` self-check** that verifies a running Filigree deployment satisfies the `registry_backend: clarion` preconditions. +- **`POST /api/v1/observations` HTTP endpoint** (ADR-016 retirement trigger) — when Filigree ships this, Loomweave's `filigree mcp` subprocess-spawn path retires; Loomweave emit switches to HTTP. +- **Server-side per-entity dedup** for scan-results (supersedes Loomweave's `mark_unseen` workaround in §7). Adds an optional `entity_id` extension field to Filigree's dedup key. +- **Native SARIF ingest endpoint** (`POST /api/v1/sarif-results`) — makes Loomweave's SARIF→Filigree translator unnecessary. Non-trivial work in Filigree (SARIF is large). +- **`filigree --loomweave-compat` self-check** that verifies a running Filigree deployment satisfies the `registry_backend: loomweave` preconditions. ### 9.2 Wardline prerequisites -**Required for Clarion v0.1 ship**: +**Required for Loomweave v0.1 ship**: -1. **Stable `REGISTRY_VERSION` export and direct-import contract** (ADR-018). Wardline's `wardline.core.registry.REGISTRY` is a `MappingProxyType` over 42 canonical decorator names; `REGISTRY_VERSION` is a string. Clarion's Python plugin imports `REGISTRY` directly at startup (no network call; lightweight dependency — `wardline` package in plugin's pipx venv). Wardline commits to: (a) `REGISTRY_VERSION` semver, (b) backward-compatible additions within a major version, (c) `LEGACY_DECORATOR_ALIASES` table maintained for renames. **Clarion workaround if absent**: hardcoded registry mirror in Clarion's plugin, maintained manually — guaranteed to drift. +1. **Stable `REGISTRY_VERSION` export and direct-import contract** (ADR-018). Wardline's `wardline.core.registry.REGISTRY` is a `MappingProxyType` over 42 canonical decorator names; `REGISTRY_VERSION` is a string. Loomweave's Python plugin imports `REGISTRY` directly at startup (no network call; lightweight dependency — `wardline` package in plugin's pipx venv). Wardline commits to: (a) `REGISTRY_VERSION` semver, (b) backward-compatible additions within a major version, (c) `LEGACY_DECORATOR_ALIASES` table maintained for renames. **Loomweave workaround if absent**: hardcoded registry mirror in Loomweave's plugin, maintained manually — guaranteed to drift. -2. **YAML/JSON descriptor export of `REGISTRY`** — promoted from the earlier v0.2 deferral. Trivial implementation (dump `REGISTRY` to YAML in `wardline annotations descriptor --format yaml`). Inverts the vocabulary coupling: new Wardline annotations land without requiring Clarion plugin release. **Clarion workaround if absent**: direct Python import works for v0.1 because Clarion's Python plugin can require the `wardline` package. Future non-Python plugins (Java, Rust) cannot directly import Python objects; descriptor file is essential for them. Since v0.1 ships only a Python plugin, the workaround is acceptable but the descriptor export is requested for v0.2. +2. **YAML/JSON descriptor export of `REGISTRY`** — promoted from the earlier v0.2 deferral. Trivial implementation (dump `REGISTRY` to YAML in `wardline annotations descriptor --format yaml`). Inverts the vocabulary coupling: new Wardline annotations land without requiring Loomweave plugin release. **Loomweave workaround if absent**: direct Python import works for v0.1 because Loomweave's Python plugin can require the `wardline` package. Future non-Python plugins (Java, Rust) cannot directly import Python objects; descriptor file is essential for them. Since v0.1 ships only a Python plugin, the workaround is acceptable but the descriptor export is requested for v0.2. 3. **Wardline `scan_source="wardline"` POST to Filigree** (ADR-015 — decision). Two options: - - **Option A** (recommended for v0.1): Clarion ships a `clarion sarif import ` translator that reads Wardline's SARIF output on disk and POSTs to Filigree with `scan_source="wardline"` and `metadata.wardline_properties.*` preserved. Advantages: no Wardline changes needed; Clarion ships independently. Disadvantages: translator lives in the wrong place (Wardline's domain knowledge in Clarion), Wardline findings appear in Filigree only if someone runs `clarion sarif import`. + - **Option A** (recommended for v0.1): Loomweave ships a `loomweave sarif import ` translator that reads Wardline's SARIF output on disk and POSTs to Filigree with `scan_source="wardline"` and `metadata.wardline_properties.*` preserved. Advantages: no Wardline changes needed; Loomweave ships independently. Disadvantages: translator lives in the wrong place (Wardline's domain knowledge in Loomweave), Wardline findings appear in Filigree only if someone runs `loomweave sarif import`. - **Option B**: Wardline gains an HTTP client and native POST. Advantages: clean responsibility boundary; Wardline emits its own findings. Disadvantages: Wardline's `pyproject.toml:22` declares `dependencies = []` — adding `httpx` or `requests` changes the project's dependency posture; not a v0.1 timeline. - - **v0.1 decision**: Option A. Option B is deferred to v0.2 where Wardline gains an HTTP client as part of the broader "Wardline consumes Clarion" refactor. + - **v0.1 decision**: Option A. Option B is deferred to v0.2 where Wardline gains an HTTP client as part of the broader "Wardline consumes Loomweave" refactor. -4. **`LEGACY_DECORATOR_ALIASES` promise** (ADR-018 supplementary). Clarion's plugin relies on Wardline's alias resolution to avoid double-counting (e.g., `tier_transition` → `trust_boundary`). Wardline commits to adding new renames to this table rather than deleting decorator names outright. +4. **`LEGACY_DECORATOR_ALIASES` promise** (ADR-018 supplementary). Loomweave's plugin relies on Wardline's alias resolution to avoid double-counting (e.g., `tier_transition` → `trust_boundary`). Wardline commits to adding new renames to this table rather than deleting decorator names outright. **Nice-to-have (v0.2+)**: -- **Wardline `ProjectIndex` abstraction + HTTP client** to consume Clarion's read API. Large refactor (`ScanEngine`, `ScanContext`, every rule in `scanner/rules/` binds to in-memory AST today). Enables AST-dedup and tier-consistency across the suite. Not a v0.1 timeline; named so the direction is recorded. +- **Wardline `ProjectIndex` abstraction + HTTP client** to consume Loomweave's read API. Large refactor (`ScanEngine`, `ScanContext`, every rule in `scanner/rules/` binds to in-memory AST today). Enables AST-dedup and tier-consistency across the suite. Not a v0.1 timeline; named so the direction is recorded. - **Qualname in annotation descriptor**. Wardline's descriptor export includes the qualname convention explicitly, simplifying identity reconciliation (§2) from a heuristic map to a direct lookup. - **Registered Filigree scanner** — `wardline.toml` in `.filigree/scanners/` so operators can `trigger_scan wardline` from Filigree's dashboard. Inversion of the "Wardline is autonomous" model; may or may not align with Wardline's operational posture. ### 9.3 Joint prerequisites (shared ownership) -1. **Cross-tool test fixtures** (§10 Acceptance). Mock Filigree HTTP server (Rust, `wiremock`), Wardline SARIF corpus (committed slice of real output), schema-pin tests. All three tools benefit; naming authority probably sits in Clarion's repo in v0.1, graduating to its own fixture repo if the suite adds a fourth tool. +1. **Cross-tool test fixtures** (§10 Acceptance). Mock Filigree HTTP server (Rust, `wiremock`), Wardline SARIF corpus (committed slice of real output), schema-pin tests. All three tools benefit; naming authority probably sits in Loomweave's repo in v0.1, graduating to its own fixture repo if the suite adds a fourth tool. -2. **ADR cross-tool coordination**. Wardline ADRs already reference Filigree issue IDs by string (recon found `wardline-63255c8d5a` cited in `ADR-006-sarif-suppress-as-native-suppression.md:280-281`). Clarion ADRs will similarly reference Wardline and Filigree issues. There's no programmatic cross-repo link; a lightweight cross-tool index in each repo's ADR folder would be a cheap way to keep those references discoverable. +2. **ADR cross-tool coordination**. Wardline ADRs already reference Filigree issue IDs by string (recon found `wardline-63255c8d5a` cited in `ADR-006-sarif-suppress-as-native-suppression.md:280-281`). Loomweave ADRs will similarly reference Wardline and Filigree issues. There's no programmatic cross-repo link; a lightweight cross-tool index in each repo's ADR folder would be a cheap way to keep those references discoverable. -3. **Suite release choreography**. The `registry_backend` flag must land in Filigree before Clarion v0.1 ships; the `REGISTRY_VERSION` export must be stable in Wardline before Clarion v0.1 ships; Clarion's SARIF translator must ship alongside v0.1 rather than later. A three-tool coordinated release plan belongs in the implementation plan, not this design document, but is named here so it doesn't become emergent. +3. **Suite release choreography**. The `registry_backend` flag must land in Filigree before Loomweave v0.1 ships; the `REGISTRY_VERSION` export must be stable in Wardline before Loomweave v0.1 ships; Loomweave's SARIF translator must ship alongside v0.1 rather than later. A three-tool coordinated release plan belongs in the implementation plan, not this design document, but is named here so it doesn't become emergent. --- @@ -1543,7 +1543,7 @@ Clarion v0.1 is not joining an existing Loom fabric — it is the work that weav | Unit | Pure functions, parsers, policy-engine | 200+ | | Integration | Store layer, LLM orchestrator (recorded), plugin protocol (fixture plugin) | 60–80 | | Component | Per-phase pipeline tests | 25–30 | -| End-to-end | `clarion analyze` + `clarion serve` + HTTP API contract | 15–20 | +| End-to-end | `loomweave analyze` + `loomweave serve` + HTTP API contract | 15–20 | | Property-based | Graph invariants, ID stability | 10–15 | **Python plugin** (pytest): @@ -1561,36 +1561,36 @@ Clarion v0.1 is not joining an existing Loom fabric — it is the work that weav **LLM testing**: `LlmProvider` wraps into a `RecordingProvider` for test mode; records API calls on first run, replays on subsequent runs. Live-LLM integration tests run nightly (not PR CI). -**Determinism**: back-to-back `clarion analyze` on identical fixture → byte-identical entity/edge state. Summaries use recorded responses for reproducibility. +**Determinism**: back-to-back `loomweave analyze` on identical fixture → byte-identical entity/edge state. Summaries use recorded responses for reproducibility. ### Observability - Structured JSON-line logs via `tracing` crate; log rotation at 100MB, 5 files kept. - `/api/v1/metrics` Prometheus-compatible endpoint. - Per-run `stats.json` — phases, cost, cache hit ratio, failures. -- `clarion cost --since ` CLI report. +- `loomweave cost --since ` CLI report. - `session_info()` MCP tool for live session state. -- `clarion sessions log ` for session-scoped tool-call replay. +- `loomweave sessions log ` for session-scoped tool-call replay. ### Error surfaces -Every failure produces either a finding or a run-stats entry. Rule-ID namespacing per ADR-017 — `CLA-INFRA-*` for core-emitted pipeline/infrastructure failures; `CLA-PY-*` for Python-plugin-emitted rule findings (including parse errors, which the plugin emits when it fails to parse a file): +Every failure produces either a finding or a run-stats entry. Rule-ID namespacing per ADR-017 — `LMWV-INFRA-*` for core-emitted pipeline/infrastructure failures; `LMWV-PY-*` for Python-plugin-emitted rule findings (including parse errors, which the plugin emits when it fails to parse a file): -- `CLA-PY-PARSE-ERROR` (Python-plugin emitted when a source file fails to parse) -- `CLA-INFRA-PLUGIN-TIMEOUT`, `CLA-INFRA-PLUGIN-CRASH` -- `CLA-INFRA-LLM-ERROR`, `CLA-INFRA-LLM-RATE-LIMIT-EXCEEDED` -- `CLA-INFRA-BUDGET-WARNING`, `CLA-INFRA-BUDGET-EXCEEDED` -- `CLA-INFRA-ANALYSIS-ABORTED`, `CLA-INFRA-BRIEFING-INVALID` -- `CLA-INFRA-MANIFEST-STALE`, `CLA-INFRA-GUIDANCE-EXPIRED` +- `LMWV-PY-PARSE-ERROR` (Python-plugin emitted when a source file fails to parse) +- `LMWV-INFRA-PLUGIN-TIMEOUT`, `LMWV-INFRA-PLUGIN-CRASH` +- `LMWV-INFRA-LLM-ERROR`, `LMWV-INFRA-LLM-RATE-LIMIT-EXCEEDED` +- `LMWV-INFRA-BUDGET-WARNING`, `LMWV-INFRA-BUDGET-EXCEEDED` +- `LMWV-INFRA-ANALYSIS-ABORTED`, `LMWV-INFRA-BRIEFING-INVALID` +- `LMWV-INFRA-MANIFEST-STALE`, `LMWV-INFRA-GUIDANCE-EXPIRED` ### Acceptance criteria **Functional**: -1. `clarion analyze /home/john/elspeth` runs to completion in under 1 hour at ~$15 ± 50%. +1. `loomweave analyze /home/john/elspeth` runs to completion in under 1 hour at ~$15 ± 50%. 2. Every subsystem, module, and per-policy in-scope entity has a briefing. -3. `clarion serve` starts within 2 seconds; MCP `initialize` completes in ≤100ms. -4. A Claude Code consult session navigates 20+ turns without exhausting parent context (parent context growth ≤500 tokens per Clarion tool call average). +3. `loomweave serve` starts within 2 seconds; MCP `initialize` completes in ≤100ms. +4. A Claude Code consult session navigates 20+ turns without exhausting parent context (parent context growth ≤500 tokens per Loomweave tool call average). 5. Guidance sheet edit → dependent summary invalidation → re-query returns fresh content in ≤5 seconds for a hot entity. 6. Wardline-derived guidance regenerates on every analyse; drift surfaced as findings. 7. Filigree observation round-trips: emit → appears in filigree → promotion works. @@ -1599,35 +1599,35 @@ Every failure produces either a finding or a run-stats entry. Rule-ID namespacin **Quality**: 1. Core test coverage ≥80% on non-trivial paths; critical paths (protocol, policy engine, migrations) ≥95%. -2. No unexpected `CLA-INFRA-*` findings on clean elspeth run. +2. No unexpected `LMWV-INFRA-*` findings on clean elspeth run. 3. Determinism test passes. 4. All integration tests pass under `RecordingProvider`. -5. Documentation: installation, `clarion.yaml` schema, plugin-authoring protocol, MCP tool reference, HTTP API reference. +5. Documentation: installation, `loomweave.yaml` schema, plugin-authoring protocol, MCP tool reference, HTTP API reference. **Operational**: 1. Single-binary: Linux (x86_64, ARM64), macOS (x86_64, ARM64), Windows (x86_64); no dynamic linking beyond libc. -2. Python plugin: `pip install clarion-plugin-python`; Python 3.11+. +2. Python plugin: `pip install loomweave-plugin-python`; Python 3.11+. 3. Store survives unclean shutdown (SIGKILL during analysis). -4. `.clarion/clarion.db` is git-committable and round-trips across machines. +4. `.loomweave/loomweave.db` is git-committable and round-trips across machines. **Ecosystem**: -1. **Mock Wardline client** successfully consumes Clarion's HTTP read API (scope: `entities_for_file`, `declared_topology`, `findings`, `analysis_state`, `entity_summary`). Exists as a test harness in Clarion's repo in v0.1; Wardline's real client is v0.2+. -2. **Mock Filigree HTTP server** (using `wiremock` on Rust) successfully accepts Clarion POSTs with: - - `metadata.clarion.*` extension fields round-trip verbatim. +1. **Mock Wardline client** successfully consumes Loomweave's HTTP read API (scope: `entities_for_file`, `declared_topology`, `findings`, `analysis_state`, `entity_summary`). Exists as a test harness in Loomweave's repo in v0.1; Wardline's real client is v0.2+. +2. **Mock Filigree HTTP server** (using `wiremock` on Rust) successfully accepts Loomweave POSTs with: + - `metadata.loomweave.*` extension fields round-trip verbatim. - Severity mapping table applied correctly (INTERNAL→wire). - `scan_run_id` lifecycle: create → intermediate posts → complete_scan_run=true. - Dedup behaviour under `mark_unseen=true` matches real Filigree. -3. **Wardline SARIF corpus** committed at `tests/fixtures/wardline-sarif/`: at minimum one SARIF file per annotation group + one per Wardline rule. Used for `clarion sarif import` translator tests. -4. **Wardline REGISTRY pin test**: on plugin startup, Clarion verifies `wardline.core.registry.REGISTRY_VERSION` against a pinned expected version; mismatch emits `CLA-INFRA-WARDLINE-REGISTRY-STALE` at severity WARN (not an abort). A test walks through `REGISTRY` and asserts that every canonical decorator name Clarion's plugin expects is present. -5. **Filigree `_schema` pin test**: Clarion's CI fetches `GET /api/files/_schema` from a running Filigree and pins `valid_severities`, `valid_finding_statuses`, `valid_association_types`. Filigree's breaking-change posture (per its `CHANGELOG.md`) makes this the minimum-viable protocol-drift detector. -6. **End-to-end "suite smoke"**: starts Clarion's `clarion serve`, runs Wardline's scanner against a fixture repo, runs `clarion sarif import` on the resulting SARIF, POSTs to a real Filigree instance, verifies that Filigree returns both `scan_source="clarion"` and `scan_source="wardline"` findings with preserved `metadata.*` fields. Requires all three tools co-present; maintained in Clarion's repo as the canonical integration test. -7. **Filigree's `add_file_association`** round-trips Clarion entity IDs (original v0.1 criterion — retained). +3. **Wardline SARIF corpus** committed at `tests/fixtures/wardline-sarif/`: at minimum one SARIF file per annotation group + one per Wardline rule. Used for `loomweave sarif import` translator tests. +4. **Wardline REGISTRY pin test**: on plugin startup, Loomweave verifies `wardline.core.registry.REGISTRY_VERSION` against a pinned expected version; mismatch emits `LMWV-INFRA-WARDLINE-REGISTRY-STALE` at severity WARN (not an abort). A test walks through `REGISTRY` and asserts that every canonical decorator name Loomweave's plugin expects is present. +5. **Filigree `_schema` pin test**: Loomweave's CI fetches `GET /api/files/_schema` from a running Filigree and pins `valid_severities`, `valid_finding_statuses`, `valid_association_types`. Filigree's breaking-change posture (per its `CHANGELOG.md`) makes this the minimum-viable protocol-drift detector. +6. **End-to-end "suite smoke"**: starts Loomweave's `loomweave serve`, runs Wardline's scanner against a fixture repo, runs `loomweave sarif import` on the resulting SARIF, POSTs to a real Filigree instance, verifies that Filigree returns both `scan_source="loomweave"` and `scan_source="wardline"` findings with preserved `metadata.*` fields. Requires all three tools co-present; maintained in Loomweave's repo as the canonical integration test. +7. **Filigree's `add_file_association`** round-trips Loomweave entity IDs (original v0.1 criterion — retained). 8. **Degraded-mode tests**: - - `clarion analyze --no-filigree` completes, writes `runs//findings.jsonl` locally, emits `CLA-INFRA-FILIGREE-UNAVAILABLE` per finding batch. - - `clarion analyze --no-wardline` completes, skips Wardline state ingest, annotations emitted with `confidence_basis: "clarion_augmentation"` only. - - Missing `clarion sarif import` at the system level does not block `clarion analyze`; it only affects Wardline-findings-to-Filigree flow. + - `loomweave analyze --no-filigree` completes, writes `runs//findings.jsonl` locally, emits `LMWV-INFRA-FILIGREE-UNAVAILABLE` per finding batch. + - `loomweave analyze --no-wardline` completes, skips Wardline state ingest, annotations emitted with `confidence_basis: "loomweave_augmentation"` only. + - Missing `loomweave sarif import` at the system level does not block `loomweave analyze`; it only affects Wardline-findings-to-Filigree flow. --- @@ -1648,8 +1648,8 @@ The table below is a navigation aid for implementers: it maps each ADR to the se | ADR-001 | Rust for the core | §1, Appendix C | | ADR-002 | Plugin transport: Content-Length framed JSON-RPC 2.0 subprocess | §1 | | ADR-003 | Entity ID scheme: symbolic canonical-name; file path as property; EntityAlias v0.2 | §2 | -| ADR-004 | Finding-exchange format: Filigree-native intake; `metadata.clarion.*` nesting | §2, §7 | -| ADR-005 | `.clarion/` git-committable by default | §3 | +| ADR-004 | Finding-exchange format: Filigree-native intake; `metadata.loomweave.*` nesting | §2, §7 | +| ADR-005 | `.loomweave/` git-committable by default | §3 | | [ADR-006](../adr/ADR-006-clustering-algorithm.md), [ADR-032](../adr/ADR-032-weighted-components-clustering-fallback.md) | Clustering algorithm: Leiden with weighted-components fallback | §4, §5 | | [ADR-007](../adr/ADR-007-summary-cache-key.md) | Summary cache key design and invalidation | §4 | | ADR-008 | Superseded by ADR-014 | §7, §9 | @@ -1659,10 +1659,10 @@ The table below is a navigation aid for implementers: it maps each ADR to the se | [ADR-012](../adr/ADR-012-http-auth-default.md) | Historical HTTP auth proposal — superseded by ADR-014 for registry-backend API | §7 | | [ADR-013](../adr/ADR-013-pre-ingest-secret-scanner.md) | Pre-ingest secret scanner | §8 | | ADR-014 | Filigree `registry_backend` flag + pluggable `RegistryProtocol`; owns registry-backend HTTP read trust model | §7, §9 | -| [ADR-015](../adr/ADR-015-wardline-filigree-emission.md) | Wardline→Filigree emission: Clarion SARIF translator (v0.1), native Wardline POST (v0.2) | §7, §9 | +| [ADR-015](../adr/ADR-015-wardline-filigree-emission.md) | Wardline→Filigree emission: Loomweave SARIF translator (v0.1), native Wardline POST (v0.2) | §7, §9 | | [ADR-016](../adr/ADR-016-observation-transport.md) | Observation transport: `filigree mcp` subprocess (v0.1); `POST /api/v1/observations` HTTP (v0.2 retirement) | §9 | | [ADR-017](../adr/ADR-017-severity-and-dedup.md) | Severity mapping + rule-ID round-trip + dedup via `mark_unseen=true` | §7 | -| [ADR-018](../adr/ADR-018-identity-reconciliation.md) | Identity reconciliation: Clarion translates; direct `REGISTRY` import with version pinning | §2, §9 | +| [ADR-018](../adr/ADR-018-identity-reconciliation.md) | Identity reconciliation: Loomweave translates; direct `REGISTRY` import with version pinning | §2, §9 | | ADR-019 | SARIF property-bag preservation: `metadata.wardline_properties.*` pass-through | §7 | | ADR-020 | Degraded-mode policy: `--no-filigree` / `--no-wardline` flags | — (system-design §11) | | [ADR-021](../adr/ADR-021-plugin-authority-hybrid.md) | Plugin authority model: hybrid — declared capabilities + core-enforced minimums | §1 (hybrid controls to be added) | @@ -1682,12 +1682,12 @@ Recorded here for implementer traceability. The amended ADR-001 text lives at [. ## Appendix A — Future direction: unified storage layer -Once all three systems (Clarion, Wardline, Filigree) are proven and their integration patterns are battle-tested, a unified storage layer across the suite may be worth considering. v0.1 explicitly keeps the stores separate — each tool owns its data, and integration is via protocols (Filigree-native scan-result findings, HTTP read APIs, MCP tool calls) rather than shared tables. +Once all three systems (Loomweave, Wardline, Filigree) are proven and their integration patterns are battle-tested, a unified storage layer across the suite may be worth considering. v0.1 explicitly keeps the stores separate — each tool owns its data, and integration is via protocols (Filigree-native scan-result findings, HTTP read APIs, MCP tool calls) rather than shared tables. This is deliberate. Premature unification would: - Force the three tools' data shapes to converge before we know whether convergence helps. - Couple release cadence (schema migrations in one tool affect all three). -- Compromise the "each tool is independently useful" property — directly violating the Loom federation axiom ([../../suite/loom.md](../../suite/loom.md) §3). +- Compromise the "each tool is independently useful" property — directly violating the Weft federation axiom ([../../suite/weft.md](../../suite/weft.md) §3). When (or if) unification makes sense, v0.1's design does not foreclose it: - All three tools speak SQLite; a unified layer could federate across files or migrate to a shared schema. @@ -1703,7 +1703,7 @@ For v0.1 through at least v0.3, each tool's store remains its own concern. Revis | Term | Definition | |---|---| | **Briefing** | Structured summary answering a fixed set of questions (purpose, maturity, risks, patterns, antipatterns, relationships). Replaces free-form narrative. | -| **Consult mode** | Interactive LLM session using Clarion MCP tools; LLM holds a cursor and navigates the store during the conversation. | +| **Consult mode** | Interactive LLM session using Loomweave MCP tools; LLM holds a cursor and navigates the store during the conversation. | | **Cursor** | Session state pointing at the entity currently under discussion; updated by navigation tools. | | **Entity** | A node in the property graph — function, class, module, package, subsystem, guidance sheet, file, etc. | | **Entity briefing** | See Briefing. | @@ -1717,35 +1717,35 @@ For v0.1 through at least v0.3, each tool's store remains its own concern. Revis | **Level** | Policy-config axis: function, class, global, module, subsystem, cross_cutting. | | **Maturity** | Classification field in EntityBriefing: Placeholder, Experimental, Alpha, Beta, Stable, Mature, Deprecated, Dead. | | **Observation** | A fire-and-forget note emitted to Filigree with 14-day expiry; can be promoted to an issue or a guidance sheet. | -| **Plugin** | A language-specific subprocess implementing the Clarion plugin protocol; owns its ontology (kinds, edges, tags, rules). | +| **Plugin** | A language-specific subprocess implementing the Loomweave plugin protocol; owns its ontology (kinds, edges, tags, rules). | | **Plugin manifest** | YAML declaration of the kinds, edges, tags, capabilities, rules, and prompt templates a plugin provides. | | **Policy engine** | Core component deciding for each unit of LLM work: mode, model tier, budget, caching. | -| **Prompt caching** | Anthropic feature layering prompt content into cacheable segments; Clarion structures prompts to maximise hit rate. | +| **Prompt caching** | Anthropic feature layering prompt content into cacheable segments; Loomweave structures prompts to maximise hit rate. | | **Scope lens** | Session filter dictating which relationships neighbour queries emphasise: Structural, Taint, Subsystem, Wardline. | -| **Finding-exchange format** | Filigree's native scan-result intake schema: `{scan_source, scan_run_id?, findings: [{path, rule_id, severity, message, line_start?, line_end?, suggestion?, metadata?}]}`. Accepted at `POST /api/v1/scan-results`. Extension slot is `metadata` (a dict); top-level unknown keys are silently dropped. Used for all cross-tool finding transport inside the suite. Not SARIF. See §7 for the Clarion-specific nesting convention. | +| **Finding-exchange format** | Filigree's native scan-result intake schema: `{scan_source, scan_run_id?, findings: [{path, rule_id, severity, message, line_start?, line_end?, suggestion?, metadata?}]}`. Accepted at `POST /api/v1/scan-results`. Extension slot is `metadata` (a dict); top-level unknown keys are silently dropped. Used for all cross-tool finding transport inside the suite. Not SARIF. See §7 for the Loomweave-specific nesting convention. | | **SARIF** | Static Analysis Results Interchange Format. Wardline independently emits genuine SARIF v2.1.0 for external consumers (GitHub code-scanning, CI reporters). Not used for suite-internal finding exchange. | | **Subsystem** | Semantically-grouped cluster of modules, derived by clustering + LLM synthesis. | | **Summary cache** | Keyed by `(entity, content_hash, template, model_tier, guidance_fingerprint)`; stores generated briefings. | -| **Taint analysis** | Dataflow tracking of tier classifications; **Wardline's responsibility, not Clarion's**. | -| **Tier** | Wardline classification. Canonical names (manifest-configurable in `wardline.yaml:tiers`): `INTEGRAL`, `ASSURED`, `GUARDED`, `EXTERNAL_RAW`. Plus `UNKNOWN` and `MIXED` as computed transient states. Clarion preserves these names verbatim in briefing vocabulary and entity `wardline.declared_tier` property. | +| **Taint analysis** | Dataflow tracking of tier classifications; **Wardline's responsibility, not Loomweave's**. | +| **Tier** | Wardline classification. Canonical names (manifest-configurable in `wardline.yaml:tiers`): `INTEGRAL`, `ASSURED`, `GUARDED`, `EXTERNAL_RAW`. Plus `UNKNOWN` and `MIXED` as computed transient states. Loomweave preserves these names verbatim in briefing vocabulary and entity `wardline.declared_tier` property. | | **Writer-actor** | Concurrency pattern: a single Tokio task owns the sole SQLite write connection; all other tasks submit mutations through a bounded `mpsc` channel. See §3. | | **Knowledge basis** | EntityBriefing field indicating the evidence class a briefing rests on: `static_only`, `runtime_informed`, or `human_verified`. | | **Content-Length framing** | JSON-RPC message framing used by the plugin protocol — the same mechanism LSP uses. `Content-Length: \r\n\r\n`. Required for binary-safe streams and crash-resumability. See §1. | | **Canonical qualified name** | Plugin-language-native fully-qualified identifier used in entity IDs. Python example: `auth.tokens.TokenManager` (with `src/` prefix stripped), not `src/auth/tokens.py::TokenManager`. | -| **Shadow DB** | Operational posture where `clarion analyze` writes to `.clarion/clarion.db.new` and atomic-renames on completion; zero impact on read-snapshot of an already-running `clarion serve`. Available via `clarion analyze --shadow-db`. | +| **Shadow DB** | Operational posture where `loomweave analyze` writes to `.loomweave/loomweave.db.new` and atomic-renames on completion; zero impact on read-snapshot of an already-running `loomweave serve`. Available via `loomweave analyze --shadow-db`. | | **Pre-ingest redaction** | Secret-scanner pass (`detect-secrets` or equivalent) executed before any file content reaches the LLM provider; unredacted hits block LLM dispatch for that file. See §8, System-design §10. | -| **Suite bootstrap** | The set of Filigree-side and Wardline-side changes Clarion v0.1 requires as prerequisites. Not "integration with existing capabilities" but "new features in sibling tools." Documented in §9. | -| **`metadata` extension slot** | Filigree's scan-result ingest preserves a per-finding `metadata` dict verbatim. Clarion's extension fields (`kind`, `confidence`, `related_entities`, internal-severity preservation, etc.) nest under `metadata.clarion.*`; Wardline's SARIF property-bag keys nest under `metadata.wardline_properties.*`. Top-level unknown keys are silently dropped by Filigree. | -| **`wardline.fingerprint.json`** | Wardline's per-function state file: `{qualified_name, module, decorators, annotation_hash, tier_context}` per entry. Ingested by Clarion v0.1 into entity `WardlineMeta`. | -| **`REGISTRY_VERSION`** | Semver-ish string on Wardline's `wardline.core.registry` module. Clarion pins it at plugin startup; mismatch emits `CLA-INFRA-WARDLINE-REGISTRY-STALE`. | -| **`scan_run_id`** | Filigree-owned run identifier. Clarion's `run_id` (§2 Finding.source.run_id) maps 1:1; Clarion creates a scan run at Phase 0 and closes it at Phase 8. See §7. | -| **Degraded mode** | Operating posture when a sibling tool is unavailable: `clarion analyze --no-filigree` writes findings locally; `clarion analyze --no-wardline` skips Wardline state ingest. Documented in System-design §11. | -| **Identity reconciliation** | The translation Clarion maintains between three concurrent identity schemes (Clarion EntityId, Wardline qualname, Wardline exception-register location string). See §2. | -| **Entity-resolve endpoint** | `GET /api/v1/entities/resolve?scheme=&value=` — exposes Clarion's identity-translation layer as a public API so sibling tools can look up Clarion entity IDs from their native schemes without embedding Clarion's ID format. See §7. | -| **Capability probe / compat report** | At `clarion analyze` startup, Clarion probes Filigree and Wardline capabilities and emits one `CLA-INFRA-SUITE-COMPAT-REPORT` finding summarising what's present and what's degraded. See System-design §11. | -| **Textual DB export** | `clarion db export --textual` — deterministic JSON-lines dump of entities, edges, guidance, findings (summary cache excluded). Enables git-friendly diffs and multi-developer merge resolution on the committed SQLite database. See §3. | -| **DB merge-helper** | `clarion db merge-helper` — Git merge driver that resolves `.clarion/clarion.db` conflicts deterministically: union entities/edges, last-writer-wins by `updated_at`, guidance conflicts surfaced for manual resolution, cache cleared. See §3. | -| **Triage-state feedback** | Mechanism by which Filigree's suppression and acknowledgement reasons surface inside Clarion briefings as operator-acknowledged evidence or synthetic risk entries. Read-only on Clarion's side. | +| **Suite bootstrap** | The set of Filigree-side and Wardline-side changes Loomweave v0.1 requires as prerequisites. Not "integration with existing capabilities" but "new features in sibling tools." Documented in §9. | +| **`metadata` extension slot** | Filigree's scan-result ingest preserves a per-finding `metadata` dict verbatim. Loomweave's extension fields (`kind`, `confidence`, `related_entities`, internal-severity preservation, etc.) nest under `metadata.loomweave.*`; Wardline's SARIF property-bag keys nest under `metadata.wardline_properties.*`. Top-level unknown keys are silently dropped by Filigree. | +| **`wardline.fingerprint.json`** | Wardline's per-function state file: `{qualified_name, module, decorators, annotation_hash, tier_context}` per entry. Ingested by Loomweave v0.1 into entity `WardlineMeta`. | +| **`REGISTRY_VERSION`** | Semver-ish string on Wardline's `wardline.core.registry` module. Loomweave pins it at plugin startup; mismatch emits `LMWV-INFRA-WARDLINE-REGISTRY-STALE`. | +| **`scan_run_id`** | Filigree-owned run identifier. Loomweave's `run_id` (§2 Finding.source.run_id) maps 1:1; Loomweave creates a scan run at Phase 0 and closes it at Phase 8. See §7. | +| **Degraded mode** | Operating posture when a sibling tool is unavailable: `loomweave analyze --no-filigree` writes findings locally; `loomweave analyze --no-wardline` skips Wardline state ingest. Documented in System-design §11. | +| **Identity reconciliation** | The translation Loomweave maintains between three concurrent identity schemes (Loomweave EntityId, Wardline qualname, Wardline exception-register location string). See §2. | +| **Entity-resolve endpoint** | `GET /api/v1/entities/resolve?scheme=&value=` — exposes Loomweave's identity-translation layer as a public API so sibling tools can look up Loomweave entity IDs from their native schemes without embedding Loomweave's ID format. See §7. | +| **Capability probe / compat report** | At `loomweave analyze` startup, Loomweave probes Filigree and Wardline capabilities and emits one `LMWV-INFRA-SUITE-COMPAT-REPORT` finding summarising what's present and what's degraded. See System-design §11. | +| **Textual DB export** | `loomweave db export --textual` — deterministic JSON-lines dump of entities, edges, guidance, findings (summary cache excluded). Enables git-friendly diffs and multi-developer merge resolution on the committed SQLite database. See §3. | +| **DB merge-helper** | `loomweave db merge-helper` — Git merge driver that resolves `.loomweave/loomweave.db` conflicts deterministically: union entities/edges, last-writer-wins by `updated_at`, guidance conflicts surfaced for manual resolution, cache cleared. See §3. | +| **Triage-state feedback** | Mechanism by which Filigree's suppression and acknowledgement reasons surface inside Loomweave briefings as operator-acknowledged evidence or synthetic risk entries. Read-only on Loomweave's side. | | **`first_seen_commit` / `last_seen_commit`** | Entity-level provenance: the git SHA of the first run to observe an entity and of the most recent run to still observe it. Enables point-in-time queries without re-running analysis. | --- @@ -1781,7 +1781,7 @@ The v0.1 core is Rust (locked — see §11 ADR-001). Crate choices below are rec - Content-Length framed JSON-RPC 2.0 (§1). Implementation: `tower-lsp-server`-derived framing or hand-rolled (simple enough — ~80 lines). Use `serde_json` for payload encoding; `jsonrpc-core` is over-featured for our two-endpoint protocol. - **tokio::process::Child** for plugin subprocess lifecycle. Explicit `wait()` to reap zombies. SIGPIPE handling (Unix only): ignore the signal so a dead plugin doesn't crash the core when we write to its stdin. - **Bounded `tokio::sync::mpsc`** for the completed-file handoff from the blocking plugin worker → async writer loop. Backpressure cap: default 100 messages. Prevents a runaway plugin session from OOMing the core while preserving a simple request/response plugin wire protocol. -- **Crash-loop circuit breaker**: >3 plugin crashes in 60 seconds → permanently disable the plugin for the run and emit `CLA-INFRA-PLUGIN-DISABLED-CRASH-LOOP`. +- **Crash-loop circuit breaker**: >3 plugin crashes in 60 seconds → permanently disable the plugin for the run and emit `LMWV-INFRA-PLUGIN-DISABLED-CRASH-LOOP`. - **stdout hygiene**: documented plugin-author requirement (§1 Error handling). Reference Python client redirects `logging.basicConfig(stream=sys.stderr)` at import time. ### Observability @@ -1809,7 +1809,7 @@ The v0.1 core is Rust (locked — see §11 ADR-001). Crate choices below are rec ### Build -- **Cargo workspaces**: `clarion-core`, `clarion-cli`, `clarion-plugin-protocol`, `clarion-api`, `clarion-llm`. +- **Cargo workspaces**: `loomweave-core`, `loomweave-cli`, `loomweave-plugin-protocol`, `loomweave-api`, `loomweave-llm`. - Cross-compilation targets per §10 Operational: Linux x86_64/ARM64, macOS x86_64/ARM64, Windows x86_64. Use `cargo-dist` or similar for release artifacts. - MSRV: 1.75+ (stable async traits). Document in README. @@ -1831,10 +1831,10 @@ Revision 5 (2026-04-17) restructures the single design document into a three-lay | Change | Rationale | |---|---| | Original document split into three layers: requirements (the *what*), system-design (the *how*, mid-level), and this detailed-design reference (implementation-level) | Original doc reached 71k tokens and mixed high-level intent with implementation detail; the three-layer docset lets a reader choose the appropriate depth without reading everything | -| Filename renamed `clarion-v0.1-design.md` → `clarion-v0.1-detailed-design.md` via `git mv` (history preserved via `git log --follow`) | Removes ambiguity between "the design doc" and the new `system-design.md` | -| Abstract, Design Principles, process/UX topology, conceptual data model, core/plugin split narrative, integration posture, security threat model, suite-bootstrap architecture, §12 Explicit Deferrals, and §1 "What Clarion is NOT" moved to higher layers (requirements or system-design) | Higher layers serve those abstractions better; this document focuses on implementation detail | -| Retained: full SQL schema, complete `clarion.yaml` example, exact Phase-7 rule catalogue with thresholds, full MCP tool list, severity mapping tables, `scan_run_id` lifecycle, dedup collision policy, commit-ref/dirty-tree handling, SARIF translator detail, Wardline state-file table, HTTP endpoint list, token-auth full spec, operator-guidance security subsection, §11 Suite Bootstrap prerequisite detail, testing/observability/acceptance, full ADR backlog, appendices | These are the implementation details that don't belong in higher layers | -| Suite naming aligned: "three-tool suite" framing replaced by "Loom suite" / "Loom fabric" references, with `docs/suite/loom.md` as the canonical doctrine source | Loom is now the family name; see [../../suite/loom.md](../../suite/loom.md) for federation axiom and composition law | +| Filename renamed `loomweave-v0.1-design.md` → `loomweave-v0.1-detailed-design.md` via `git mv` (history preserved via `git log --follow`) | Removes ambiguity between "the design doc" and the new `system-design.md` | +| Abstract, Design Principles, process/UX topology, conceptual data model, core/plugin split narrative, integration posture, security threat model, suite-bootstrap architecture, §12 Explicit Deferrals, and §1 "What Loomweave is NOT" moved to higher layers (requirements or system-design) | Higher layers serve those abstractions better; this document focuses on implementation detail | +| Retained: full SQL schema, complete `loomweave.yaml` example, exact Phase-7 rule catalogue with thresholds, full MCP tool list, severity mapping tables, `scan_run_id` lifecycle, dedup collision policy, commit-ref/dirty-tree handling, SARIF translator detail, Wardline state-file table, HTTP endpoint list, token-auth full spec, operator-guidance security subsection, §11 Suite Bootstrap prerequisite detail, testing/observability/acceptance, full ADR backlog, appendices | These are the implementation details that don't belong in higher layers | +| Suite naming aligned: "three-tool suite" framing replaced by "Weft suite" / "Weft fabric" references, with `docs/suite/weft.md` as the canonical doctrine source | Weft is now the family name; see [../../suite/weft.md](../../suite/weft.md) for federation axiom and composition law | | Section numbering renumbered 1-11 (not gapped) to match the document's reduced scope | Readers don't encounter gaps in numbering; cleaner navigation | ### Rev 4 changes (from follow-up review, 2026-04-17) @@ -1842,11 +1842,11 @@ Revision 5 (2026-04-17) restructures the single design document into a three-lay | Feedback item | Action taken | Now in | |---|---|---| | Gap 1: Triage state has no path back into briefings | Read-only feedback loop in briefing composition; operator-acknowledged evidence / synthetic risk entries; `guidance_fingerprint` includes acknowledged finding IDs | System-design §7 Composition, System-design §3 EntityBriefing | -| Gap 2: Degraded-mode matrix incomplete (combinations & version skew) | Capability-negotiation probe at `clarion analyze` startup; single `CLA-INFRA-SUITE-COMPAT-REPORT` finding summarising all probe results; per-component fallback table covers pre-flag Filigree, REGISTRY additive skew, SARIF property-bag removal | System-design §11 | -| Gap 3: SQLite merge conflicts on committed `.clarion/clarion.db` | Promoted `clarion db export --textual` to v0.1; added `clarion db merge-helper` with git-merge-driver integration | §3 | -| Gap 4: No story for entity deletion between runs | `CLA-FACT-ENTITY-DELETED` emitted at Phase 7 via entity-set diff; `CLA-FACT-GUIDANCE-ORPHAN` for affected sheets; cache invalidation on deletion | §5, System-design §6 | -| Leverage 1: Clarion as cross-tool identity oracle | New `GET /api/v1/entities/resolve?scheme=&value=` endpoint; covers wardline_qualname, wardline_exception_location, file_path, sarif_logical_location | §7 | -| Leverage 2: Subsystem-tier-mixing rule | `CLA-FACT-TIER-SUBSYSTEM-MIXING` and `CLA-FACT-SUBSYSTEM-TIER-UNANIMOUS` added to Phase 7; unique-to-Clarion structural signal | §5 | +| Gap 2: Degraded-mode matrix incomplete (combinations & version skew) | Capability-negotiation probe at `loomweave analyze` startup; single `LMWV-INFRA-SUITE-COMPAT-REPORT` finding summarising all probe results; per-component fallback table covers pre-flag Filigree, REGISTRY additive skew, SARIF property-bag removal | System-design §11 | +| Gap 3: SQLite merge conflicts on committed `.loomweave/loomweave.db` | Promoted `loomweave db export --textual` to v0.1; added `loomweave db merge-helper` with git-merge-driver integration | §3 | +| Gap 4: No story for entity deletion between runs | `LMWV-FACT-ENTITY-DELETED` emitted at Phase 7 via entity-set diff; `LMWV-FACT-GUIDANCE-ORPHAN` for affected sheets; cache invalidation on deletion | §5, System-design §6 | +| Leverage 1: Loomweave as cross-tool identity oracle | New `GET /api/v1/entities/resolve?scheme=&value=` endpoint; covers wardline_qualname, wardline_exception_location, file_path, sarif_logical_location | §7 | +| Leverage 2: Subsystem-tier-mixing rule | `LMWV-FACT-TIER-SUBSYSTEM-MIXING` and `LMWV-FACT-SUBSYSTEM-TIER-UNANIMOUS` added to Phase 7; unique-to-Loomweave structural signal | §5 | | Leverage 3: `knowledge_basis` has no promotion path | Added promotion rule: guidance authorship matching an entity, or acknowledged/suppressed finding with reason, promotes briefing to `HumanVerified` | §2 EntityBriefing | | Leverage 4: Commit SHAs on runs but not on entities | Added `first_seen_commit` / `last_seen_commit` to Entity struct and schema; indexed; dirty-tree runs use underlying SHA | §2, §3 | | Small: SARIF translator framing | Reframed as a permanent general-purpose feature (Semgrep, CodeQL, etc.); only Wardline-specific ownership moves in v0.2 | §7 | @@ -1857,27 +1857,27 @@ Revision 5 (2026-04-17) restructures the single design document into a three-lay | Recon item | Action taken | Now in | |---|---|---| -| §1 headline: suite fabric does not exist yet | Reframed v0.1 scope as "weaving the Loom fabric"; Abstract rewritten; new §11 Suite Bootstrap | System-design, §9 | +| §1 headline: suite fabric does not exist yet | Reframed v0.1 scope as "weaving the Weft fabric"; Abstract rewritten; new §11 Suite Bootstrap | System-design, §9 | | §3.1 Wire format wrong — `properties`→`metadata`, `line`→`line_start/line_end` | Finding struct and wire-format block rewritten; full example JSON emitted | §2, §7 | -| §3.2 Severity enum wrong — wire values `{critical,high,medium,low,info}` | Mapping table added; internal vocabulary preserved in `metadata.clarion.internal_severity`; `warnings[]` response inspection required | §7 | +| §3.2 Severity enum wrong — wire values `{critical,high,medium,low,info}` | Mapping table added; internal vocabulary preserved in `metadata.loomweave.internal_severity`; `warnings[]` response inspection required | §7 | | §3.3 Wardline groups 9/12/13 are decorator-based (Rev 2 fix was wrong) | v1.0 no longer advertises Wardline group detection in the Python manifest; decorator/Wardline ontology is deferred until extractor support exists | §1 manifest | | §3.4 Wardline tier vocabulary uses `INTEGRAL/ASSURED/GUARDED/EXTERNAL_RAW`, not T1–T4 | Glossary and WardlineMeta struct corrected; `declared_tier: Option` | §2, Appendix B | -| §3.5 Wardline has no Filigree integration today | SARIF→Filigree translator (Clarion-side `clarion sarif import`) specified in §7; §9.2 documents Wardline prerequisite | §7, §9.2 | +| §3.5 Wardline has no Filigree integration today | SARIF→Filigree translator (Loomweave-side `loomweave sarif import`) specified in §7; §9.2 documents Wardline prerequisite | §7, §9.2 | | §3.6 `registry_backend` flag does not exist in Filigree | ADR-014 added; §9.1 names the schema surgery (4 NOT-NULL FKs, 3 auto-create paths, 5–8 hot files); degraded-mode fallback in System-design §11 | §7, §9.1, ADR-014 | -| §3.7 Observation API is MCP-only in Filigree | ADR-016 added; §9.1 asks Filigree for `POST /api/v1/observations`; Clarion workaround (MCP client) documented | §9.1 | -| §3.8 HTTP routes parallel displaced MCP tools | §7 addresses: pick redirect-to-Clarion or shadow-read; cannot mix | §7, ADR-014 | +| §3.7 Observation API is MCP-only in Filigree | ADR-016 added; §9.1 asks Filigree for `POST /api/v1/observations`; Loomweave workaround (MCP client) documented | §9.1 | +| §3.8 HTTP routes parallel displaced MCP tools | §7 addresses: pick redirect-to-Loomweave or shadow-read; cannot mix | §7, ADR-014 | | §3.9 `get_issue` default-embeds file IDs | Documented as user-visible leak in §7 release-notes requirement | §7 | | §3.10 `_schema` does not enumerate `scan_source` | Glossary and §7 corrected; `scan_source` is social convention | §7, Appendix B | | §4.1 Wardline SARIF property-bag translation layer missing | Translator design added; `metadata.wardline_properties.*` nesting convention | §7 | | §4.2 Severity/rule-ID mapping tables missing | Mapping table + rule-ID round-trip policy added | §7 | | §4.3 `wardline.fingerprint.json` and other state files not ingested | State-file in/out table (10 files) added; fingerprint + exceptions in v0.1 scope | §7 Wardline | -| §4.4 `REGISTRY_VERSION` compatibility mechanism missing | Direct import with version pin; `CLA-INFRA-WARDLINE-REGISTRY-STALE` finding | §1, §9.2, ADR-018 | +| §4.4 `REGISTRY_VERSION` compatibility mechanism missing | Direct import with version pin; `LMWV-INFRA-WARDLINE-REGISTRY-STALE` finding | §1, §9.2, ADR-018 | | §4.5 Legacy decorator aliases unhandled | Row added to decorator-detection table | §1 Python plugin specifics | -| §4.6 Class decoration goes beyond Wardline | `confidence_basis: "clarion_augmentation"` tag added | §1 | +| §4.6 Class decoration goes beyond Wardline | `confidence_basis: "loomweave_augmentation"` tag added | §1 | | §4.7 Three auto-create paths in Filigree | Named explicitly in §9.1 under registry_backend | §9.1 | | §4.8 Observation transport ambiguity | ADR-016 resolves: HTTP endpoint in Filigree (primary); MCP client fallback | §9.1, ADR-016 | | §4.9 Scanner registration for Filigree dashboard | Deferred to v0.2; explicit non-silence | Requirements NG-* | -| §4.10 `scan_run_id` lifecycle | Mapped 1:1 to Clarion `run_id`; Phase 0 creates, Phase 8 completes | §7 | +| §4.10 `scan_run_id` lifecycle | Mapped 1:1 to Loomweave `run_id`; Phase 0 creates, Phase 8 completes | §7 | | §4.11 Dedup key collision on entity moves | `mark_unseen=true` + `--prune-unseen` policy; v0.2 server-side dedup deferral | §7, Requirements NG-21 | | §4.12 Integration test fixtures missing | §10 Acceptance Ecosystem expanded to 8 criteria; mock Filigree, SARIF corpus, pin tests, end-to-end smoke | §10 | | §4.13 Commit-ref dirty handling silent | Policy added: `-dirty` suffix recorded; `--require-clean` flag | §7 | @@ -1914,4 +1914,4 @@ The Rev 2 → Rev 3 transition is the classic layered-review problem. Rev 2 corr --- -**End of Clarion v1.0 detailed design reference.** +**End of Loomweave v1.0 detailed design reference.** diff --git a/docs/clarion/1.0/operations.md b/docs/loomweave/1.0/operations.md similarity index 60% rename from docs/clarion/1.0/operations.md rename to docs/loomweave/1.0/operations.md index aed091d1..6bea2860 100644 --- a/docs/clarion/1.0/operations.md +++ b/docs/loomweave/1.0/operations.md @@ -1,37 +1,37 @@ -# Clarion v1.0 Storage Operations +# Loomweave v1.0 Storage Operations -Operator-facing reference for the constraints around Clarion's local-state +Operator-facing reference for the constraints around Loomweave's local-state directory. v1.0 is local-first; the storage subsystem is plain SQLite under -`.clarion/`. The constraints below come straight out of +`.loomweave/`. The constraints below come straight out of [ADR-011](../adr/ADR-011-storage-architecture.md) (writer-actor + reader-pool over SQLite) and the v1.0 tag-cut gap-register entries DOC-11, STO-01, STO-04, and STO-05. ## 1. Local-first storage layout -Per ADR-011, every Clarion project keeps its state in a `.clarion/` +Per ADR-011, every Loomweave project keeps its state in a `.loomweave/` directory at the project root: ``` -.clarion/ -├── clarion.db SQLite database (entities, edges, runs, findings, summary_cache) -├── clarion.db-wal SQLite WAL companion file -├── clarion.db-shm SQLite shared-memory file -├── clarion.lock fs2 advisory lock file (writer claim) +.loomweave/ +├── loomweave.db SQLite database (entities, edges, runs, findings, summary_cache) +├── loomweave.db-wal SQLite WAL companion file +├── loomweave.db-shm SQLite shared-memory file +├── loomweave.lock fs2 advisory lock file (writer claim) ├── instance_id Stable per-project instance ID └── ... plugin caches, scanner baseline, etc. ``` There is no central server, no shared registry, no networked state. -`clarion install --path` creates `.clarion/` on a new project root; -`clarion analyze` and `clarion serve` both read and (in `analyze`'s case) +`loomweave install --path` creates `.loomweave/` on a new project root; +`loomweave analyze` and `loomweave serve` both read and (in `analyze`'s case) write into it. ## 2. NFS is prohibited -**`.clarion/` MUST live on a local filesystem.** Do not place a project root -on NFS, SMB, sshfs, or any other network filesystem and expect `clarion -analyze` or `clarion serve` to behave correctly. +**`.loomweave/` MUST live on a local filesystem.** Do not place a project root +on NFS, SMB, sshfs, or any other network filesystem and expect `loomweave +analyze` or `loomweave serve` to behave correctly. The two specific failure modes: @@ -44,16 +44,16 @@ The two specific failure modes: kernel does not provide across NFS. A WAL-mode database opened over NFS can lose committed writes or corrupt the database file. -If your only available storage is networked, do not use Clarion against +If your only available storage is networked, do not use Loomweave against that workspace. Clone the project to local disk first. ## 3. No double-analyze -Only one `clarion analyze` may run against a given project root at a time. +Only one `loomweave analyze` may run against a given project root at a time. The v1.0 binary enforces this with an exclusive `fs2` advisory lock on -`.clarion/clarion.lock`, acquired at the start of `analyze` and held for the -writer-actor lifetime. A second `clarion analyze` against the same -`.clarion/` fails fast with a clear "another clarion analyze is in progress +`.loomweave/loomweave.lock`, acquired at the start of `analyze` and held for the +writer-actor lifetime. A second `loomweave analyze` against the same +`.loomweave/` fails fast with a clear "another loomweave analyze is in progress against this project" error rather than racing the first analyzer's run state. (See STO-01 in the v1.0 tag-cut gap register for the originating finding.) @@ -61,13 +61,13 @@ finding.) The lock is process-scoped, not user-scoped: the same user starting two analyzers in two terminals is the typical mistake the lock prevents. -`clarion serve` is read-only against the database and does not contend with +`loomweave serve` is read-only against the database and does not contend with `analyze` on the writer lock, but it shares the same database file via the reader pool and will observe whichever state `analyze` last committed. **Same-process restart caveat (STO-05):** the fs2 lock is released when the analyzer process exits, including on crash. If the previous analyzer -exited abnormally, the next `clarion analyze` will sweep +exited abnormally, the next `loomweave analyze` will sweep `runs.status='running'` rows to `'failed'` (see `recover_preexisting_running_runs`). There is no PID column or heartbeat on `runs` at v1.0 — a same-host restart cannot distinguish "previous @@ -79,14 +79,14 @@ follow-up. ## 4. Supported backup procedure The v1.0 supported backup procedure is a four-step shutdown-and-copy. There -is no live `clarion db backup` subcommand at v1.0 (deferred to v1.1, §6). +is no live `loomweave db backup` subcommand at v1.0 (deferred to v1.1, §6). -1. **Ensure no `clarion analyze` is running** against the project root. - The fs2 advisory lock on `.clarion/clarion.lock` will be released; a - subsequent `clarion analyze` from a backup script would otherwise race +1. **Ensure no `loomweave analyze` is running** against the project root. + The fs2 advisory lock on `.loomweave/loomweave.lock` will be released; a + subsequent `loomweave analyze` from a backup script would otherwise race with the backup. -2. **Stop `clarion serve` or wait for it to be idle.** `serve` only reads, +2. **Stop `loomweave serve` or wait for it to be idle.** `serve` only reads, so a torn copy from a running `serve` is less catastrophic than from `analyze`, but a clean shutdown is required for the WAL checkpoint to complete. @@ -95,23 +95,23 @@ is no live `clarion db backup` subcommand at v1.0 (deferred to v1.1, §6). captures all committed state: ```bash - sqlite3 .clarion/clarion.db "PRAGMA wal_checkpoint(TRUNCATE);" + sqlite3 .loomweave/loomweave.db "PRAGMA wal_checkpoint(TRUNCATE);" ``` `TRUNCATE` mode is the strongest checkpoint — it flushes the WAL into - `clarion.db` and resets `clarion.db-wal` to zero length. + `loomweave.db` and resets `loomweave.db-wal` to zero length. **Why this step matters:** in WAL mode, committed pages live in - `clarion.db-wal` until a checkpoint folds them back into `clarion.db`. A - naive `cp .clarion/clarion.db backup.db` during (or shortly after) a live + `loomweave.db-wal` until a checkpoint folds them back into `loomweave.db`. A + naive `cp .loomweave/loomweave.db backup.db` during (or shortly after) a live `analyze` therefore captures a *torn* copy — the main database file is missing the most recent committed transactions, which are still sitting in the separate `-wal` file. Forcing a `TRUNCATE` checkpoint first guarantees - `clarion.db` is self-contained before the copy. + `loomweave.db` is self-contained before the copy. -4. **Copy `.clarion/` to the backup location** with any standard tool - (`cp -a`, `rsync -a`, `tar`). All three of `clarion.db`, - `clarion.db-wal`, and `clarion.db-shm` should be present in the copy; +4. **Copy `.loomweave/` to the backup location** with any standard tool + (`cp -a`, `rsync -a`, `tar`). All three of `loomweave.db`, + `loomweave.db-wal`, and `loomweave.db-shm` should be present in the copy; after a successful TRUNCATE the WAL is empty but the file should still be copied. @@ -119,19 +119,19 @@ is no live `clarion db backup` subcommand at v1.0 (deferred to v1.1, §6). To restore a backup: -1. Stop any running `clarion analyze` or `clarion serve` against the +1. Stop any running `loomweave analyze` or `loomweave serve` against the project root. -2. Replace the project's `.clarion/` directory with the backup copy. The - `instance_id` file inside `.clarion/` is part of the backup; restoring +2. Replace the project's `.loomweave/` directory with the backup copy. The + `instance_id` file inside `.loomweave/` is part of the backup; restoring it preserves the project's federation identity (`/api/v1/_capabilities` `instance_id` stays stable across the restore). -3. Run `clarion analyze` to validate the restored database. A fresh analyze +3. Run `loomweave analyze` to validate the restored database. A fresh analyze re-applies any pending migrations and exercises the integrity-check path on the e2e test surface. -## 6. v1.1 follow-up: `clarion db backup` subcommand +## 6. v1.1 follow-up: `loomweave db backup` subcommand -A live-safe `clarion db backup ` subcommand backed by SQLite's online +A live-safe `loomweave db backup ` subcommand backed by SQLite's online backup API is tracked for v1.1. It will replace steps 1–4 of §4 with a single command that takes a snapshot without requiring `analyze` and `serve` to be quiesced. Until then, follow §4. diff --git a/docs/clarion/1.0/requirements.md b/docs/loomweave/1.0/requirements.md similarity index 65% rename from docs/clarion/1.0/requirements.md rename to docs/loomweave/1.0/requirements.md index 34ac09d5..beb2c081 100644 --- a/docs/clarion/1.0/requirements.md +++ b/docs/loomweave/1.0/requirements.md @@ -1,4 +1,4 @@ -# Clarion v1.0 — Requirements +# Loomweave v1.0 — Requirements **Status**: Baselined for v1.0 release (carried forward from v0.1 baseline 2026-04-17 with the Sprint 3 federation-hardening additions) **Baseline**: 2026-04-17 · **Last updated**: 2026-05-19 @@ -17,27 +17,27 @@ ### What this document is -This is the requirements specification for Clarion v1.0 — the *what*: capabilities, constraints, quality attributes, and explicit non-goals. The *how* (architecture, mechanisms, diagrams) lives in the system-design; the *implementation detail* (SQL schemas, Rust crate selection, rule-ID catalogues) lives in the detailed-design. +This is the requirements specification for Loomweave v1.0 — the *what*: capabilities, constraints, quality attributes, and explicit non-goals. The *how* (architecture, mechanisms, diagrams) lives in the system-design; the *implementation detail* (SQL schemas, Rust crate selection, rule-ID catalogues) lives in the detailed-design. ### How to read this - Each requirement has a stable ID (`REQ-*`, `NFR-*`, `CON-*`, `NG-*`), a plain-English statement, a rationale, a verification method, and a **See** line pointing to the system-design section that addresses it. - Requirement IDs are load-bearing: filigree issues cite them by ID in descriptions and commit messages; code review references them when discussing implementation. IDs are stable across rev bumps unless a requirement is fully retired (in which case the ID is never reused). -- "Clarion" throughout means Clarion v1.0 specifically. Where v2.0+ behaviour is deliberately different, it's named as a non-goal (`NG-*`) in the Non-Goals section of this document. +- "Loomweave" throughout means Loomweave v1.0 specifically. Where v2.0+ behaviour is deliberately different, it's named as a non-goal (`NG-*`) in the Non-Goals section of this document. -### Relationship to Loom +### Relationship to Weft -Clarion is one product in the **Loom** suite (see [../../suite/loom.md](../../suite/loom.md) for the family's founding doctrine). These requirements respect the Loom federation axiom: Clarion must be useful standalone, must compose pairwise with each sibling product, and must never become load-bearing for another product's semantics. That posture is formalised in `CON-LOOM-01`. +Loomweave is one product in the **Weft** suite (see [../../suite/weft.md](../../suite/weft.md) for the family's founding doctrine). These requirements respect the Weft federation axiom: Loomweave must be useful standalone, must compose pairwise with each sibling product, and must never become load-bearing for another product's semantics. That posture is formalised in `CON-WEFT-01`. ### Design principles (framing, not numbered requirements) The five design principles from the detailed-design frame the specific requirements that follow. When a requirement could be interpreted more than one way, these principles break ties: 1. **Enterprise at lack of scale.** Bring governance / trust-topology / audit-trail rigor to small teams without enterprise operational weight. Single-binary, file-based state, SQLite, local-first, composable through open protocols. -2. **Exploration elimination.** Every question an LLM explore-agent has to spawn for is a question Clarion should have answered from cache. Batch analysis pre-computes; MCP responses stay bounded. +2. **Exploration elimination.** Every question an LLM explore-agent has to spawn for is a question Loomweave should have answered from cache. Batch analysis pre-computes; MCP responses stay bounded. 3. **Plugin owns ontology; core owns algorithms.** The Rust core is language-*agnostic* — no fixed enum of entity kinds, no hardcoded concept of "function" or "class." Language plugins declare the ontology they emit. 4. **Finding as fact exchange.** Findings are claims-with-evidence, not just errors: defects, structural observations, classifications, metrics, and suggestions share one record type. Findings are the suite-wide cross-tool exchange format. -5. **Observe vs. enforce is a strict boundary.** Clarion observes; Wardline enforces. Clarion's plugin detects *that* an annotation is present; Wardline determines *whether* the annotated code satisfies the semantic it declares. +5. **Observe vs. enforce is a strict boundary.** Loomweave observes; Wardline enforces. Loomweave's plugin detects *that* an annotation is present; Wardline determines *whether* the annotated code satisfies the semantic it declares. ### Glossary @@ -46,14 +46,14 @@ The terms below are the ones this requirements layer uses most often: | Term | Definition | |---|---| | **Briefing** | Structured summary answering a fixed set of questions about an entity. | -| **Entity** | Typed node in Clarion's property graph. | +| **Entity** | Typed node in Loomweave's property graph. | | **Entity ID** | Stable identifier of the form `{plugin_id}:{kind}:{canonical_qualified_name}`. | | **Edge** | Typed relationship between entities such as `contains`, `calls`, or `imports`. | | **Finding** | Structured claim-with-evidence; may be a defect, fact, classification, metric, or suggestion. | | **Guidance fingerprint** | Hash of the guidance sheets applied to a query; part of the summary-cache key. | | **Plugin manifest** | YAML declaration of a plugin's kinds, edges, rules, and capabilities. | | **Scope lens** | Query/session filter that biases neighbour lookups toward a relationship family. | -| **Tier** | Wardline trust classification preserved verbatim by Clarion. | +| **Tier** | Wardline trust classification preserved verbatim by Loomweave. | | **Writer-actor** | Single task that owns SQLite writes; other tasks submit mutations through it. | | **Pre-ingest redaction** | Secret scan that runs before any file content is sent to the LLM provider. | | **Capability probe / compat report** | Startup check of sibling-tool availability that emits one compatibility finding. | @@ -69,25 +69,25 @@ See [detailed-design.md](./detailed-design.md) Appendix B for the full glossary. Producing the entity/edge/subsystem catalog from source. -#### REQ-CATALOG-01 — Entity catalog from `clarion analyze` +#### REQ-CATALOG-01 — Entity catalog from `loomweave analyze` -Clarion produces a typed entity catalog (functions, classes, modules, packages, subsystems, files, guidance sheets) from an input source tree via the `clarion analyze` command. +Loomweave produces a typed entity catalog (functions, classes, modules, packages, subsystems, files, guidance sheets) from an input source tree via the `loomweave analyze` command. -**Rationale**: Catalog production is the core product output — every downstream capability (briefings, MCP navigation, HTTP read API, guidance matching) reads the catalog. Without it, Clarion has nothing to serve. -**Verification**: Running `clarion analyze` against the elspeth fixture produces a populated store with ≥1 entity per known kind. +**Rationale**: Catalog production is the core product output — every downstream capability (briefings, MCP navigation, HTTP read API, guidance matching) reads the catalog. Without it, Loomweave has nothing to serve. +**Verification**: Running `loomweave analyze` against the elspeth fixture produces a populated store with ≥1 entity per known kind. **See**: System Design §3 (Data Model), §6 (Analysis Pipeline). #### REQ-CATALOG-02 — Plugin-declared entity kinds Entity kinds are strings declared by a language plugin's manifest, not a fixed enum in the core. Adding a new kind (e.g., `protocol`, `dataclass`) must not require core changes. -**Rationale**: Principle 3 (plugin owns ontology). A core with a fixed entity-kind enum rots the moment a language or framework introduces a new abstraction; Clarion's structural model must survive those additions without releases. +**Rationale**: Principle 3 (plugin owns ontology). A core with a fixed entity-kind enum rots the moment a language or framework introduces a new abstraction; Loomweave's structural model must survive those additions without releases. **Verification**: Fixture test with a plugin declaring a novel kind (`test_custom_kind`) produces entities of that kind without core code changes. **See**: System Design §2 (Core / Plugin Architecture), §3 (Data Model). #### REQ-CATALOG-03 — Edges as typed relationships -Clarion records typed edges between entities. Core reserves `contains`, `guides`, `emits_finding`, and `in_subsystem`; all other edge kinds are plugin-declared. +Loomweave records typed edges between entities. Core reserves `contains`, `guides`, `emits_finding`, and `in_subsystem`; all other edge kinds are plugin-declared. **Rationale**: Edges are how navigation queries and clustering work. A language-agnostic core with a handful of reserved edges keeps the property-graph generic while giving plugins full expressive range for language-specific relationships (Python's `imports`, `calls`, `inherits_from`, `decorated_by`). **Verification**: Plugin emits edges with plugin-defined kinds; core persists them; `neighbors(id, edge_kind=...)` returns them correctly. @@ -103,18 +103,18 @@ Files are entities in the catalog (`kind: file`) with metadata including git chu #### REQ-CATALOG-05 — Subsystem entities from clustering -Clarion clusters the entity graph (imports + calls at module level) and emits one `subsystem` entity per cluster. Cluster members have `in_subsystem` edges to the subsystem. +Loomweave clusters the entity graph (imports + calls at module level) and emits one `subsystem` entity per cluster. Cluster members have `in_subsystem` edges to the subsystem. **Rationale**: Subsystems make large codebases navigable at a higher level of abstraction than modules — an agent reasoning about "the authentication subsystem" should be able to zoom to that cluster without re-deriving its membership. Clustering is core-owned because it depends on whole-graph structure no single plugin sees. -**Verification**: `clarion analyze` on a multi-module fixture emits subsystem entities with populated members; `subsystem_members(id)` returns the clustered entity list. +**Verification**: `loomweave analyze` on a multi-module fixture emits subsystem entities with populated members; `subsystem_members(id)` returns the clustered entity list. **See**: System Design §3 (Data Model), §6 Phase 3 (Clustering). #### REQ-CATALOG-06 — Symbolic entity IDs (not path-embedded) Entity IDs follow `{plugin_id}:{kind}:{canonical_qualified_name}` for source entities; file paths are a property on the entity's `SourceRange`, not part of the ID. -**Rationale**: Cross-tool identity (Filigree issues referencing Clarion entities, Wardline findings carrying qualnames) depends on ID stability across file moves. A path-embedded ID silently detaches every reference when a file moves; canonical-name IDs survive the 80% case (file move without symbol rename). -**Verification**: Move a module on disk without renaming its symbols; run `clarion analyze`; assert that affected entity IDs are unchanged. +**Rationale**: Cross-tool identity (Filigree issues referencing Loomweave entities, Wardline findings carrying qualnames) depends on ID stability across file moves. A path-embedded ID silently detaches every reference when a file moves; canonical-name IDs survive the 80% case (file move without symbol rename). +**Verification**: Move a module on disk without renaming its symbols; run `loomweave analyze`; assert that affected entity IDs are unchanged. **See**: System Design §3 (Data Model, Identity). #### REQ-CATALOG-07 — `first_seen_commit` and `last_seen_commit` on every entity @@ -129,7 +129,7 @@ Each entity records the git SHA of the first run that observed it and the SHA of ### Analysis Pipeline (`REQ-ANALYZE-*`) -Running `clarion analyze` over a source tree. +Running `loomweave analyze` over a source tree. #### REQ-ANALYZE-01 — Phased pipeline execution @@ -149,12 +149,12 @@ Within a single phase, LLM calls execute in parallel up to a configurable cap (d #### REQ-ANALYZE-03 — Resumable after crash or interrupt -> **v1.x status amended by ADR-041.** `clarion analyze --resume ` +> **v1.x status amended by ADR-041.** `loomweave analyze --resume ` > reopens the existing run row, re-walks idempotently, and emits Filigree > findings with `mark_unseen=false`. It does **not** promise durable > phase/file checkpoint recovery or skipped provider calls. -`clarion analyze --resume ` is a same-run repair path: it preserves +`loomweave analyze --resume ` is a same-run repair path: it preserves Filigree seen/unseen semantics while safely repeating deterministic writes. Checkpoint recovery is deferred until a successor ADR defines the durable checkpoint schema and provider-call accounting. @@ -170,44 +170,44 @@ phase/file skipping unless a successor checkpoint ADR lands. #### REQ-ANALYZE-04 — Deletion detection via entity-set diff -At Phase 7, Clarion compares the current run's entity IDs against the prior run's set and emits `CLA-FACT-ENTITY-DELETED` per missing entity, invalidates summary cache rows for deleted entities, and emits `CLA-FACT-GUIDANCE-ORPHAN` for guidance sheets pointing at deleted IDs. +At Phase 7, Loomweave compares the current run's entity IDs against the prior run's set and emits `LMWV-FACT-ENTITY-DELETED` per missing entity, invalidates summary cache rows for deleted entities, and emits `LMWV-FACT-GUIDANCE-ORPHAN` for guidance sheets pointing at deleted IDs. -> **v1.1 status (built).** Implemented in the SEI mint pass, which already computes the orphaned-binding set (prior-run locators absent from the current run, *excluding renames*, which are carried). A locator IS an entity id (ADR-038), so that set is exactly this requirement's deleted-entity set. Each deleted entity gets a `CLA-FACT-ENTITY-DELETED` finding (anchored to its own never-pruned row), its cached summaries are deleted (the `summary_cache` `ON DELETE CASCADE` never fires because `entities` is cumulative), and every guidance sheet whose explicit `guides` edge targets a deleted entity gets `CLA-FACT-GUIDANCE-ORPHAN`. **Caveat:** deletion detection rides the SEI pass, so `--no-sei` disables it (consistent with `--no-sei` disabling identity); the default path satisfies the verification method. **Filigree emission (resolved, `clarion-ef8f64d5fd`):** these findings are persisted *post-`CommitRun`*, after the pre-commit Phase-8 emission (`findings_for_emit(run_id)`) has already run. A second, additive Phase-8c emission pass re-reads the post-commit rules and POSTs them in the same run (`mark_unseen` mirrors Phase 8; `complete_scan_run=false` as it appends to the already-completed run). `CLA-FACT-ENTITY-DELETED` anchors to the deleted entity's own path-bearing row and emits normally. The Phase-8c pass is best-effort and enrich-only like Phase 8 (a Filigree outage never changes the run outcome) and posts nothing when a run deletes nothing. +> **v1.1 status (built).** Implemented in the SEI mint pass, which already computes the orphaned-binding set (prior-run locators absent from the current run, *excluding renames*, which are carried). A locator IS an entity id (ADR-038), so that set is exactly this requirement's deleted-entity set. Each deleted entity gets a `LMWV-FACT-ENTITY-DELETED` finding (anchored to its own never-pruned row), its cached summaries are deleted (the `summary_cache` `ON DELETE CASCADE` never fires because `entities` is cumulative), and every guidance sheet whose explicit `guides` edge targets a deleted entity gets `LMWV-FACT-GUIDANCE-ORPHAN`. **Caveat:** deletion detection rides the SEI pass, so `--no-sei` disables it (consistent with `--no-sei` disabling identity); the default path satisfies the verification method. **Filigree emission (resolved, `clarion-ef8f64d5fd`):** these findings are persisted *post-`CommitRun`*, after the pre-commit Phase-8 emission (`findings_for_emit(run_id)`) has already run. A second, additive Phase-8c emission pass re-reads the post-commit rules and POSTs them in the same run (`mark_unseen` mirrors Phase 8; `complete_scan_run=false` as it appends to the already-completed run). `LMWV-FACT-ENTITY-DELETED` anchors to the deleted entity's own path-bearing row and emits normally. The Phase-8c pass is best-effort and enrich-only like Phase 8 (a Filigree outage never changes the run outcome) and posts nothing when a run deletes nothing. **Rationale**: Without deletion detection, removed entities silently strand Filigree issues, guidance sheets, and summary-cache rows. A removed function's issues become un-actionable orphans; a removed class's guidance persists and affects briefings for entities that no longer exist. Explicit signal prevents this. -**Verification**: Run analyze, delete a file, re-run; assert a `CLA-FACT-ENTITY-DELETED` finding per previously-extracted entity in the file. +**Verification**: Run analyze, delete a file, re-run; assert a `LMWV-FACT-ENTITY-DELETED` finding per previously-extracted entity in the file. **See**: System Design §6 (Analysis Pipeline, Deletion). #### REQ-ANALYZE-05 — Phase-7 structural findings -Clarion emits structural findings that combine signals no single sibling tool can compute alone: `CLA-FACT-TIER-SUBSYSTEM-MIXING` (cluster members in disagreeing Wardline tiers), `CLA-FACT-SUBSYSTEM-TIER-UNANIMOUS` (positive signal for tier-consistency reports), and `CLA-FACT-ENTITY-DELETED`. +Loomweave emits structural findings that combine signals no single sibling tool can compute alone: `LMWV-FACT-TIER-SUBSYSTEM-MIXING` (cluster members in disagreeing Wardline tiers), `LMWV-FACT-SUBSYSTEM-TIER-UNANIMOUS` (positive signal for tier-consistency reports), and `LMWV-FACT-ENTITY-DELETED`. -> **v1.1 status (built).** The tier rules emit at Phase 7. Wardline tiers land on functions (`python:function:`), not modules, so each tier-bearing entity is resolved up its `contains` chain to its subsystem; over a subsystem's tier-bearing members, ≥2 distinct tiers ⇒ `CLA-FACT-TIER-SUBSYSTEM-MIXING`, one tier across ≥2 members ⇒ `CLA-FACT-SUBSYSTEM-TIER-UNANIMOUS`. **Conditional on a prior Wardline ingest:** `analyze` never writes tier facts (the enrich-only axiom), so a project that has not ingested Wardline produces no tier findings — correct, not a gap. **Filigree emission (resolved, `clarion-ef8f64d5fd`):** like the deletion findings, the tier findings are persisted post-`CommitRun` and reach Filigree via the same-run Phase-8c additive pass. Because they anchor to a synthetic subsystem entity with no `source_file_path` (and Filigree rejects path-less findings), the Phase-8c pass posts them against the **project-root** fallback path — mirroring the `core:project:*` `CLA-INFRA-*` anchor — and flags them `metadata.clarion.synthetic_anchor=true` so a consumer reads the path as a placeholder for a cluster, not a file location. `CLA-FACT-ENTITY-DELETED` is built under [REQ-ANALYZE-04](#req-analyze-04--deletion-detection-via-entity-set-diff). +> **v1.1 status (built).** The tier rules emit at Phase 7. Wardline tiers land on functions (`python:function:`), not modules, so each tier-bearing entity is resolved up its `contains` chain to its subsystem; over a subsystem's tier-bearing members, ≥2 distinct tiers ⇒ `LMWV-FACT-TIER-SUBSYSTEM-MIXING`, one tier across ≥2 members ⇒ `LMWV-FACT-SUBSYSTEM-TIER-UNANIMOUS`. **Conditional on a prior Wardline ingest:** `analyze` never writes tier facts (the enrich-only axiom), so a project that has not ingested Wardline produces no tier findings — correct, not a gap. **Filigree emission (resolved, `clarion-ef8f64d5fd`):** like the deletion findings, the tier findings are persisted post-`CommitRun` and reach Filigree via the same-run Phase-8c additive pass. Because they anchor to a synthetic subsystem entity with no `source_file_path` (and Filigree rejects path-less findings), the Phase-8c pass posts them against the **project-root** fallback path — mirroring the `core:project:*` `LMWV-INFRA-*` anchor — and flags them `metadata.loomweave.synthetic_anchor=true` so a consumer reads the path as a placeholder for a cluster, not a file location. `LMWV-FACT-ENTITY-DELETED` is built under [REQ-ANALYZE-04](#req-analyze-04--deletion-detection-via-entity-set-diff). -**Rationale**: These rules combine Phase-3 clustering with Wardline tier declarations and prior-run state — signals Clarion uniquely holds. Emitting them from the plugin would require the plugin to know about subsystems and prior runs (Principle 3 violation); emitting from the core places them where the signals already live. -**Verification**: Fixture with mixed tiers produces `CLA-FACT-TIER-SUBSYSTEM-MIXING`; uniform-tier subsystem produces the unanimous finding. +**Rationale**: These rules combine Phase-3 clustering with Wardline tier declarations and prior-run state — signals Loomweave uniquely holds. Emitting them from the plugin would require the plugin to know about subsystems and prior runs (Principle 3 violation); emitting from the core places them where the signals already live. +**Verification**: Fixture with mixed tiers produces `LMWV-FACT-TIER-SUBSYSTEM-MIXING`; uniform-tier subsystem produces the unanimous finding. **See**: System Design §6 (Analysis Pipeline, Phase 7). #### REQ-ANALYZE-06 — No silent fallbacks on failure -Every recoverable failure in the pipeline emits a structured finding (`CLA-PY-SYNTAX-ERROR`, `CLA-PY-TIMEOUT`, `CLA-INFRA-PLUGIN-CRASH`, `CLA-INFRA-LLM-ERROR`, `CLA-INFRA-BUDGET-WARNING`, etc.). No failure is silently swallowed; every finding is visible in `runs//stats.json`, the store, and Filigree. +Every recoverable failure in the pipeline emits a structured finding (`LMWV-PY-SYNTAX-ERROR`, `LMWV-PY-TIMEOUT`, `LMWV-INFRA-PLUGIN-CRASH`, `LMWV-INFRA-LLM-ERROR`, `LMWV-INFRA-BUDGET-WARNING`, etc.). No failure is silently swallowed; every finding is visible in `runs//stats.json`, the store, and Filigree. -> **Subcode reconciliation (v1.1, [ADR-039]-era cleanup)**: the Python plugin surfaces a syntax-failed file as a degraded module entity carrying `parse_status="syntax_error"` (extractor.py), so the persisted finding's canonical subcode is **`CLA-PY-SYNTAX-ERROR`** — this requirement's earlier `CLA-PY-PARSE-ERROR` spelling is retired in its favour. `CLA-PY-TIMEOUT` is emitted by a per-file analysis-timeout in the host (a slow plugin is killed and the finding persisted), not by the plugin itself. +> **Subcode reconciliation (v1.1, [ADR-039]-era cleanup)**: the Python plugin surfaces a syntax-failed file as a degraded module entity carrying `parse_status="syntax_error"` (extractor.py), so the persisted finding's canonical subcode is **`LMWV-PY-SYNTAX-ERROR`** — this requirement's earlier `LMWV-PY-PARSE-ERROR` spelling is retired in its favour. `LMWV-PY-TIMEOUT` is emitted by a per-file analysis-timeout in the host (a slow plugin is killed and the finding persisted), not by the plugin itself. > -> **v1.1 status (met for the analyze pipeline).** The pipeline now persists every recoverable failure to the store and reports the count in `stats.json`: `CLA-PY-SYNTAX-ERROR` (parse failure), `CLA-PY-TIMEOUT` (per-file analysis-timeout watchdog), `CLA-INFRA-PLUGIN-CRASH` (plugin crash), and the host `CLA-INFRA-*` ontology/protocol/limit diagnostics — anchored to the file's module entity or a synthetic `core:project:*` entity. Two narrow items remain: -> - **`CLA-INFRA-LLM-ERROR` / `CLA-INFRA-BUDGET-WARNING`** are not analyze-pipeline failures in v1.1 — LLM work is on-demand via the MCP `summary` tool ([ADR-030](../adr/ADR-030-on-demand-summary-scope.md)), not a batched analyze phase. They belong to the `serve` summary path, where a returned error to the agent is the visibility (no silent fallback). They re-enter the analyze pipeline only when the batched phases 4–6 land (v1.1+ per ADR-030). +> **v1.1 status (met for the analyze pipeline).** The pipeline now persists every recoverable failure to the store and reports the count in `stats.json`: `LMWV-PY-SYNTAX-ERROR` (parse failure), `LMWV-PY-TIMEOUT` (per-file analysis-timeout watchdog), `LMWV-INFRA-PLUGIN-CRASH` (plugin crash), and the host `LMWV-INFRA-*` ontology/protocol/limit diagnostics — anchored to the file's module entity or a synthetic `core:project:*` entity. Two narrow items remain: +> - **`LMWV-INFRA-LLM-ERROR` / `LMWV-INFRA-BUDGET-WARNING`** are not analyze-pipeline failures in v1.1 — LLM work is on-demand via the MCP `summary` tool ([ADR-030](../adr/ADR-030-on-demand-summary-scope.md)), not a batched analyze phase. They belong to the `serve` summary path, where a returned error to the agent is the visibility (no silent fallback). They re-enter the analyze pipeline only when the batched phases 4–6 land (v1.1+ per ADR-030). > - **Filigree visibility** rides on WP9-B (findings emission, `emit_findings` default-off); until it lands, the store + `stats.json` halves satisfy the operator-visibility intent. The "and Filigree" clause is met when emission is enabled. -**Rationale**: Silent fallbacks make debugging impossible and gradually erode trust — operators stop believing the catalog because "Clarion sometimes skips files for reasons I can't see." Explicit findings make the degradation visible and actionable. -**Verification**: Fixture with a deliberately malformed file produces `CLA-PY-SYNTAX-ERROR`; plugin timeout on a slow fixture produces `CLA-PY-TIMEOUT`; both are persisted to the store (and reach Filigree when emission is enabled). +**Rationale**: Silent fallbacks make debugging impossible and gradually erode trust — operators stop believing the catalog because "Loomweave sometimes skips files for reasons I can't see." Explicit findings make the degradation visible and actionable. +**Verification**: Fixture with a deliberately malformed file produces `LMWV-PY-SYNTAX-ERROR`; plugin timeout on a slow fixture produces `LMWV-PY-TIMEOUT`; both are persisted to the store (and reach Filigree when emission is enabled). **See**: System Design §6 (Analysis Pipeline, Failure & Degradation). #### REQ-ANALYZE-07 — Determinism of outputs -Back-to-back `clarion analyze` runs against identical source trees and the same recorded LLM provider produce byte-identical entity/edge/finding state (summary content may vary only if the LLM provider is non-recording). +Back-to-back `loomweave analyze` runs against identical source trees and the same recorded LLM provider produce byte-identical entity/edge/finding state (summary content may vary only if the LLM provider is non-recording). **Rationale**: Determinism is the foundation of the diff-based developer experience — without it, every run shows spurious changes and git-committed DBs become noise. Summaries use `temperature: 0`; clustering uses a seeded RNG; phase ordering is strict. -**Verification**: Snapshot test: two sequential runs against `tests/fixtures/tiny/` produce identical `clarion db export --textual` output. +**Verification**: Snapshot test: two sequential runs against `tests/fixtures/tiny/` produce identical `loomweave db export --textual` output. **See**: System Design §6 (Analysis Pipeline, Determinism). --- @@ -218,22 +218,22 @@ Structured summaries produced for each entity at policy-defined levels. #### REQ-BRIEFING-01 — Structured, not prose -> **v1.0 ships a 4-field on-demand summary** (`purpose`, `behavior`, `relationships`, `risks`) per [ADR-030](../adr/ADR-030-on-demand-summary-scope.md) (WP6 narrowed to leaf-tier on-demand `summary(id)`). The 9-field `EntityBriefing` schema below — `maturity` / `maturity_reasoning` / `patterns` / `antipatterns` / `knowledge_basis` / `notes` plus the `CLA-INFRA-BRIEFING-INVALID` retry path — is the v1.1 target when the briefing pipeline (Phases 4–6) lands per the [Sprint 2 scope amendment §4](../../implementation/sprint-2/scope-amendment-2026-05.md). +> **v1.0 ships a 4-field on-demand summary** (`purpose`, `behavior`, `relationships`, `risks`) per [ADR-030](../adr/ADR-030-on-demand-summary-scope.md) (WP6 narrowed to leaf-tier on-demand `summary(id)`). The 9-field `EntityBriefing` schema below — `maturity` / `maturity_reasoning` / `patterns` / `antipatterns` / `knowledge_basis` / `notes` plus the `LMWV-INFRA-BRIEFING-INVALID` retry path — is the v1.1 target when the briefing pipeline (Phases 4–6) lands per the [Sprint 2 scope amendment §4](../../implementation/sprint-2/scope-amendment-2026-05.md). Briefings follow a fixed schema (`purpose`, `maturity`, `maturity_reasoning`, `risks`, `patterns`, `antipatterns`, `relationships`, `knowledge_basis`, `notes`). Free-form prose is not an acceptable briefing output. **Rationale**: Principle 2 (exploration elimination) + Principle 4 (finding as fact exchange). LLM agents consume briefings as structured data to drive further navigation; prose requires re-parsing and is an order of magnitude worse for composability. -**Verification**: Every briefing in the store validates against the `EntityBriefing` schema; schema-invalid LLM responses retry once then emit `CLA-INFRA-BRIEFING-INVALID`. +**Verification**: Every briefing in the store validates against the `EntityBriefing` schema; schema-invalid LLM responses retry once then emit `LMWV-INFRA-BRIEFING-INVALID`. **See**: System Design §3 (Data Model, Entity Briefing). #### REQ-BRIEFING-02 — Controlled vocabulary for `patterns` / `antipatterns` / risk tags -> **Deferred to v1.1** per [ADR-030](../adr/ADR-030-on-demand-summary-scope.md) + [Sprint 2 scope amendment §4](../../implementation/sprint-2/scope-amendment-2026-05.md). The `patterns` / `antipatterns` fields belong to the 9-field `EntityBriefing` schema deferred in [REQ-BRIEFING-01](#req-briefing-01--structured-not-prose); the `CLA-FACT-VOCABULARY-CANDIDATE` rule and adversarial-docstring containment land with that pipeline. +> **Deferred to v1.1** per [ADR-030](../adr/ADR-030-on-demand-summary-scope.md) + [Sprint 2 scope amendment §4](../../implementation/sprint-2/scope-amendment-2026-05.md). The `patterns` / `antipatterns` fields belong to the 9-field `EntityBriefing` schema deferred in [REQ-BRIEFING-01](#req-briefing-01--structured-not-prose); the `LMWV-FACT-VOCABULARY-CANDIDATE` rule and adversarial-docstring containment land with that pipeline. -`patterns` and `antipatterns` fields draw from a controlled vocabulary (core base set + plugin extensions). Novel tags proposed by the LLM are accepted once but logged as `CLA-FACT-VOCABULARY-CANDIDATE` for human review; they do not silently promote into future prompts. +`patterns` and `antipatterns` fields draw from a controlled vocabulary (core base set + plugin extensions). Novel tags proposed by the LLM are accepted once but logged as `LMWV-FACT-VOCABULARY-CANDIDATE` for human review; they do not silently promote into future prompts. **Rationale**: An unconstrained vocabulary is a prompt-injection vector (see `NFR-SEC-03`) and drifts across runs, undermining comparability. A controlled vocabulary gives LLMs a fixed palette while preserving extensibility for genuinely new patterns. -**Verification**: Fixture with an adversarial docstring attempting to inject a novel `antipattern` produces a `CLA-FACT-VOCABULARY-CANDIDATE`; the term is not promoted into the controlled vocabulary without human review. +**Verification**: Fixture with an adversarial docstring attempting to inject a novel `antipattern` produces a `LMWV-FACT-VOCABULARY-CANDIDATE`; the term is not promoted into the controlled vocabulary without human review. **See**: System Design §3 (Data Model, Briefing), §10 (Security, Prompt-injection containment). #### REQ-BRIEFING-03 — Summary cache keyed on content + template + tier + guidance + TTL @@ -256,9 +256,9 @@ Every briefing carries `knowledge_basis: static_only | runtime_informed | human_ #### REQ-BRIEFING-05 — Triage-state feedback from Filigree -> **Deferred to v1.1** per the [Sprint 2 scope amendment §4](../../implementation/sprint-2/scope-amendment-2026-05.md) (WP9-B finding emission + WP7 guidance both deferred). Triage feedback requires Clarion-Filigree finding-state plumbing that does not ship in v1.0; the guidance-fingerprint side is moot until [REQ-GUIDANCE-02](#req-guidance-02--composition-algorithm) composition ships. `EMPTY_GUIDANCE_FINGERPRINT` is the v1.0 placeholder. +> **Deferred to v1.1** per the [Sprint 2 scope amendment §4](../../implementation/sprint-2/scope-amendment-2026-05.md) (WP9-B finding emission + WP7 guidance both deferred). Triage feedback requires Loomweave-Filigree finding-state plumbing that does not ship in v1.0; the guidance-fingerprint side is moot until [REQ-GUIDANCE-02](#req-guidance-02--composition-algorithm) composition ships. `EMPTY_GUIDANCE_FINGERPRINT` is the v1.0 placeholder. -When rendering a briefing, Clarion queries Filigree for findings on this entity with `status ∈ {suppressed, acknowledged}` and a non-empty reason, and surfaces those as either inline notes (≤3) or a synthetic `RiskItem` with `tag: operator-acknowledged` (>3). The guidance fingerprint incorporates the set of acknowledged finding IDs. +When rendering a briefing, Loomweave queries Filigree for findings on this entity with `status ∈ {suppressed, acknowledged}` and a non-empty reason, and surfaces those as either inline notes (≤3) or a synthetic `RiskItem` with `tag: operator-acknowledged` (>3). The guidance fingerprint incorporates the set of acknowledged finding IDs. **Rationale**: Operator triage decisions (suppressions, acknowledgements) are institutional knowledge in the same shape as guidance; they must flow back into briefings so the next LLM agent sees "this was already decided" rather than re-opening the conversation. **Verification**: Filigree finding suppressed with a reason; next briefing for the affected entity includes the acknowledgement; cache invalidates when the triage state changes. @@ -287,12 +287,12 @@ Institutional knowledge attached to entities and composed into prompts. Guidance sheets are entities of `kind: guidance` with properties `{content, scope_level, scope, match_rules, expires, pinned, provenance, ...}` (per ADR-024). They participate in the same navigation, finding, and cache machinery as code entities. **Rationale**: First-class entity status lets guidance share the identity, search, finding-emission, and MCP-tool infrastructure without parallel mechanisms. A guidance sheet can be inspected, linked to, and affected by findings in the same ways code entities are. -**Verification**: `clarion guidance create` produces an entity of `kind: guidance` with populated properties; `show_guidance(id)` returns it; findings can be emitted against it. +**Verification**: `loomweave guidance create` produces an entity of `kind: guidance` with populated properties; `show_guidance(id)` returns it; findings can be emitted against it. **See**: System Design §7 (Guidance System). #### REQ-GUIDANCE-02 — Composition algorithm -Given `(entity_id, query_type, model_tier)`, Clarion collects candidate sheets via match-rule resolution (path, kind, tag, subsystem, wardline-group, explicit), filters by scope and expiry, sorts by `scope_rank` ASC (project → subsystem → package → module → class → function), applies a token budget (preserving `pinned: true` sheets), and returns `(segments, sheets_used, sheets_dropped, fingerprint)`. +Given `(entity_id, query_type, model_tier)`, Loomweave collects candidate sheets via match-rule resolution (path, kind, tag, subsystem, wardline-group, explicit), filters by scope and expiry, sorts by `scope_rank` ASC (project → subsystem → package → module → class → function), applies a token budget (preserving `pinned: true` sheets), and returns `(segments, sheets_used, sheets_dropped, fingerprint)`. **Rationale**: Composition order matters — inner sheets override outer with controlled precedence — and the token budget must preserve `pinned` guidance. The fingerprint determines cache invalidation and must be deterministic given the same (entity, query, tier) inputs. **Verification**: Fixture with layered guidance (project + module + class) composes in the correct order; `pinned` sheets survive budget pressure that drops non-pinned ones; fingerprints are stable. @@ -300,7 +300,7 @@ Given `(entity_id, query_type, model_tier)`, Clarion collects candidate sheets v #### REQ-GUIDANCE-03 — Authoring workflows (CLI + MCP) -Operators author guidance via CLI (`clarion guidance create/edit/list/show/delete`) and via consult-mode MCP tool (`propose_guidance`). `propose_guidance` creates a Filigree *observation*, not a sheet; operator action (`clarion guidance promote `) is required to promote an observation into an active guidance sheet. +Operators author guidance via CLI (`loomweave guidance create/edit/list/show/delete`) and via consult-mode MCP tool (`propose_guidance`). `propose_guidance` creates a Filigree *observation*, not a sheet; operator action (`loomweave guidance promote `) is required to promote an observation into an active guidance sheet. **Rationale**: Gating LLM-proposed guidance behind explicit promotion prevents a single adversarial prompt from poisoning every future LLM call via the summary cache (`NFR-SEC-03`). CLI authoring covers the manual human workflow; MCP proposal covers in-session agent suggestions. **Verification**: `propose_guidance(...)` creates an observation in Filigree; active guidance sheet only exists after `guidance promote`; a compromised LLM cannot promote its own proposals. @@ -308,15 +308,15 @@ Operators author guidance via CLI (`clarion guidance create/edit/list/show/delet #### REQ-GUIDANCE-04 — Wardline-derived guidance -On every `clarion analyze` run with `wardline.yaml` present, Clarion auto-generates `provenance: wardline_derived` guidance sheets for declared tier assignments, boundary contracts, and annotation groups in use. Auto-generated sheets carry `pinned: true`. User edits are preserved (`provenance: wardline_derived_overridden`) across regenerations. +On every `loomweave analyze` run with `wardline.yaml` present, Loomweave auto-generates `provenance: wardline_derived` guidance sheets for declared tier assignments, boundary contracts, and annotation groups in use. Auto-generated sheets carry `pinned: true`. User edits are preserved (`provenance: wardline_derived_overridden`) across regenerations. -**Rationale**: Wardline declarations (tiers, contracts) are project-wide institutional knowledge Clarion already reads at analysis time. Regenerating the corresponding guidance sheets eliminates the manual labour of keeping them in sync with `wardline.yaml`; preserving user edits respects operator curation. +**Rationale**: Wardline declarations (tiers, contracts) are project-wide institutional knowledge Loomweave already reads at analysis time. Regenerating the corresponding guidance sheets eliminates the manual labour of keeping them in sync with `wardline.yaml`; preserving user edits respects operator curation. **Verification**: Fixture with `wardline.yaml` produces auto-derived sheets on first run; edit a sheet; re-run; assert the edit is preserved and flagged `wardline_derived_overridden`. **See**: System Design §7 (Guidance System, Wardline-derived). #### REQ-GUIDANCE-05 — Staleness signals tied to code churn -For each guidance sheet, Clarion computes the aggregate `git_churn_count` delta over matched entities since the sheet's `authored_at` (or `reviewed_at`, whichever is later). Exceeding a threshold (default 50 commits; 20 for `pinned: true` sheets) emits `CLA-FACT-GUIDANCE-CHURN-STALE` with `confidence: 0.7, confidence_basis: heuristic`. +For each guidance sheet, Loomweave computes the aggregate `git_churn_count` delta over matched entities since the sheet's `authored_at` (or `reviewed_at`, whichever is later). Exceeding a threshold (default 50 commits; 20 for `pinned: true` sheets) emits `LMWV-FACT-GUIDANCE-CHURN-STALE` with `confidence: 0.7, confidence_basis: heuristic`. **Rationale**: Guidance is an accumulating stock with no intrinsic quality signal — especially `pinned: true` sheets, which shape LLM output most. Tying staleness to churn gives operators a review signal without auto-expiring sheets (which risks dropping still-valid guidance). **Verification**: Fixture with high churn on matched entities produces the finding; lower threshold for pinned sheets fires earlier. @@ -324,7 +324,7 @@ For each guidance sheet, Clarion computes the aggregate `git_churn_count` delta #### REQ-GUIDANCE-06 — Export / import -`clarion guidance export --to ` writes guidance sheets as human-readable files; `clarion guidance import ` loads them back into the store. Both are deterministic (same input produces same output) and support round-tripping across Clarion installations. +`loomweave guidance export --to ` writes guidance sheets as human-readable files; `loomweave guidance import ` loads them back into the store. Both are deterministic (same input produces same output) and support round-tripping across Loomweave installations. **Rationale**: Guidance is committable team knowledge; export/import lets teams move guidance between machines, version it alongside code in a readable form, and review proposed guidance changes via standard diff tooling. **Verification**: Round-trip a set of guidance sheets through export → delete from store → import → diff the restored sheets against originals; assert identical. @@ -338,35 +338,35 @@ Emitting, classifying, and exchanging structured claims with evidence. #### REQ-FINDING-01 — Unified Finding record -Clarion uses a single `Finding` record shape covering five kinds: `Defect`, `Fact`, `Classification`, `Metric`, `Suggestion`. Each finding carries `entity_id`, `related_entities`, `rule_id`, `message`, `evidence`, `confidence`, `confidence_basis`, and status/triage fields. +Loomweave uses a single `Finding` record shape covering five kinds: `Defect`, `Fact`, `Classification`, `Metric`, `Suggestion`. Each finding carries `entity_id`, `related_entities`, `rule_id`, `message`, `evidence`, `confidence`, `confidence_basis`, and status/triage fields. -**Rationale**: Principle 4 (finding as fact exchange). A single record shape lets all tools in the Loom suite emit, consume, triage, and reason about findings uniformly — a defect, an architectural observation, and a metric all flow through the same pipe. +**Rationale**: Principle 4 (finding as fact exchange). A single record shape lets all tools in the Weft suite emit, consume, triage, and reason about findings uniformly — a defect, an architectural observation, and a metric all flow through the same pipe. **Verification**: Schema test: each kind round-trips through the store and through Filigree's wire format without information loss. **See**: System Design §3 (Data Model, Finding). #### REQ-FINDING-02 — Namespaced rule IDs -Clarion's rule IDs follow the `CLA-*` namespace: `CLA-PY-*` for Python-plugin structural findings, `CLA-FACT-*` for factual observations, `CLA-INFRA-*` for pipeline failures, `CLA-SEC-*` for security findings. Rule IDs round-trip byte-for-byte through Filigree (`rule_id` column is free-text). +Loomweave's rule IDs follow the `LMWV-*` namespace: `LMWV-PY-*` for Python-plugin structural findings, `LMWV-FACT-*` for factual observations, `LMWV-INFRA-*` for pipeline failures, `LMWV-SEC-*` for security findings. Rule IDs round-trip byte-for-byte through Filigree (`rule_id` column is free-text). -**Rationale**: Namespacing lets operators filter findings by source tool without Clarion parsing Wardline's or Semgrep's rule IDs. Free-text `rule_id` avoids enum coordination overhead while preserving social-convention namespacing. -**Verification**: Every rule Clarion emits matches its namespace prefix; a finding POSTed to Filigree returns unmodified `rule_id` on read-back. +**Rationale**: Namespacing lets operators filter findings by source tool without Loomweave parsing Wardline's or Semgrep's rule IDs. Free-text `rule_id` avoids enum coordination overhead while preserving social-convention namespacing. +**Verification**: Every rule Loomweave emits matches its namespace prefix; a finding POSTed to Filigree returns unmodified `rule_id` on read-back. **See**: System Design §9 (Integrations, Rule-ID round-trip). #### REQ-FINDING-03 — Emit findings to Filigree -> **Implemented (WP9-B core, post-1.0 `release:1.1`).** `clarion analyze` Phase 8 POSTs persisted findings to Filigree's `POST /api/v1/scan-results` intake; the wire contract is pinned in [`docs/federation/contracts.md`](../../federation/contracts.md#consumed-filigree-route-scan-results-intake-finding-emission). Emission is enrich-only and opt-in: gated behind `integrations.filigree.{enabled,emit_findings}`, **both default `false`**, and any Filigree-side failure is recorded in `stats.json` (`CLA-INFRA-FILIGREE-UNREACHABLE`) rather than failing the run. Findings anchored to a `briefing_blocked` entity are excluded, matching the fail-closed read posture. Clarion still emits to its own SQLite `findings` table regardless. +> **Implemented (WP9-B core, post-1.0 `release:1.1`).** `loomweave analyze` Phase 8 POSTs persisted findings to Filigree's `POST /api/v1/scan-results` intake; the wire contract is pinned in [`docs/federation/contracts.md`](../../federation/contracts.md#consumed-filigree-route-scan-results-intake-finding-emission). Emission is enrich-only and opt-in: gated behind `integrations.filigree.{enabled,emit_findings}`, **both default `false`**, and any Filigree-side failure is recorded in `stats.json` (`LMWV-INFRA-FILIGREE-UNREACHABLE`) rather than failing the run. Findings anchored to a `briefing_blocked` entity are excluded, matching the fail-closed read posture. Loomweave still emits to its own SQLite `findings` table regardless. -Clarion POSTs findings to Filigree via `POST /api/v1/scan-results` using Filigree's native intake schema. Clarion's richer fields (`kind`, `confidence`, `confidence_basis`, `supports`/`supported_by`, `related_entities`, internal severity, internal status) nest inside each finding's `metadata.clarion.*`. +Loomweave POSTs findings to Filigree via `POST /api/v1/scan-results` using Filigree's native intake schema. Loomweave's richer fields (`kind`, `confidence`, `confidence_basis`, `supports`/`supported_by`, `related_entities`, internal severity, internal status) nest inside each finding's `metadata.loomweave.*`. -**Rationale**: Filigree owns finding triage and lifecycle (per the Loom federation axiom — `CON-LOOM-01`). Emitting to Filigree surfaces Clarion's findings alongside Wardline's and any future scanners' in the team's unified triage view. -**Verification**: POST a Clarion finding to a mock Filigree; read back via Filigree API; assert `metadata.clarion.*` preserved verbatim; severity mapped per the spec. +**Rationale**: Filigree owns finding triage and lifecycle (per the Weft federation axiom — `CON-WEFT-01`). Emitting to Filigree surfaces Loomweave's findings alongside Wardline's and any future scanners' in the team's unified triage view. +**Verification**: POST a Loomweave finding to a mock Filigree; read back via Filigree API; assert `metadata.loomweave.*` preserved verbatim; severity mapped per the spec. **See**: System Design §9 (Integrations, Filigree). #### REQ-FINDING-04 — General-purpose SARIF → Filigree translator > **Deferred to v1.1** per the [Sprint 2 scope amendment §4](../../implementation/sprint-2/scope-amendment-2026-05.md) (WP10). The SARIF translator is independent of MCP surface delivery and lands with the v1.1 cross-product work. -Clarion ships `clarion sarif import [--scan-source ]` that translates any SARIF v2.1.0 emitter (Wardline, Semgrep, CodeQL, Trivy) to Filigree's scan-results format, preserving `result.properties` into `metadata._properties.*`. +Loomweave ships `loomweave sarif import [--scan-source ]` that translates any SARIF v2.1.0 emitter (Wardline, Semgrep, CodeQL, Trivy) to Filigree's scan-results format, preserving `result.properties` into `metadata._properties.*`. **Rationale**: The translator is a permanent suite feature (SARIF is external, Filigree's intake is not SARIF). For v0.1 it served Wardline-to-Filigree as a side-effect; in v0.2+ Wardline ships its own native POST, but the translator stays for third-party SARIF sources. **Verification**: SARIF corpus at `tests/fixtures/wardline-sarif/` translates to scan-results POSTs; `wardline.*` property-bag keys land in `metadata.wardline_properties.*`; severity mapped via `{error→high, warning→medium, note→info}`. @@ -374,9 +374,9 @@ Clarion ships `clarion sarif import [--scan-source ]` that translat #### REQ-FINDING-05 — `scan_run_id` lifecycle -> **Implemented (WP9-B, post-1.0 `release:1.1`); no Phase-0 handshake by design.** The wire shape ships: Clarion's `run_id` maps 1:1 onto Filigree's `scan_run_id`, and Phase 8 closes the run with `complete_scan_run=true`. **`--resume RUN_ID` now ships** (clarion-dd29e69e0e): a `WriterCmd::ResumeRun` reopens the prior run's `runs` row instead of `INSERT`ing (which conflicted on the existing `run_id`), the re-walk UPSERTs entities and run-scoped findings idempotently, and Phase 8 emits with `mark_unseen=false`. **Contract resolved (2026-05-31):** Filigree confirmed that tolerating an unknown `scan_run_id` — ingest the findings and reconstruct the run in history, no pre-create endpoint — is its *intended permanent* contract (path (a); Filigree `contracts.md` §F6 + `TestUnknownScanRunIdContract`). Clarion therefore emits **no Phase-0 create call**; it depends on that behavior and honors the three intake obligations pinned in [`docs/federation/contracts.md`](../../federation/contracts.md) (globally-unique UUIDv4 `run_id`, stable `scan_source`, benign-completion-warning handling). +> **Implemented (WP9-B, post-1.0 `release:1.1`); no Phase-0 handshake by design.** The wire shape ships: Loomweave's `run_id` maps 1:1 onto Filigree's `scan_run_id`, and Phase 8 closes the run with `complete_scan_run=true`. **`--resume RUN_ID` now ships** (clarion-dd29e69e0e): a `WriterCmd::ResumeRun` reopens the prior run's `runs` row instead of `INSERT`ing (which conflicted on the existing `run_id`), the re-walk UPSERTs entities and run-scoped findings idempotently, and Phase 8 emits with `mark_unseen=false`. **Contract resolved (2026-05-31):** Filigree confirmed that tolerating an unknown `scan_run_id` — ingest the findings and reconstruct the run in history, no pre-create endpoint — is its *intended permanent* contract (path (a); Filigree `contracts.md` §F6 + `TestUnknownScanRunIdContract`). Loomweave therefore emits **no Phase-0 create call**; it depends on that behavior and honors the three intake obligations pinned in [`docs/federation/contracts.md`](../../federation/contracts.md) (globally-unique UUIDv4 `run_id`, stable `scan_source`, benign-completion-warning handling). -Clarion's `run_id` maps 1:1 onto Filigree's `scan_run_id`. `clarion analyze` emits findings carrying that id with no pre-create handshake — Filigree ingests them and reconstructs the run in history (its confirmed permanent contract) — and Phase 8 closes the run with `complete_scan_run=true`. Resume (`--resume`) reuses the same `run_id` and posts with `mark_unseen=false`. +Loomweave's `run_id` maps 1:1 onto Filigree's `scan_run_id`. `loomweave analyze` emits findings carrying that id with no pre-create handshake — Filigree ingests them and reconstructs the run in history (its confirmed permanent contract) — and Phase 8 closes the run with `complete_scan_run=true`. Resume (`--resume`) reuses the same `run_id` and posts with `mark_unseen=false`. **Rationale**: Run lifecycle alignment lets Filigree apply "seen-in-latest" logic correctly. Without it, resumed runs would be indistinguishable from fresh runs and prior findings would flap to `unseen_in_latest` prematurely. **Verification**: Integration test with mock Filigree: analyze creates scan run; resumed analyze reuses the same ID; unseen transitions occur only after genuine completion. @@ -384,9 +384,9 @@ Clarion's `run_id` maps 1:1 onto Filigree's `scan_run_id`. `clarion analyze` emi #### REQ-FINDING-06 — Dedup policy for moved entities -> **Implemented (WP9-B, post-1.0 `release:1.1`).** Clarion POSTs with `mark_unseen=true` by default (Phase 8), so old-position findings transition to `unseen_in_latest`. `clarion analyze --prune-unseen` (Phase 8b) then POSTs Filigree's `POST /api/loom/findings/clean-stale` retention route (contract pinned in [`docs/federation/contracts.md`](../../federation/contracts.md)), scoped to `scan_source=clarion`, with the age threshold from `integrations.filigree.prune_unseen_days` (default 30). **Note — soft-archive, not delete:** Filigree owns the finding lifecycle and realises "removes" as an audit-preserving soft-archive (`unseen_in_latest` → `fixed`, auto-reopens on reappearance; Filigree ADR-015). Enrich-only: a Filigree outage or disabled integration is recorded in `stats.json` (`filigree_prune`), never fails the run. +> **Implemented (WP9-B, post-1.0 `release:1.1`).** Loomweave POSTs with `mark_unseen=true` by default (Phase 8), so old-position findings transition to `unseen_in_latest`. `loomweave analyze --prune-unseen` (Phase 8b) then POSTs Filigree's `POST /api/weft/findings/clean-stale` retention route (contract pinned in [`docs/federation/contracts.md`](../../federation/contracts.md)), scoped to `scan_source=loomweave`, with the age threshold from `integrations.filigree.prune_unseen_days` (default 30). **Note — soft-archive, not delete:** Filigree owns the finding lifecycle and realises "removes" as an audit-preserving soft-archive (`unseen_in_latest` → `fixed`, auto-reopens on reappearance; Filigree ADR-015). Enrich-only: a Filigree outage or disabled integration is recorded in `stats.json` (`filigree_prune`), never fails the run. -Clarion POSTs findings with `mark_unseen=true` by default so that old-position findings for the same rule on the same file transition to `unseen_in_latest` when the entity moves within the file. `clarion analyze --prune-unseen` removes `unseen_in_latest` findings older than 30 days (configurable). +Loomweave POSTs findings with `mark_unseen=true` by default so that old-position findings for the same rule on the same file transition to `unseen_in_latest` when the entity moves within the file. `loomweave analyze --prune-unseen` removes `unseen_in_latest` findings older than 30 days (configurable). **Rationale**: Filigree's dedup key includes `line_start`; entities moving within a file produce two findings. `mark_unseen` is the v0.1 compromise — acceptable coarseness until Filigree ships server-side per-entity dedup (v0.2). **Verification**: Fixture where a function moves from line 50 to line 80; two consecutive runs produce the expected `unseen_in_latest` state on the old finding. @@ -396,7 +396,7 @@ Clarion POSTs findings with `mark_unseen=true` by default so that old-position f ### MCP Consult Surface (`REQ-MCP-*`) -The MCP tool surface exposed by `clarion serve` for consult-mode LLM agents. +The MCP tool surface exposed by `loomweave serve` for consult-mode LLM agents. #### REQ-MCP-01 — Cursor-based session model @@ -412,7 +412,7 @@ Every MCP session has server-held state: cursor (`EntityId`), breadcrumb history > **v1.0 ships an 8-tool MVP subset** per the [Sprint 2 scope amendment §3 (Box B.6)](../../implementation/sprint-2/scope-amendment-2026-05.md): `entity_at`, `find_entity`, `callers_of`, `execution_paths_from`, `summary`, `issues_for`, `neighborhood`, plus `subsystem_members` (added in Sprint 3 with WP4 Phase-3 clustering). The remaining categories listed below — the cursor-based Navigation model, write-effect Inspection tools, Search, Findings operations, Guidance (deferred with WP7), and Session/scope — are deferred to v1.1 per the same amendment §4. The catalogue paragraph below documents the v1.1 target surface. -Clarion exposes MCP tools in the documented categories: Navigation (`goto`, `goto_path`, `back`, `zoom_out`, `zoom_in`, `breadcrumbs`); Inspection (`summary`, `source`, `metadata`, `guidance_for`, `findings_for`, `wardline_for`); Neighbours (`neighbors`, `callers`, `callees`, `children`, `imports_from`, `imported_by`, `in_subsystem`, `subsystem_members`); Search (`search_structural`, `search_semantic`, `find_by_tag`, `find_by_wardline`, `find_by_kind`); Findings & observability (`list_findings`, `emit_observation`, `promote_observation`, `cost_report`); Guidance (`show_guidance`, `list_guidance`, `propose_guidance`, `promote_guidance`); Session/scope (`set_scope_lens`, `session_info`). +Loomweave exposes MCP tools in the documented categories: Navigation (`goto`, `goto_path`, `back`, `zoom_out`, `zoom_in`, `breadcrumbs`); Inspection (`summary`, `source`, `metadata`, `guidance_for`, `findings_for`, `wardline_for`); Neighbours (`neighbors`, `callers`, `callees`, `children`, `imports_from`, `imported_by`, `in_subsystem`, `subsystem_members`); Search (`search_structural`, `search_semantic`, `find_by_tag`, `find_by_wardline`, `find_by_kind`); Findings & observability (`list_findings`, `emit_observation`, `promote_observation`, `cost_report`); Guidance (`show_guidance`, `list_guidance`, `propose_guidance`, `promote_guidance`); Session/scope (`set_scope_lens`, `session_info`). **Rationale**: Principle 2 (exploration elimination) requires the tool catalogue to cover the common explore-agent questions. Missing a category forces agents to spawn sub-agents to answer what the catalogue should have answered; bloating it dilutes the surface and raises consumption. **Verification**: For each documented tool, an MCP integration test calls it and asserts the response shape. @@ -422,7 +422,7 @@ Clarion exposes MCP tools in the documented categories: Navigation (`goto`, `got > **Deferred to v1.1** per the [Sprint 2 scope amendment §3 (Box B.6 — narrowed MCP surface)](../../implementation/sprint-2/scope-amendment-2026-05.md) and §4 (WP4 Phase-7 cross-cutting analysis deferred to v1.1). Exploration shortcuts are populated by pre-computation during batched Phase-7 analysis; v1.0 ships on-demand `summary` only ([ADR-030](../adr/ADR-030-on-demand-summary-scope.md)) and has no batched pre-computation pass to feed the shortcut indices. -Clarion exposes pre-computed shortcuts operationalising common exploration queries: `find_entry_points`, `find_http_routes`, `find_cli_commands`, `find_data_models`, `find_config_loaders`, `find_tests`, `find_fixtures`, `find_deprecations`, `find_todos`, `find_dead_code`, `find_circular_imports`, `find_coupling_hotspots`, `recently_changed`, `high_churn`, `what_tests_this`. Each accepts an optional `scope` (entity ID or path glob). +Loomweave exposes pre-computed shortcuts operationalising common exploration queries: `find_entry_points`, `find_http_routes`, `find_cli_commands`, `find_data_models`, `find_config_loaders`, `find_tests`, `find_fixtures`, `find_deprecations`, `find_todos`, `find_dead_code`, `find_circular_imports`, `find_coupling_hotspots`, `recently_changed`, `high_churn`, `what_tests_this`. Each accepts an optional `scope` (entity ID or path glob). **Rationale**: Each of these is an "explore-agent spawn" an LLM would otherwise perform by walking the graph — and each can be pre-computed during batch analysis and cached. Principle 2 says those spawns are a failure mode; the shortcuts are the remedy. **Verification**: Call each shortcut on elspeth-slice fixture; assert responses populated and bounded. @@ -434,7 +434,7 @@ Clarion exposes pre-computed shortcuts operationalising common exploration queri MCP tool responses respect per-tool token budgets: `summary(short) ≤100`, `summary(medium) ≤400`, `summary(full) ≤1,500`, `neighbors / callers / callees / children ≤20 results × ≤50 tokens each`, `source` paginated above 2,000 tokens, `search_*` ≤10 results. Budgets are configurable per-session via `set_budget(tool, max_tokens)`. -**Rationale**: Unbounded responses re-introduce the context-pollution problem Clarion exists to solve. Explicit budgets keep parent context growth predictable and let agents reason about how much slack they have before compaction. +**Rationale**: Unbounded responses re-introduce the context-pollution problem Loomweave exists to solve. Explicit budgets keep parent context growth predictable and let agents reason about how much slack they have before compaction. **Verification**: Tool-by-tool integration test asserts responses stay within budget on representative inputs. **See**: System Design §8 (MCP Consult Surface, Token budgeting). @@ -452,7 +452,7 @@ Write-effect tools (`emit_observation`, `promote_observation`, `propose_guidance > **Deferred to v1.1** per the [Sprint 2 scope amendment §3 (Box B.6)](../../implementation/sprint-2/scope-amendment-2026-05.md). Session persistence is the cursor-based Navigation model deferred with [REQ-MCP-01](#req-mcp-01--cursor-based-session-model); v1.0 tools take explicit `id` arguments per-call and hold no per-session state beyond the request scope. -Sessions are created on MCP `initialize`, idle-timeout after 1 hour (configurable), and persist to `.clarion/sessions/.json` for reconnection. `clarion sessions list` and `clarion sessions close ` provide admin surfaces. +Sessions are created on MCP `initialize`, idle-timeout after 1 hour (configurable), and persist to `.loomweave/sessions/.json` for reconnection. `loomweave sessions list` and `loomweave sessions close ` provide admin surfaces. **Rationale**: Agents reconnecting after a transport interruption expect their cursor and breadcrumbs to survive; losing session state mid-investigation is hostile to the workflow. **Verification**: Open a session, populate cursor, disconnect, reconnect, assert state restored. @@ -462,13 +462,13 @@ Sessions are created on MCP `initialize`, idle-timeout after 1 hour (configurabl ### Catalog Artefacts (`REQ-ARTEFACT-*`) -Human-readable outputs from `clarion analyze`. +Human-readable outputs from `loomweave analyze`. #### REQ-ARTEFACT-01 — JSON catalog output > **Deferred to v1.1** per the [Sprint 2 scope amendment §3 (Box B.4 removed)](../../implementation/sprint-2/scope-amendment-2026-05.md). The `catalog.json` artefact has no consumer in the v1.0 MVP MCP surface; the MCP tools query the SQLite store directly. -`clarion analyze` emits `.clarion/catalog.json` — a deterministic, stable-shape dump of the entity catalog, edges, subsystems, and findings at run completion. +`loomweave analyze` emits `.loomweave/catalog.json` — a deterministic, stable-shape dump of the entity catalog, edges, subsystems, and findings at run completion. **Rationale**: JSON is the universal interchange format; downstream consumers (dashboards, bespoke scripts, CI gates) can read the catalog without speaking SQLite. Deterministic output means git diffs reflect real changes, not run-to-run noise. **Verification**: Two consecutive runs produce byte-identical `catalog.json`; schema conforms to a versioned JSON schema. @@ -478,9 +478,9 @@ Human-readable outputs from `clarion analyze`. > **Deferred to v1.1** per the [Sprint 2 scope amendment §3 (Box B.5 removed)](../../implementation/sprint-2/scope-amendment-2026-05.md). Subsystem rendering lands with WP4 in v1.1. -`clarion analyze` emits `.clarion/catalog/.md` (one markdown file per subsystem) plus `.clarion/catalog/index.md` (top-level navigation). Markdown is generated from the store, not authored. +`loomweave analyze` emits `.loomweave/catalog/.md` (one markdown file per subsystem) plus `.loomweave/catalog/index.md` (top-level navigation). Markdown is generated from the store, not authored. -**Rationale**: Markdown is the human-reading surface for cases where a human (reviewer, new team member) wants to read the catalog without running `clarion serve` or speaking MCP. Subsystem granularity matches how humans think about large codebases; the index makes discovery cheap. +**Rationale**: Markdown is the human-reading surface for cases where a human (reviewer, new team member) wants to read the catalog without running `loomweave serve` or speaking MCP. Subsystem granularity matches how humans think about large codebases; the index makes discovery cheap. **Verification**: For each subsystem entity, a corresponding markdown file exists and renders cleanly; index lists all subsystems. **See**: System Design §6 (Analysis Pipeline, Emission). @@ -488,21 +488,21 @@ Human-readable outputs from `clarion analyze`. ### Configuration / Policy Engine (`REQ-CONFIG-*`) -Reading and applying `clarion.yaml`, profiles, budgets, and caching policy. +Reading and applying `loomweave.yaml`, profiles, budgets, and caching policy. -#### REQ-CONFIG-01 — `clarion.yaml` as primary config +#### REQ-CONFIG-01 — `loomweave.yaml` as primary config -Clarion reads project configuration from `clarion.yaml` at the repository root, merged with user-level defaults (`~/.config/clarion/defaults.yaml`) and CLI flag overrides. The schema is versioned (`version: 1`) so breaking schema changes bump the version. +Loomweave reads project configuration from `loomweave.yaml` at the repository root, merged with user-level defaults (`~/.config/loomweave/defaults.yaml`) and CLI flag overrides. The schema is versioned (`version: 1`) so breaking schema changes bump the version. **Rationale**: Single-file project-level config matches the idioms of surrounding tooling (`pyproject.toml`, `wardline.yaml`, `.filigree/`) and keeps project policy visible in the repo. Three-tier merge order (user defaults → project → CLI) matches user expectations. -**Verification**: Fixture with clarion.yaml + user defaults + CLI override produces the expected merged config in `runs//stats.json`. +**Verification**: Fixture with loomweave.yaml + user defaults + CLI override produces the expected merged config in `runs//stats.json`. **See**: System Design §5 (Policy Engine, Config). #### REQ-CONFIG-02 — Profile presets (budget / default / deep / custom) > **Deferred to v1.1** per the [Sprint 2 scope amendment §4](../../implementation/sprint-2/scope-amendment-2026-05.md) (WP6 narrowed to on-demand `summary(id)` per [ADR-030](../adr/ADR-030-on-demand-summary-scope.md)). Profile presets sit on top of the LLM dispatch + per-level policy that v1.0 does not ship. -Clarion ships three named profiles (`budget`, `default`, `deep`) and supports `custom`. Each profile specifies per-level mode / model_tier / summary_length; `clarion.yaml:llm_policy.profile` picks one; `overrides` layer on top. +Loomweave ships three named profiles (`budget`, `default`, `deep`) and supports `custom`. Each profile specifies per-level mode / model_tier / summary_length; `loomweave.yaml:llm_policy.profile` picks one; `overrides` layer on top. **Rationale**: Named profiles make cost trade-offs legible. An operator saying "use `budget`" and getting 4× cost reduction with predictable depth loss is faster than tuning six per-level parameters; `custom` is the escape hatch for teams with specific needs. **Verification**: Each profile produces the expected per-level configuration after merge; CLI `--profile ` switches profiles without editing the file. @@ -512,7 +512,7 @@ Clarion ships three named profiles (`budget`, `default`, `deep`) and supports `c > **Deferred to v1.1** per the [Sprint 2 scope amendment §4](../../implementation/sprint-2/scope-amendment-2026-05.md) (WP6 narrowed; the Phase 0 dry-run / preflight estimator + run-level budget gate are part of the deferred batched pipeline). v1.0 reaches LLM cost only lazily via MCP `summary` per [ADR-030](../adr/ADR-030-on-demand-summary-scope.md); there is no batch run for a preflight to estimate. -`clarion analyze` computes a cost estimate from the dry-run and confirms with the user before dispatching (default `dry_run_first: true`). During the run, budget watchers enforce `max_usd_per_run` and `max_minutes`; exceeding either with `on_exceed: stop` halts dispatch and writes a partial manifest. +`loomweave analyze` computes a cost estimate from the dry-run and confirms with the user before dispatching (default `dry_run_first: true`). During the run, budget watchers enforce `max_usd_per_run` and `max_minutes`; exceeding either with `on_exceed: stop` halts dispatch and writes a partial manifest. **Rationale**: LLM cost surprise is a common failure mode for teams adopting LLM-assisted tooling. Preflight prevents "I just spent $400 to analyse my monorepo" incidents; in-flight enforcement bounds the worst case when estimates are wrong. **Verification**: Estimator within ±50% on elspeth; `on_exceed: stop` with a low budget halts and writes `runs//partial.json`. @@ -522,7 +522,7 @@ Clarion ships three named profiles (`budget`, `default`, `deep`) and supports `c > **Deferred to v1.1** per the [Sprint 2 scope amendment §4](../../implementation/sprint-2/scope-amendment-2026-05.md) (WP6 narrowed to on-demand `summary(id)` per [ADR-030](../adr/ADR-030-on-demand-summary-scope.md)). Per-level policy is meaningful only when batched module/subsystem-tier summarisation exists. -`clarion.yaml:llm_policy.levels.` specifies `mode` (`batch | on_demand | off`), `model_tier` (`haiku | sonnet | opus`), and `summary_length`. Overrides match on `path` / `subsystem` / other criteria and layer per-level. +`loomweave.yaml:llm_policy.levels.` specifies `mode` (`batch | on_demand | off`), `model_tier` (`haiku | sonnet | opus`), and `summary_length`. Overrides match on `path` / `subsystem` / other criteria and layer per-level. **Rationale**: Different entity levels (function vs subsystem) warrant different cost / depth trade-offs. Per-level policy lets operators spend Opus tokens where they matter (subsystem synthesis) and Haiku tokens where they don't (per-function leaf summaries), with path-based overrides for exceptions. **Verification**: Config with `tests/**` → function: off filters out test-function summaries; a subsystem-specific override applies Opus to that subsystem alone. @@ -568,7 +568,7 @@ Plugins implement two phases of lifecycle calls: batch (`initialize`, `analyze_f #### REQ-PLUGIN-04 — Python plugin (v1.0) -Clarion ships a Python plugin supporting Python >=3.11. The v1.0 plugin +Loomweave ships a Python plugin supporting Python >=3.11. The v1.0 plugin declares and emits the narrower ontology that is present in `plugins/python/plugin.toml`: `function`, `class`, and `module` entities, plus `contains`, `calls`, `references`, and `imports` edges. The plugin is not @@ -611,15 +611,15 @@ metadata, decorator arguments, or alias-resolved decorator facts. ### HTTP Read API (`REQ-HTTP-*`) -The read-only HTTP surface exposed by `clarion serve`. +The read-only HTTP surface exposed by `loomweave serve`. #### REQ-HTTP-01 — Read endpoints for entities, findings, wardline, state > **v1.0 ships the [ADR-014](../adr/ADR-014-filigree-registry-backend.md) file-registry subset only**: `GET /api/v1/files`, `POST /api/v1/files:resolve`, `POST /api/v1/files/batch`, `GET /api/v1/_capabilities` (plus the ADR-034 authentication surface — see [REQ-HTTP-03](#req-http-03--registry-backend-http-trust-model)). The broader `/entities*`, `/findings`, `/wardline/declared`, `/state`, `/health`, `/metrics` catalogue documented below is deferred to v1.1 per the [Sprint 2 scope amendment §4](../../implementation/sprint-2/scope-amendment-2026-05.md) (WP9-B for findings/wardline/entities; WP10 for `/state`, `/health`, `/metrics`). The endpoint list below documents the v1.1 target surface. -Clarion exposes read-only HTTP endpoints: `GET /api/v1/entities`, `GET /api/v1/entities/{id}`, `GET /api/v1/entities/{id}/neighbors`, `GET /api/v1/entities/{id}/summary`, `GET /api/v1/entities/{id}/guidance`, `GET /api/v1/entities/{id}/findings`, `GET /api/v1/findings`, `GET /api/v1/wardline/declared`, `GET /api/v1/state`, `GET /api/v1/health`, `GET /api/v1/metrics` (Prometheus-compatible). +Loomweave exposes read-only HTTP endpoints: `GET /api/v1/entities`, `GET /api/v1/entities/{id}`, `GET /api/v1/entities/{id}/neighbors`, `GET /api/v1/entities/{id}/summary`, `GET /api/v1/entities/{id}/guidance`, `GET /api/v1/entities/{id}/findings`, `GET /api/v1/findings`, `GET /api/v1/wardline/declared`, `GET /api/v1/state`, `GET /api/v1/health`, `GET /api/v1/metrics` (Prometheus-compatible). -**Rationale**: Sibling tools (Wardline in v0.2+, future dashboards, CI gates) consume Clarion's catalog via HTTP; MCP is not appropriate for cross-process state pulls. Read-only in v0.1 keeps the surface small. +**Rationale**: Sibling tools (Wardline in v0.2+, future dashboards, CI gates) consume Loomweave's catalog via HTTP; MCP is not appropriate for cross-process state pulls. Read-only in v0.1 keeps the surface small. **Verification**: Contract test per endpoint against a fixture catalog; `/metrics` scrapes cleanly; `/health` returns the expected envelope. **See**: System Design §9 (Integrations, HTTP Read API). @@ -627,22 +627,22 @@ Clarion exposes read-only HTTP endpoints: `GET /api/v1/entities`, `GET /api/v1/e > **Deferred to v1.1** per the [Sprint 2 scope amendment §4](../../implementation/sprint-2/scope-amendment-2026-05.md) (WP9-B Wardline state-file ingest + WP10 broader HTTP surface). The full multi-scheme oracle depends on Wardline state-file ingest landing for `wardline_qualname` / `wardline_exception_location` / `sarif_logical_location`; v1.0 resolves the `file_path` scheme only via [REQ-HTTP-01](#req-http-01--read-endpoints-for-entities-findings-wardline-state)'s `POST /api/v1/files:resolve` and `POST /api/v1/files/batch` per [ADR-014](../adr/ADR-014-filigree-registry-backend.md). -`GET /api/v1/entities/resolve?scheme=&value=` translates from sibling-tool identity schemes (`wardline_qualname`, `wardline_exception_location`, `file_path`, `sarif_logical_location`) to Clarion entity IDs. Returns `{entity_id, kind, resolution_confidence: exact|heuristic|none, alternatives}`. +`GET /api/v1/entities/resolve?scheme=&value=` translates from sibling-tool identity schemes (`wardline_qualname`, `wardline_exception_location`, `file_path`, `sarif_logical_location`) to Loomweave entity IDs. Returns `{entity_id, kind, resolution_confidence: exact|heuristic|none, alternatives}`. -**Rationale**: Enrichment-not-load-bearing (`CON-LOOM-01`): sibling tools consuming Clarion should ask in *their* native identity scheme, not embed Clarion's ID format. `resolve` exposes Clarion's internal translation layer as a public API so every sibling doesn't re-implement it. +**Rationale**: Enrichment-not-load-bearing (`CON-WEFT-01`): sibling tools consuming Loomweave should ask in *their* native identity scheme, not embed Loomweave's ID format. `resolve` exposes Loomweave's internal translation layer as a public API so every sibling doesn't re-implement it. **Verification**: Contract test for each scheme; `resolution_confidence: none` returned as 200 with empty `entity_id` (not 404) so callers can distinguish absent-from-catalog vs. server-down. **See**: System Design §9 (Integrations, Entity resolve). #### REQ-HTTP-03 — Registry-backend HTTP trust model -For Filigree `registry_backend: clarion`, Clarion's HTTP read API is +For Filigree `registry_backend: loomweave`, Loomweave's HTTP read API is loopback-only by default. Non-loopback binds require **both** `serve.http.allow_non_loopback: true` **and** a resolved authentication secret — either HMAC identity via `serve.http.identity_token_env` (preferred per ADR-034, hardened with timestamp/nonce replay protection by ADR-042) or a legacy bearer token via `serve.http.token_env`. A non-loopback bind with the opt-in but no resolved secret is refused at startup with -`CLA-CONFIG-HTTP-NO-AUTH`. The loopback-without-token mode remains +`LMWV-CONFIG-HTTP-NO-AUTH`. The loopback-without-token mode remains unauthenticated and emits a startup warning that any local process can read the catalogue; non-loopback no longer has an unauthenticated mode. @@ -652,7 +652,7 @@ unauthenticated non-loopback binds behind the `allow_non_loopback` opt-in alone. The implementation prevents accidental network exposure mechanically and makes intentional non-loopback exposure fail closed without an authentication secret rather than silently warn-and-bind. -**Verification**: `crates/clarion-cli/tests/serve.rs` covers the loopback +**Verification**: `crates/loomweave-cli/tests/serve.rs` covers the loopback default (line 1457), the loopback `identity_token_env`-resolution-failure refusal (line 1495), the non-loopback-without-auth startup refusal (line 1547), the non-loopback HMAC-required path (line 1579), and the non-loopback @@ -664,9 +664,9 @@ For the exact HMAC header and canonical-message shape, use #### REQ-HTTP-04 — ETag-style response caching -Every HTTP response carries `X-Clarion-State: ` for client-side caching. Clients can supply `If-None-Match` with the previously-received state hash; unchanged → 304. +Every HTTP response carries `X-Loomweave-State: ` for client-side caching. Clients can supply `If-None-Match` with the previously-received state hash; unchanged → 304. -**Rationale**: Wardline-style consumers poll at commit cadence; cheap cache revalidation reduces load on Clarion and network cost for the consumer. `X-Clarion-State` is a run-level hash (not per-entity) for simplicity; finer-grained caching is v0.2+. +**Rationale**: Wardline-style consumers poll at commit cadence; cheap cache revalidation reduces load on Loomweave and network cost for the consumer. `X-Loomweave-State` is a run-level hash (not per-entity) for simplicity; finer-grained caching is v0.2+. **Verification**: Contract test: second request with matching state hash returns 304 with no body. **See**: System Design §9 (Integrations, HTTP Read API). @@ -674,53 +674,53 @@ Every HTTP response carries `X-Clarion-State: ` for client-side caching. C ### Filigree Integration (`REQ-INTEG-FILIGREE-*`) -Clarion's side of Filigree integration. +Loomweave's side of Filigree integration. #### REQ-INTEG-FILIGREE-01 — Findings via scan-results intake > **Deferred to v1.1** per the [Sprint 2 scope amendment §4](../../implementation/sprint-2/scope-amendment-2026-05.md) (WP9-B). Lands with REQ-FINDING-03. -Clarion POSTs findings to Filigree's `POST /api/v1/scan-results` using the native schema (see `REQ-FINDING-03`) with `scan_source: "clarion"`. Clarion inspects `response.warnings[]` on every POST for silent coercion / unknown-key drops. +Loomweave POSTs findings to Filigree's `POST /api/v1/scan-results` using the native schema (see `REQ-FINDING-03`) with `scan_source: "loomweave"`. Loomweave inspects `response.warnings[]` on every POST for silent coercion / unknown-key drops. **Rationale**: Filigree's scan-results intake is the battle-tested cross-tool finding-exchange path; the warnings array is how Filigree signals schema drift. Ignoring warnings means silently shipping malformed findings. -**Verification**: Mock Filigree returns synthetic warnings; Clarion logs them at WARN and emits `CLA-INFRA-FILIGREE-WARNINGS`. +**Verification**: Mock Filigree returns synthetic warnings; Loomweave logs them at WARN and emits `LMWV-INFRA-FILIGREE-WARNINGS`. **See**: System Design §9 (Integrations, Filigree — Finding exchange). #### REQ-INTEG-FILIGREE-02 — Observation emission (HTTP preferred, MCP fallback) -> **Deferred to v1.1** per the [Sprint 2 scope amendment §4](../../implementation/sprint-2/scope-amendment-2026-05.md) (WP9-B). Clarion v0.1 surfaces structural and security findings via its own MCP `issues_for` tool (WP9-A); cross-product observation emission to Filigree lands with WP9-B. +> **Deferred to v1.1** per the [Sprint 2 scope amendment §4](../../implementation/sprint-2/scope-amendment-2026-05.md) (WP9-B). Loomweave v0.1 surfaces structural and security findings via its own MCP `issues_for` tool (WP9-A); cross-product observation emission to Filigree lands with WP9-B. -Clarion emits observations to Filigree via `POST /api/v1/observations` when available; falls back to MCP-client transport (spawning `filigree mcp` as subprocess) when the HTTP endpoint is absent. The fallback is signalled in the capability compat report. +Loomweave emits observations to Filigree via `POST /api/v1/observations` when available; falls back to MCP-client transport (spawning `filigree mcp` as subprocess) when the HTTP endpoint is absent. The fallback is signalled in the capability compat report. -**Rationale**: Observations are fire-and-forget notes; Clarion generates them during analyse and consult. HTTP is the natural transport for a Rust binary emitting to a local Filigree; MCP is the v0.1 workaround while Filigree adds the HTTP endpoint. +**Rationale**: Observations are fire-and-forget notes; Loomweave generates them during analyse and consult. HTTP is the natural transport for a Rust binary emitting to a local Filigree; MCP is the v0.1 workaround while Filigree adds the HTTP endpoint. **Verification**: HTTP path used by default; `--no-filigree-http` flag forces MCP fallback; both paths produce observations visible in Filigree. **See**: System Design §9 (Integrations, Filigree — Observations), §11 (Suite Bootstrap). #### REQ-INTEG-FILIGREE-03 — Registry-backend consumption -> **Deferred to v1.1** per the [Sprint 2 scope amendment §4](../../implementation/sprint-2/scope-amendment-2026-05.md) (WP9-B/WP10) and reflected in [CON-FILIGREE-02](#con-filigree-02--file-registry-displacement-is-deferred-to-v02). Filigree's `registry_backend` flag shipped (ADR-014); Clarion's read-side `RegistryProtocol` implementation is the v1.1 work. Clarion v0.1 ships shadow-registry only. +> **Deferred to v1.1** per the [Sprint 2 scope amendment §4](../../implementation/sprint-2/scope-amendment-2026-05.md) (WP9-B/WP10) and reflected in [CON-FILIGREE-02](#con-filigree-02--file-registry-displacement-is-deferred-to-v02). Filigree's `registry_backend` flag shipped (ADR-014); Loomweave's read-side `RegistryProtocol` implementation is the v1.1 work. Loomweave v0.1 ships shadow-registry only. -When Filigree's `registry_backend` flag is set to `clarion`, Clarion serves as Filigree's file registry: Filigree consults Clarion's HTTP read API for file ID resolution; auto-create paths route through `RegistryProtocol` to Clarion. Absent the flag, Clarion operates in shadow-registry mode (findings POSTed normally; Filigree auto-creates `file_records` under its native rules). +When Filigree's `registry_backend` flag is set to `loomweave`, Loomweave serves as Filigree's file registry: Filigree consults Loomweave's HTTP read API for file ID resolution; auto-create paths route through `RegistryProtocol` to Loomweave. Absent the flag, Loomweave operates in shadow-registry mode (findings POSTed normally; Filigree auto-creates `file_records` under its native rules). -**Rationale**: File-registry displacement is the cleanest expression of "Clarion owns structural truth" (per Loom federation). The shadow-registry fallback preserves v0.1 shipability when Filigree hasn't yet landed `registry_backend`. -**Verification**: Mock Filigree with flag set → Clarion serves `GET /api/v1/entities/resolve?scheme=file_path` in response to Filigree resolution calls; flag absent → shadow mode, compat report flags degradation. +**Rationale**: File-registry displacement is the cleanest expression of "Loomweave owns structural truth" (per Weft federation). The shadow-registry fallback preserves v0.1 shipability when Filigree hasn't yet landed `registry_backend`. +**Verification**: Mock Filigree with flag set → Loomweave serves `GET /api/v1/entities/resolve?scheme=file_path` in response to Filigree resolution calls; flag absent → shadow mode, compat report flags degradation. **See**: System Design §9 (Integrations, Filigree — Registry), §11 (Suite Bootstrap, Prerequisites named here). #### REQ-INTEG-FILIGREE-04 — `scan_source` namespace + schema pin test -> **Deferred to v1.1** per the [Sprint 2 scope amendment §4](../../implementation/sprint-2/scope-amendment-2026-05.md) (WP9-B). Lands with REQ-FINDING-03 (findings emission to Filigree); schema-pin test is moot until Clarion is actually POSTing. +> **Deferred to v1.1** per the [Sprint 2 scope amendment §4](../../implementation/sprint-2/scope-amendment-2026-05.md) (WP9-B). Lands with REQ-FINDING-03 (findings emission to Filigree); schema-pin test is moot until Loomweave is actually POSTing. -Clarion uses `scan_source: "clarion"` for emissions; CI runs a schema-compatibility test against a Filigree release's `GET /api/files/_schema` output to detect drift in `valid_severities`, `valid_finding_statuses`, `valid_association_types`. +Loomweave uses `scan_source: "loomweave"` for emissions; CI runs a schema-compatibility test against a Filigree release's `GET /api/files/_schema` output to detect drift in `valid_severities`, `valid_finding_statuses`, `valid_association_types`. -**Rationale**: Filigree's CHANGELOG flags breaking API changes but relies on social discipline; a pinned schema-compat test gives Clarion CI-level protection against silent schema shifts. +**Rationale**: Filigree's CHANGELOG flags breaking API changes but relies on social discipline; a pinned schema-compat test gives Loomweave CI-level protection against silent schema shifts. **Verification**: CI job against a tagged Filigree release passes; modifying the fixture schema fails the test. **See**: System Design §9 (Integrations, Filigree — Schema contract). #### REQ-INTEG-FILIGREE-05 — Capability-negotiation probe -> **Deferred to v1.1** per the [Sprint 2 scope amendment §4](../../implementation/sprint-2/scope-amendment-2026-05.md) (WP9-B). The capability probe is meaningful only once Clarion is talking to Filigree via the v1.1 cross-product surfaces (REQ-FINDING-03, REQ-INTEG-FILIGREE-02/03). +> **Deferred to v1.1** per the [Sprint 2 scope amendment §4](../../implementation/sprint-2/scope-amendment-2026-05.md) (WP9-B). The capability probe is meaningful only once Loomweave is talking to Filigree via the v1.1 cross-product surfaces (REQ-FINDING-03, REQ-INTEG-FILIGREE-02/03). -At `clarion analyze` startup, Clarion probes Filigree's presence, version, `registry_backend` setting, and `/api/v1/observations` availability via `GET /api/files/_schema` + `HEAD` checks. Results emit in a single `CLA-INFRA-SUITE-COMPAT-REPORT` finding. +At `loomweave analyze` startup, Loomweave probes Filigree's presence, version, `registry_backend` setting, and `/api/v1/observations` availability via `GET /api/files/_schema` + `HEAD` checks. Results emit in a single `LMWV-INFRA-SUITE-COMPAT-REPORT` finding. **Rationale**: Partial Filigree versions are common (deployment skew). A single compat report collapses scattered runtime surprises into one auditable signal operators can read at the start of each run. **Verification**: Mock Filigree with varied capability responses; compat report correctly reflects each. @@ -730,11 +730,11 @@ At `clarion analyze` startup, Clarion probes Filigree's presence, version, `regi ### Wardline Integration (`REQ-INTEG-WARDLINE-*`) -Clarion's side of Wardline integration (v0.1 is read-only ingest). +Loomweave's side of Wardline integration (v0.1 is read-only ingest). #### REQ-INTEG-WARDLINE-01 — Direct REGISTRY import with version pin -Clarion's Python plugin imports `wardline.core.registry.REGISTRY` at startup and pins against an expected `REGISTRY_VERSION`. Additive-newer versions proceed with a warning; major-bump or older falls back to a hardcoded registry mirror (`wardline_registry_v.py`) with `CLA-INFRA-WARDLINE-REGISTRY-MIRRORED`. +Loomweave's Python plugin imports `wardline.core.registry.REGISTRY` at startup and pins against an expected `REGISTRY_VERSION`. Additive-newer versions proceed with a warning; major-bump or older falls back to a hardcoded registry mirror (`wardline_registry_v.py`) with `LMWV-INFRA-WARDLINE-REGISTRY-MIRRORED`. **Rationale**: Direct import is cheaper than file-descriptor reading and avoids a vocabulary-drift window where two tools have different understandings of decorator semantics. Version pinning plus mirror fallback preserves operation when skew occurs. **Verification**: Matching version → normal operation; additive skew → warning; major skew → mirror mode. @@ -744,7 +744,7 @@ Clarion's Python plugin imports `wardline.core.registry.REGISTRY` at startup and > **Deferred to v1.1** per the [Sprint 2 scope amendment §4](../../implementation/sprint-2/scope-amendment-2026-05.md) (WP9-B Wardline-config ingest). v1.0 ships only the [REQ-INTEG-WARDLINE-01](#req-integ-wardline-01--direct-registry-import-with-version-pin) runtime REGISTRY probe; state-file ingest (`wardline.yaml` + overlays) lands with the Wardline read-side bundle in v1.1. -Clarion reads `wardline.yaml` and overlay files matching `src/**/wardline.overlay.yaml` at analyse time; declared tiers, groups, and boundary contracts become `WardlineMeta` properties on affected entities. +Loomweave reads `wardline.yaml` and overlay files matching `src/**/wardline.overlay.yaml` at analyse time; declared tiers, groups, and boundary contracts become `WardlineMeta` properties on affected entities. **Rationale**: The manifest is Wardline's declarative source of truth; ingesting it makes tier/group/contract declarations available as entity metadata without re-implementing Wardline's parsing. Overlays let per-subsystem declarations compose cleanly. **Verification**: Fixture with manifest + overlays produces entities with correct `declared_tier`, `declared_groups`, `declared_boundary_contracts`. @@ -754,9 +754,9 @@ Clarion reads `wardline.yaml` and overlay files matching `src/**/wardline.overla > **Deferred to v1.1** per the [Sprint 2 scope amendment §4](../../implementation/sprint-2/scope-amendment-2026-05.md) (WP9-B Wardline-config ingest). Fingerprint ingest depends on the same state-file reader path deferred for [REQ-INTEG-WARDLINE-02](#req-integ-wardline-02--manifest--overlay-ingest); v1.0 has no `WardlineMeta.annotation_hash` population path. -Clarion reads `wardline.fingerprint.json` at analyse time; each per-function `FingerprintEntry` becomes `WardlineMeta.annotation_hash` + `wardline_qualname` on the resolved entity. Unresolved fingerprint entries emit `CLA-INFRA-WARDLINE-FINGERPRINT-UNRESOLVED`. +Loomweave reads `wardline.fingerprint.json` at analyse time; each per-function `FingerprintEntry` becomes `WardlineMeta.annotation_hash` + `wardline_qualname` on the resolved entity. Unresolved fingerprint entries emit `LMWV-INFRA-WARDLINE-FINGERPRINT-UNRESOLVED`. -**Rationale**: Fingerprint provides the authoritative per-function annotation hash — without it, Clarion can't track drift between what Wardline enforces and what the code declares. Resolution failures must be visible (not silent) so operators can fix the mapping. +**Rationale**: Fingerprint provides the authoritative per-function annotation hash — without it, Loomweave can't track drift between what Wardline enforces and what the code declares. Resolution failures must be visible (not silent) so operators can fix the mapping. **Verification**: Fingerprint entries for known entities populate `annotation_hash`; deliberate mis-resolution emits the finding. **See**: System Design §9 (Integrations, Wardline — Fingerprint). @@ -764,7 +764,7 @@ Clarion reads `wardline.fingerprint.json` at analyse time; each per-function `Fi > **Deferred to v1.1** per the [Sprint 2 scope amendment §4](../../implementation/sprint-2/scope-amendment-2026-05.md) (WP9-B Wardline-config ingest). Exceptions ingest depends on the same state-file reader path deferred for [REQ-INTEG-WARDLINE-02](#req-integ-wardline-02--manifest--overlay-ingest); v1.0 carries no `wardline.excepted` tag on briefings. -Clarion reads `wardline.exceptions.json` at analyse time; entities referenced by active exceptions are tagged `wardline.excepted`. Unresolvable exception `location` strings emit `CLA-INFRA-WARDLINE-EXCEPTION-UNRESOLVED` and persist as dangling records with `entity_id: null`. +Loomweave reads `wardline.exceptions.json` at analyse time; entities referenced by active exceptions are tagged `wardline.excepted`. Unresolvable exception `location` strings emit `LMWV-INFRA-WARDLINE-EXCEPTION-UNRESOLVED` and persist as dangling records with `entity_id: null`. **Rationale**: Exceptions are operator-curated decisions ("this finding is accepted"). Agents reading briefings for excepted entities should see "this has an active exception" as part of the picture; unresolvable exceptions are operator bugs that need visibility. **Verification**: Exception entries tag resolved entities with `wardline.excepted`; unresolvable entries produce the finding. @@ -772,22 +772,22 @@ Clarion reads `wardline.exceptions.json` at analyse time; entities referenced by #### REQ-INTEG-WARDLINE-05 — SARIF baseline ingest for translator -> **Deferred to v1.1** per the [Sprint 2 scope amendment §4](../../implementation/sprint-2/scope-amendment-2026-05.md) (WP10 SARIF translator). The translator surface (`clarion sarif import`) itself is deferred — see [REQ-INTEG-FILIGREE-04](#req-integ-filigree-04--sarif-import-translator) — so the baseline-ingest path it feeds is moot until WP10 lands. +> **Deferred to v1.1** per the [Sprint 2 scope amendment §4](../../implementation/sprint-2/scope-amendment-2026-05.md) (WP10 SARIF translator). The translator surface (`loomweave sarif import`) itself is deferred — see [REQ-INTEG-FILIGREE-04](#req-integ-filigree-04--sarif-import-translator) — so the baseline-ingest path it feeds is moot until WP10 lands. -Clarion reads `wardline.sarif.baseline.json` (read-only) for the `clarion sarif import` translator path — the 663-result baseline is the source for Wardline-to-Filigree finding flow in v0.1. +Loomweave reads `wardline.sarif.baseline.json` (read-only) for the `loomweave sarif import` translator path — the 663-result baseline is the source for Wardline-to-Filigree finding flow in v0.1. -**Rationale**: Translator ownership lives Clarion-side in v0.1 (ADR-015); reading the baseline from disk keeps Wardline's dependency graph unchanged until it ships a native Filigree emitter in v0.2+. -**Verification**: `clarion sarif import wardline.sarif.baseline.json --scan-source wardline` produces expected Filigree POST payload. +**Rationale**: Translator ownership lives Loomweave-side in v0.1 (ADR-015); reading the baseline from disk keeps Wardline's dependency graph unchanged until it ships a native Filigree emitter in v0.2+. +**Verification**: `loomweave sarif import wardline.sarif.baseline.json --scan-source wardline` produces expected Filigree POST payload. **See**: System Design §9 (Integrations, SARIF translator). #### REQ-INTEG-WARDLINE-06 — Identity reconciliation across three schemes > **Deferred to v1.1** per the [Sprint 2 scope amendment §4](../../implementation/sprint-2/scope-amendment-2026-05.md) (WP9-B Wardline-config ingest). The three-scheme oracle depends on fingerprint + exceptions ingest ([REQ-INTEG-WARDLINE-03](#req-integ-wardline-03--fingerprint-ingest) / [-04](#req-integ-wardline-04--exceptions-ingest)) populating `wardline_qualname` and `wardline_exception_location`; v1.0 resolves the `file_path` scheme only via [REQ-HTTP-01](#req-http-01--read-endpoints-for-entities-findings-wardline-state)'s `POST /api/v1/files:resolve` and `POST /api/v1/files/batch` per [ADR-014](../adr/ADR-014-filigree-registry-backend.md). -Clarion maintains translation between three identity schemes: Clarion `EntityId`, Wardline `qualname`, Wardline exception-register `location` string. Reconciliation uses Wardline's `module_file_map` (from `ScanContext`) plus parsed location strings. +Loomweave maintains translation between three identity schemes: Loomweave `EntityId`, Wardline `qualname`, Wardline exception-register `location` string. Reconciliation uses Wardline's `module_file_map` (from `ScanContext`) plus parsed location strings. -**Rationale**: The three schemes arose independently and are not byte-equal for the same symbol. Clarion is the translator (Principle 3 — Wardline keeps its scheme; Clarion produces the join); exposing the translation via `GET /api/v1/entities/resolve` (`REQ-HTTP-02`) lets sibling tools consume it without embedding Clarion's ID format. -**Verification**: Given a Wardline qualname + file, `resolve` returns the correct Clarion entity ID; given an exception location string, same. +**Rationale**: The three schemes arose independently and are not byte-equal for the same symbol. Loomweave is the translator (Principle 3 — Wardline keeps its scheme; Loomweave produces the join); exposing the translation via `GET /api/v1/entities/resolve` (`REQ-HTTP-02`) lets sibling tools consume it without embedding Loomweave's ID format. +**Verification**: Given a Wardline qualname + file, `resolve` returns the correct Loomweave entity ID; given an exception location string, same. **See**: System Design §3 (Data Model, Identity reconciliation). --- @@ -798,9 +798,9 @@ Clarion maintains translation between three identity schemes: Clarion `EntityId` #### NFR-PERF-01 — Elspeth-scale wall-clock budget -> **Deferred to v1.1** per [ADR-030](../adr/ADR-030-on-demand-summary-scope.md) + [Sprint 2 scope amendment §4](../../implementation/sprint-2/scope-amendment-2026-05.md). The 60-min target includes Phase 4–6 LLM summarisation wall-clock, which is the deferred batched pipeline; v1.0 `clarion analyze` ends at Phase 3 and is bounded by Python plugin extraction + Leiden clustering only. Remains the v1.1 acceptance criterion when phases 4–6 land. +> **Deferred to v1.1** per [ADR-030](../adr/ADR-030-on-demand-summary-scope.md) + [Sprint 2 scope amendment §4](../../implementation/sprint-2/scope-amendment-2026-05.md). The 60-min target includes Phase 4–6 LLM summarisation wall-clock, which is the deferred batched pipeline; v1.0 `loomweave analyze` ends at Phase 3 and is bounded by Python plugin extraction + Leiden clustering only. Remains the v1.1 acceptance criterion when phases 4–6 land. -`clarion analyze /home/john/elspeth` completes in ≤60 minutes (target ~38 minutes) on a representative developer machine (8+ cores, SSD, ≥16GB RAM). +`loomweave analyze /home/john/elspeth` completes in ≤60 minutes (target ~38 minutes) on a representative developer machine (8+ cores, SSD, ≥16GB RAM). **Rationale**: 60 minutes is the psychological ceiling for a "run overnight or during lunch" batch tool; significantly above it pushes teams to run less often and lose feedback. The ~38-minute target is derived from the detailed-design's example run; the ceiling bounds estimation error. **Verification**: Full elspeth run measured end-to-end; time logged in `runs//stats.json`. @@ -808,17 +808,17 @@ Clarion maintains translation between three identity schemes: Clarion `EntityId` #### NFR-PERF-02 — MCP response latency -`clarion serve`'s MCP initialize completes in ≤100ms. Cached MCP tool calls (hot entities) return in ≤50ms p95. Cache-miss `summary()` calls bounded by LLM latency but must not block other concurrent MCP tool calls. +`loomweave serve`'s MCP initialize completes in ≤100ms. Cached MCP tool calls (hot entities) return in ≤50ms p95. Cache-miss `summary()` calls bounded by LLM latency but must not block other concurrent MCP tool calls. -**Rationale**: Interactive consult-mode latency shapes agent-workflow UX; 100ms initialise keeps session spin-up invisible. Cache-hit latency determines whether Clarion feels like "instant lookup" or "async service"; agents giving up on slow tools is a failure mode. +**Rationale**: Interactive consult-mode latency shapes agent-workflow UX; 100ms initialise keeps session spin-up invisible. Cache-hit latency determines whether Loomweave feels like "instant lookup" or "async service"; agents giving up on slow tools is a failure mode. **Verification**: Microbenchmark harness against `tests/fixtures/moderate/`; p95 latencies logged. **See**: System Design §8 (MCP Consult Surface, Token budgeting — implementation). #### NFR-PERF-03 — Parent context growth per MCP call -A Claude Code consult session navigating 20+ turns grows parent context by ≤500 tokens per Clarion tool call on average (summary short + neighbours short composition). +A Claude Code consult session navigating 20+ turns grows parent context by ≤500 tokens per Loomweave tool call on average (summary short + neighbours short composition). -**Rationale**: Principle 2 (exploration elimination) fails if Clarion's responses pollute the parent as much as explore-subagents do. Bounded growth per call keeps long consult sessions viable within a single conversation window. +**Rationale**: Principle 2 (exploration elimination) fails if Loomweave's responses pollute the parent as much as explore-subagents do. Bounded growth per call keeps long consult sessions viable within a single conversation window. **Verification**: Integration test with a recorded 20-turn consult session; average growth per call measured. **See**: System Design §8 (MCP Consult Surface). @@ -828,15 +828,15 @@ A Claude Code consult session navigating 20+ turns grows parent context by ≤50 #### NFR-SCALE-01 — Elspeth validation (~425k LOC Python) -Clarion v0.1 is validated against `elspeth` (~425k LOC Python, ~1,100 files). The system handles that scale with the entity count (~100-200k entities, ~500k-1M edges) expected from that input. +Loomweave v0.1 is validated against `elspeth` (~425k LOC Python, ~1,100 files). The system handles that scale with the entity count (~100-200k entities, ~500k-1M edges) expected from that input. -**Rationale**: Elspeth is the validating first customer — the scale wall where the Claude Code archaeologist skill broke. Clarion's purpose is crossing that wall; meeting elspeth is the minimum viable product proof. +**Rationale**: Elspeth is the validating first customer — the scale wall where the Claude Code archaeologist skill broke. Loomweave's purpose is crossing that wall; meeting elspeth is the minimum viable product proof. **Verification**: Full elspeth run produces expected entity / edge counts within ±20%; no OOM on a 16GB machine. **See**: System Design §4 (Storage, Scale estimate). #### NFR-SCALE-02 — DB size bound -The `.clarion/clarion.db` store for an elspeth-scale project fits within 2GB. Larger projects degrade gracefully — no hard cap, but cost of commit-the-DB grows. +The `.loomweave/loomweave.db` store for an elspeth-scale project fits within 2GB. Larger projects degrade gracefully — no hard cap, but cost of commit-the-DB grows. **Rationale**: Committed DBs live in git; a 2GB DB is uncomfortable but workable, 10GB is pathological. Matching elspeth to 500MB-2GB keeps the commit-DB story honest. **Verification**: Elspeth run produces a DB within the bound; DB growth linear with entity count. @@ -856,29 +856,29 @@ The `.clarion/clarion.db` store for an elspeth-scale project fits within 2GB. La #### NFR-SEC-01 — Pre-ingest secret scanning -Before any file content reaches the LLM provider, Clarion runs a pre-ingest secret scanner (bundled `detect-secrets` or equivalent) on the file buffer. Unredacted secrets emit `CLA-SEC-SECRET-DETECTED` and **block LLM dispatch for that file**. False-positive whitelist at `.clarion/secrets-baseline.yaml`. +Before any file content reaches the LLM provider, Loomweave runs a pre-ingest secret scanner (bundled `detect-secrets` or equivalent) on the file buffer. Unredacted secrets emit `LMWV-SEC-SECRET-DETECTED` and **block LLM dispatch for that file**. False-positive whitelist at `.loomweave/secrets-baseline.yaml`. -**Rationale**: The first real user running `clarion analyze` on a repo with a committed `.env` would otherwise silently leak to Anthropic. Pre-ingest redaction is a hard dependency for v0.1; retrofitting it after a leak is too late. +**Rationale**: The first real user running `loomweave analyze` on a repo with a committed `.env` would otherwise silently leak to Anthropic. Pre-ingest redaction is a hard dependency for v0.1; retrofitting it after a leak is too late. **Verification**: Fixture with a deliberately-committed test secret blocks LLM dispatch; baseline whitelist suppresses the block for approved false positives. **See**: System Design §10 (Security, Pre-ingest redaction). #### NFR-SEC-02 — Prompt-injection containment -Clarion structures prompts with explicit `...` delimiters; briefing outputs are validated against the `EntityBriefing` JSON schema; `patterns` / `antipatterns` are controlled vocabulary; `propose_guidance` creates observations (not sheets) requiring manual promotion; `knowledge_basis: static_only` flags briefings derived solely from LLM output. +Loomweave structures prompts with explicit `...` delimiters; briefing outputs are validated against the `EntityBriefing` JSON schema; `patterns` / `antipatterns` are controlled vocabulary; `propose_guidance` creates observations (not sheets) requiring manual promotion; `knowledge_basis: static_only` flags briefings derived solely from LLM output. **Rationale**: Adversarial docstrings / comments / string literals can attempt to inject instructions into the summarisation prompt or propose attacker guidance that lands in every future prompt (cache poisoning). Layered defence is required because no single mechanism is sufficient. -**Verification**: Fixture with adversarial docstring; briefing schema-validates; `propose_guidance` call produces an observation, not a sheet; novel vocabulary surfaces as `CLA-FACT-VOCABULARY-CANDIDATE`. +**Verification**: Fixture with adversarial docstring; briefing schema-validates; `propose_guidance` call produces an observation, not a sheet; novel vocabulary surfaces as `LMWV-FACT-VOCABULARY-CANDIDATE`. **See**: System Design §10 (Security, Prompt-injection). #### NFR-SEC-03 — Non-loopback HTTP read API guard -Clarion refuses to start the HTTP read API on a non-loopback bind unless **both** +Loomweave refuses to start the HTTP read API on a non-loopback bind unless **both** `serve.http.allow_non_loopback: true` is set **and** an authentication secret is resolved at startup — either an HMAC identity secret via `serve.http.identity_token_env` (preferred, per ADR-034) or a legacy bearer token via `serve.http.token_env`. A non-loopback bind with neither secret -resolved fails closed at startup with `CLA-CONFIG-HTTP-NO-AUTH`. The -startup-warning surface (endpoint unauthenticated; protect outside Clarion) +resolved fails closed at startup with `LMWV-CONFIG-HTTP-NO-AUTH`. The +startup-warning surface (endpoint unauthenticated; protect outside Loomweave) applies only to the loopback-without-token mode, which remains the ADR-014 default for the local sidecar case. @@ -888,7 +888,7 @@ the registry-backend surface. ADR-034 closes the original ADR-014 gap that permitted unauthenticated non-loopback binds behind the `allow_non_loopback` opt-in alone; the opt-in remains the gate that admits non-loopback binds at all but no longer admits them unauthenticated. -**Verification**: `crates/clarion-cli/tests/serve.rs` covers the +**Verification**: `crates/loomweave-cli/tests/serve.rs` covers the non-loopback-bind-without-opt-in refusal (`serve_rejects_non_loopback_http_bind_before_binding_without_opt_in`), the non-loopback-without-auth refusal @@ -908,18 +908,18 @@ loopback startup-warning surface. #### NFR-SEC-04 — Audit surface — security events as findings -Every security-relevant event (`CLA-SEC-SECRET-DETECTED`, `CLA-SEC-UNREDACTED-SECRETS-ALLOWED`, `CLA-INFRA-TOKEN-STORAGE-DEGRADED`, `CLA-INFRA-BRIEFING-INVALID`, `CLA-SEC-VOCABULARY-CANDIDATE-NOVEL`) emits a finding that reaches Filigree via the normal exchange. +Every security-relevant event (`LMWV-SEC-SECRET-DETECTED`, `LMWV-SEC-UNREDACTED-SECRETS-ALLOWED`, `LMWV-INFRA-TOKEN-STORAGE-DEGRADED`, `LMWV-INFRA-BRIEFING-INVALID`, `LMWV-SEC-VOCABULARY-CANDIDATE-NOVEL`) emits a finding that reaches Filigree via the normal exchange. -**Rationale**: Security observability is part of the normal finding flow — no separate audit subsystem. Operators running `filigree list --label=security --since 7d` across the suite see Clarion's events alongside Wardline's and any future scanners'. +**Rationale**: Security observability is part of the normal finding flow — no separate audit subsystem. Operators running `filigree list --label=security --since 7d` across the suite see Loomweave's events alongside Wardline's and any future scanners'. **Verification**: Each event type produces a finding in Filigree when triggered. **See**: System Design §10 (Security, Audit Surface). #### NFR-SEC-05 — Run log exclusion from git by default -`runs//log.jsonl` (raw LLM request/response bodies) is git-excluded by default via `.clarion/.gitignore` (`runs/*/log.jsonl`). Operators opt-in to committing explicitly. +`runs//log.jsonl` (raw LLM request/response bodies) is git-excluded by default via `.loomweave/.gitignore` (`runs/*/log.jsonl`). Operators opt-in to committing explicitly. **Rationale**: Run logs may contain source excerpts appropriate to ship to Anthropic but not appropriate to commit to a public repo. Default-exclude prevents accidental exposure; explicit opt-in forces the operator to own the choice. -**Verification**: Fresh install produces `.clarion/.gitignore` with the rule; log files not tracked in the next `git status`. +**Verification**: Fresh install produces `.loomweave/.gitignore` with the rule; log files not tracked in the next `git status`. **See**: System Design §4 (Storage, Commit posture), §10 (Security, Operator guidance). --- @@ -928,7 +928,7 @@ Every security-relevant event (`CLA-SEC-SECRET-DETECTED`, `CLA-SEC-UNREDACTED-SE #### NFR-OPS-01 — Single-binary distribution -Clarion core distributes as a single native binary per target (Linux x86_64, Linux ARM64, macOS x86_64, macOS ARM64, Windows x86_64). No dynamic linking beyond libc; no required runtime dependencies. +Loomweave core distributes as a single native binary per target (Linux x86_64, Linux ARM64, macOS x86_64, macOS ARM64, Windows x86_64). No dynamic linking beyond libc; no required runtime dependencies. **Rationale**: Principle 1 (enterprise at lack of scale). Small teams don't have platform engineers; "download and run" is the deployment target. Dynamic dependencies re-introduce the platform-team problem. **Verification**: Binary on each target runs on a fresh install without installing additional dependencies; startup succeeds. @@ -936,25 +936,25 @@ Clarion core distributes as a single native binary per target (Linux x86_64, Lin #### NFR-OPS-02 — Local-first; no cloud dependency -Clarion runs entirely locally. The only required network egress is the LLM provider API during `clarion analyze` summarisation phases. No telemetry, no crash-reporting phone-home, no license-server callback. +Loomweave runs entirely locally. The only required network egress is the LLM provider API during `loomweave analyze` summarisation phases. No telemetry, no crash-reporting phone-home, no license-server callback. -**Rationale**: Enterprise-at-lack-of-scale means not forcing a hosted service on users. Adopting Clarion must not require signing a cloud agreement. -**Verification**: Network egress audit during `clarion analyze`: only Anthropic endpoints in the packet capture. +**Rationale**: Enterprise-at-lack-of-scale means not forcing a hosted service on users. Adopting Loomweave must not require signing a cloud agreement. +**Verification**: Network egress audit during `loomweave analyze`: only Anthropic endpoints in the packet capture. **See**: System Design §1 (Context & Boundaries). -#### NFR-OPS-03 — `.clarion/` git-committable +#### NFR-OPS-03 — `.loomweave/` git-committable -The `.clarion/` directory (including `clarion.db` by default) is safe to commit to git. Textual DB export (`clarion db export --textual`) and a merge helper (`clarion db merge-helper`) handle multi-developer conflicts. +The `.loomweave/` directory (including `loomweave.db` by default) is safe to commit to git. Textual DB export (`loomweave db export --textual`) and a merge helper (`loomweave db merge-helper`) handle multi-developer conflicts. **Rationale**: Shared analysis state benefits small teams (one developer pays the LLM cost; the team sees the briefings). Commit-by-default matches Filigree's and Wardline's storage patterns. Textual export makes git diffs meaningful. -**Verification**: `git add .clarion && git commit` succeeds on a populated store; two developers' simultaneous runs produce a DB that the merge helper resolves deterministically. +**Verification**: `git add .loomweave && git commit` succeeds on a populated store; two developers' simultaneous runs produce a DB that the merge helper resolves deterministically. **See**: System Design §4 (Storage, File layout). #### NFR-OPS-04 — Python plugin install via pipx -The Python plugin installs via `pipx install clarion-plugin-python` into its own venv. Clarion's `plugins.toml` records the plugin's executable path and Python version. +The Python plugin installs via `pipx install loomweave-plugin-python` into its own venv. Loomweave's `plugins.toml` records the plugin's executable path and Python version. -**Rationale**: Installing the plugin into the analysed project's venv causes dependency conflicts (Clarion's plugin dependencies can collide with the project's). pipx isolation sidesteps this at the cost of an extra install step — acceptable tradeoff. +**Rationale**: Installing the plugin into the analysed project's venv causes dependency conflicts (Loomweave's plugin dependencies can collide with the project's). pipx isolation sidesteps this at the cost of an extra install step — acceptable tradeoff. **Verification**: `pipx install` succeeds against a fresh Python 3.11 environment; plugin loads and emits entities for the tiny fixture. **See**: System Design §2 (Python plugin specifics, Packaging). @@ -964,15 +964,15 @@ The Python plugin installs via `pipx install clarion-plugin-python` into its own #### NFR-OBSERV-01 — Structured JSON logs -Clarion emits structured JSON-line logs via the `tracing` crate. Logs rotate at 100MB with 5 files kept. Per-run log at `.clarion/runs//log.jsonl`; per-process log at `.clarion/clarion.log`. +Loomweave emits structured JSON-line logs via the `tracing` crate. Logs rotate at 100MB with 5 files kept. Per-run log at `.loomweave/runs//log.jsonl`; per-process log at `.loomweave/loomweave.log`. -**Rationale**: Structured logs are machine-parseable; text logs aren't. Downstream log aggregation (if operators route Clarion's output into Vector / Loki / Splunk) works by default. +**Rationale**: Structured logs are machine-parseable; text logs aren't. Downstream log aggregation (if operators route Loomweave's output into Vector / Loki / Splunk) works by default. **Verification**: Log entries parse as JSON; rotation verified; log levels respected. **See**: System Design §5 (Policy Engine, Observability). #### NFR-OBSERV-02 — Per-run `stats.json` -Each `clarion analyze` run writes `runs//stats.json` with total cost, per-level LLM cost breakdown, per-model breakdown, cache hit rate, phase durations, finding counts, failure counts, and compat-probe result. +Each `loomweave analyze` run writes `runs//stats.json` with total cost, per-level LLM cost breakdown, per-model breakdown, cache hit rate, phase durations, finding counts, failure counts, and compat-probe result. **Rationale**: Post-run introspection without grepping logs. Cost, cache hit rate, and failure counts are the first three things an operator asks about after a run. **Verification**: `stats.json` schema validates; values match what logs record. @@ -980,15 +980,15 @@ Each `clarion analyze` run writes `runs//stats.json` with total cost, pe #### NFR-OBSERV-03 — Prometheus-compatible `/api/v1/metrics` -`clarion serve` exposes `/api/v1/metrics` in Prometheus text format, covering MCP request counts, HTTP request counts by endpoint and status, cache hit/miss counts, session counts, active LLM call count. +`loomweave serve` exposes `/api/v1/metrics` in Prometheus text format, covering MCP request counts, HTTP request counts by endpoint and status, cache hit/miss counts, session counts, active LLM call count. -**Rationale**: Operators running Clarion as a long-lived service need metrics; Prometheus is the ubiquitous standard. Exposing without a separate exporter means small teams can point Prometheus at Clarion directly. +**Rationale**: Operators running Loomweave as a long-lived service need metrics; Prometheus is the ubiquitous standard. Exposing without a separate exporter means small teams can point Prometheus at Loomweave directly. **Verification**: `curl /api/v1/metrics` returns valid Prometheus text; key metrics present after a load-test. **See**: System Design §9 (HTTP Read API). -#### NFR-OBSERV-04 — `CLA-INFRA-SUITE-COMPAT-REPORT` at every analyse +#### NFR-OBSERV-04 — `LMWV-INFRA-SUITE-COMPAT-REPORT` at every analyse -Every `clarion analyze` emits exactly one `CLA-INFRA-SUITE-COMPAT-REPORT` finding summarising the capability-probe results (Filigree presence, version, flags; Wardline REGISTRY version; SARIF schema version; degraded paths active). +Every `loomweave analyze` emits exactly one `LMWV-INFRA-SUITE-COMPAT-REPORT` finding summarising the capability-probe results (Filigree presence, version, flags; Wardline REGISTRY version; SARIF schema version; degraded paths active). **Rationale**: One finding collapses scattered runtime signals. Operators asking "why did this run behave differently from last week?" check the compat report first. **Verification**: Every run produces exactly one such finding in the run's finding set. @@ -1002,7 +1002,7 @@ Every `clarion analyze` emits exactly one `CLA-INFRA-SUITE-COMPAT-REPORT` findin > **Deferred to v1.1** per [ADR-030](../adr/ADR-030-on-demand-summary-scope.md) + [Sprint 2 scope amendment §4](../../implementation/sprint-2/scope-amendment-2026-05.md). The $15 ± 50% target measures a full batched run including phases 4–6 LLM spend; v1.0 reaches LLM cost only lazily through MCP `summary`. Remains the v1.1 acceptance criterion when the batched pipeline lands. -`clarion analyze /home/john/elspeth` costs $15 ± 50% in LLM spend (range: $7.50 - $22.50) at default profile with current Anthropic pricing. +`loomweave analyze /home/john/elspeth` costs $15 ± 50% in LLM spend (range: $7.50 - $22.50) at default profile with current Anthropic pricing. **Rationale**: Matches the detailed-design's example run. The ±50% band reflects estimator uncertainty (subsystem synthesis cost varies with clustering) plus pricing volatility; wider bands undermine operator trust. **Verification**: Full elspeth run measures total cost; repeat runs within the band. @@ -1032,25 +1032,25 @@ The dry-run cost estimate is within ±50% of actual spend on representative proj #### NFR-RELIABILITY-01 — Crash-surviving store -> **v1.x status amended by ADR-041.** `.clarion/clarion.db` must survive +> **v1.x status amended by ADR-041.** `.loomweave/loomweave.db` must survive > unclean shutdown (SIGKILL during analyze) without corruption. Subsequent -> `clarion analyze --resume ` safely reopens and re-walks the same run +> `loomweave analyze --resume ` safely reopens and re-walks the same run > id; it does not continue from a phase/file checkpoint. **Rationale**: SQLite WAL + writer-actor transactions protect the store from corruption. Same-run re-emit protects federated finding lifecycle semantics; skipping already completed work is deferred checkpoint-recovery behavior, not the v1.x guarantee. -**Verification**: Test harness: `clarion analyze` → `kill -9` mid-run → next +**Verification**: Test harness: `loomweave analyze` → `kill -9` mid-run → next invocation opens the DB cleanly → `--resume ` reopens the row and can complete or fail deterministically without Filigree unseen flapping. **See**: System Design §4 (Storage, Concurrency). #### NFR-RELIABILITY-02 — Degraded modes for missing siblings -`clarion analyze --no-filigree` (Filigree unreachable) writes findings to `runs//findings.jsonl` locally and continues. `clarion analyze --no-wardline` skips Wardline state ingest and continues. Missing `clarion sarif import` doesn't block `clarion analyze`. +`loomweave analyze --no-filigree` (Filigree unreachable) writes findings to `runs//findings.jsonl` locally and continues. `loomweave analyze --no-wardline` skips Wardline state ingest and continues. Missing `loomweave sarif import` doesn't block `loomweave analyze`. -**Rationale**: Clarion ships on its own timeline, not the slowest of three (`CON-LOOM-01` — enrichment-not-load-bearing). Explicit flags document the degradation; they're not silent fallbacks. +**Rationale**: Loomweave ships on its own timeline, not the slowest of three (`CON-WEFT-01` — enrichment-not-load-bearing). Explicit flags document the degradation; they're not silent fallbacks. **Verification**: Each flag produces a successful run with the corresponding feature disabled; per-flag compat-report entry. **See**: System Design §11 (Suite Bootstrap, Per-component fallbacks). @@ -1076,7 +1076,7 @@ CI runs a schema-compatibility test against a tagged Filigree release's `GET /ap #### NFR-COMPAT-02 — Wardline REGISTRY pin test -Clarion's plugin verifies `wardline.core.registry.REGISTRY_VERSION` against a pinned version at startup. Additive-newer passes with warning; major-bump or older falls back to mirror mode with a finding. +Loomweave's plugin verifies `wardline.core.registry.REGISTRY_VERSION` against a pinned version at startup. Additive-newer passes with warning; major-bump or older falls back to mirror mode with a finding. **Rationale**: Wardline's REGISTRY is the shared decorator vocabulary; skew produces incorrect Wardline-derived guidance and detection gaps. Version-pinning with graceful degradation matches the prod reality that install versions don't always match. **Verification**: Unit test with pinned version succeeds; test with mismatched version produces expected finding. @@ -1094,35 +1094,35 @@ The Anthropic model-tier mapping (`haiku / sonnet / opus` → concrete model IDs ## Constraints (`CON-*`) -External limits that shape what Clarion v0.1 can do. +External limits that shape what Loomweave v0.1 can do. -### CON-LOOM-01 — Loom federation axiom (solo + pairwise + enrich-only) +### CON-WEFT-01 — Weft federation axiom (solo + pairwise + enrich-only) -Clarion v0.1 must satisfy the Loom federation axiom: useful standalone, composable pairwise with each sibling product, and enrich-only with respect to sibling data. Sibling absence must never change the *meaning* of Clarion's own data; reduced capability is acceptable, altered semantics is not. +Loomweave v0.1 must satisfy the Weft federation axiom: useful standalone, composable pairwise with each sibling product, and enrich-only with respect to sibling data. Sibling absence must never change the *meaning* of Loomweave's own data; reduced capability is acceptable, altered semantics is not. -**Rationale**: Founding doctrine of the Loom suite ([../../suite/loom.md](../../suite/loom.md) §5). Violating it collapses federation into monolith. -**Verification**: Clarion operates meaningfully with `--no-filigree` and `--no-wardline` (reduced capability, coherent semantics); briefings and catalog structure are unchanged by sibling presence. -**See**: [../../suite/loom.md](../../suite/loom.md), System Design §1 (Context & Boundaries), §11 (Suite Bootstrap). +**Rationale**: Founding doctrine of the Weft suite ([../../suite/weft.md](../../suite/weft.md) §5). Violating it collapses federation into monolith. +**Verification**: Loomweave operates meaningfully with `--no-filigree` and `--no-wardline` (reduced capability, coherent semantics); briefings and catalog structure are unchanged by sibling presence. +**See**: [../../suite/weft.md](../../suite/weft.md), System Design §1 (Context & Boundaries), §11 (Suite Bootstrap). ### CON-FILIGREE-01 — Use Filigree's native scan-results intake (not SARIF) -Clarion emits findings to Filigree via `POST /api/v1/scan-results` using Filigree's flat JSON schema. Extension fields nest under `metadata` (not `properties`). Line fields are `line_start` + `line_end` (not a single `line`). Severity uses Filigree's lowercase enum (`{critical, high, medium, low, info}`). +Loomweave emits findings to Filigree via `POST /api/v1/scan-results` using Filigree's flat JSON schema. Extension fields nest under `metadata` (not `properties`). Line fields are `line_start` + `line_end` (not a single `line`). Severity uses Filigree's lowercase enum (`{critical, high, medium, low, info}`). **Rationale**: Filigree's scan-results endpoint is the production path; deviating requires Filigree work that is out of v0.1 scope. The `metadata` nesting and severity enum are existing Filigree semantics verified by recon. **See**: System Design §9 (Integrations, Filigree — Wire format). ### CON-FILIGREE-02 — File-registry displacement is deferred to v0.2 -Clarion v0.1 does **not** displace Filigree's file registry. The "Clarion owns the file registry" story — Filigree's `registry_backend` flag + pluggable `RegistryProtocol` and Clarion serving as Filigree's registry backend — is deferred to v0.2 per the [Sprint 2 scope amendment §4](../../implementation/sprint-2/scope-amendment-2026-05.md) (WP9-B + WP10). Filigree did ship the `registry_backend` flag during Sprint 2 (ADR-014); Clarion's read-side implementation of `RegistryProtocol` and the associated suite-compat reporting are the v0.2 work. +Loomweave v0.1 does **not** displace Filigree's file registry. The "Loomweave owns the file registry" story — Filigree's `registry_backend` flag + pluggable `RegistryProtocol` and Loomweave serving as Filigree's registry backend — is deferred to v0.2 per the [Sprint 2 scope amendment §4](../../implementation/sprint-2/scope-amendment-2026-05.md) (WP9-B + WP10). Filigree did ship the `registry_backend` flag during Sprint 2 (ADR-014); Loomweave's read-side implementation of `RegistryProtocol` and the associated suite-compat reporting are the v0.2 work. -In v0.1, Clarion operates in shadow-registry mode by default: Clarion owns its entity catalog; Filigree, when present, auto-creates `file_records` under its native rules. The two stores reference the same paths but neither delegates id allocation to the other. This is acceptable because Clarion v0.1's MVP MCP surface (the seven tools — `entity_at`, `find_entity`, `callers_of`, `execution_paths_from`, `summary`, `issues_for`, `neighborhood`) queries Clarion's own SQLite store directly and does not require registry coupling. +In v0.1, Loomweave operates in shadow-registry mode by default: Loomweave owns its entity catalog; Filigree, when present, auto-creates `file_records` under its native rules. The two stores reference the same paths but neither delegates id allocation to the other. This is acceptable because Loomweave v0.1's MVP MCP surface (the seven tools — `entity_at`, `find_entity`, `callers_of`, `execution_paths_from`, `summary`, `issues_for`, `neighborhood`) queries Loomweave's own SQLite store directly and does not require registry coupling. **Rationale**: Filigree's four NOT-NULL `file_records(id)` foreign keys + three auto-create paths make registry displacement a schema-surgery integration, not a feature flag — out of v0.1 scope per the scope amendment. Shadow mode preserves v0.1 shipability; full integration lands with the WP9-B/WP10 cluster in v0.2. **See**: System Design §11 (Suite Bootstrap), [ADR-014 — Filigree `registry_backend` flag and pluggable `RegistryProtocol`](../adr/ADR-014-filigree-registry-backend.md), [Sprint 2 scope amendment §4](../../implementation/sprint-2/scope-amendment-2026-05.md). ### CON-WARDLINE-01 — Wardline owns its REGISTRY -Clarion consumes Wardline's `wardline.core.registry.REGISTRY` and `REGISTRY_VERSION` via direct Python import in v0.1. Wardline's decorator vocabulary is authoritative; Clarion does not redefine it, override it, or ship a parallel vocabulary. +Loomweave consumes Wardline's `wardline.core.registry.REGISTRY` and `REGISTRY_VERSION` via direct Python import in v0.1. Wardline's decorator vocabulary is authoritative; Loomweave does not redefine it, override it, or ship a parallel vocabulary. **Rationale**: Principle 5 (observe vs. enforce); Wardline is authoritative for trust-topology vocabulary. Respecting this keeps the suite coherent. **See**: System Design §2 (Core / Plugin Architecture, Direct REGISTRY import), §11 (Suite Bootstrap, Prerequisites named here). @@ -1131,28 +1131,28 @@ Clarion consumes Wardline's `wardline.core.registry.REGISTRY` and `REGISTRY_VERS > **Superseded by [ADR-039](../adr/ADR-039-llm-provider-pivot-openrouter-cli.md)**. The implementation pivoted to OpenRouter (live HTTP) + Codex/Claude CLI bridges + a recording provider, all declaring `CachingModel::OpenAiChatCompletions` rather than Anthropic's four-`cache_control`-breakpoint scheme. The "Anthropic-only" constraint and its four-segment-caching premise below are retained for historical traceability; ADR-039 is the current decision. -Clarion v0.1's LLM provider is Anthropic only. The `LlmProvider` trait exists for testability (`RecordingProvider`) and future extensibility, but the plugin-level prompt protocol assumes Anthropic prompt-caching semantics (four `cache_control` breakpoints at specific segment boundaries). Adding a provider without that caching structure sacrifices cost performance. +Loomweave v0.1's LLM provider is Anthropic only. The `LlmProvider` trait exists for testability (`RecordingProvider`) and future extensibility, but the plugin-level prompt protocol assumes Anthropic prompt-caching semantics (four `cache_control` breakpoints at specific segment boundaries). Adding a provider without that caching structure sacrifices cost performance. **Rationale**: Anthropic's prompt caching is the mechanism that makes elspeth-scale cost tractable; alternative providers either lose caching advantage (pay more) or require prompt-protocol refactoring (v0.3+). **See**: System Design §5 (Policy Engine, LLM provider abstraction — Honest framing). ### CON-LOCAL-01 — Local-first operational posture -Clarion v0.1 runs entirely on the operator's machine. No mandatory cloud service, no telemetry, no hosted component. The only required network egress is the LLM provider API during summarisation. +Loomweave v0.1 runs entirely on the operator's machine. No mandatory cloud service, no telemetry, no hosted component. The only required network egress is the LLM provider API during summarisation. -**Rationale**: Enterprise-at-lack-of-scale commitment. A hosted component re-introduces the platform-team burden Clarion is designed to avoid. +**Rationale**: Enterprise-at-lack-of-scale commitment. A hosted component re-introduces the platform-team burden Loomweave is designed to avoid. **See**: System Design §1 (Context & Boundaries). ### CON-RUST-01 — Core implemented in Rust -Clarion's core is implemented in Rust. This is a directive (ADR-001) and not subject to alternatives analysis. +Loomweave's core is implemented in Rust. This is a directive (ADR-001) and not subject to alternatives analysis. **Rationale**: Primary author's directive. Consequences (single-binary ship, mature ecosystem, plugin interop via subprocess, higher recruiting bar) are accepted. **See**: System Design §12 (Architecture Decisions — ADR-001), [../adr/ADR-001-rust-for-core.md](../adr/ADR-001-rust-for-core.md). ### CON-SQLITE-01 — SQLite is the v0.1 store -Clarion v0.1 uses SQLite as its persistence layer. Kuzu, DuckDB, and custom graph stores are considered and rejected for v0.1 (detailed-design §4); repository layer is kept thin enough to swap post-v0.3 if profiling demands. +Loomweave v0.1 uses SQLite as its persistence layer. Kuzu, DuckDB, and custom graph stores are considered and rejected for v0.1 (detailed-design §4); repository layer is kept thin enough to swap post-v0.3 if profiling demands. **Rationale**: SQLite is single-file, mature, debuggable with standard tooling, and handles JSON1 + FTS5 without giving up query ergonomics. Matches the "enterprise at lack of scale" commitment. **See**: System Design §4 (Storage — Technology). @@ -1161,71 +1161,71 @@ Clarion v0.1 uses SQLite as its persistence layer. Kuzu, DuckDB, and custom grap ## Non-Goals (`NG-*`) -These are Clarion v0.1's authoritative non-goals — what the product explicitly does NOT do. Items originating as deferrals from the pre-restructure design have been normalised into this list. +These are Loomweave v0.1's authoritative non-goals — what the product explicitly does NOT do. Items originating as deferrals from the pre-restructure design have been normalised into this list. ### NG-01 — Not a linter or pattern-rule scanner -Clarion does not implement pattern-rule scanning of the kind Wardline's enforcer owns. Structural findings (`CLA-FACT-*`) emit observations; they do not enforce compliance. +Loomweave does not implement pattern-rule scanning of the kind Wardline's enforcer owns. Structural findings (`LMWV-FACT-*`) emit observations; they do not enforce compliance. **Why**: Principle 5 (observe vs enforce). Wardline's territory. ### NG-02 — Not a workflow / issue tracker -Clarion does not own issues, triage state, or workflow transitions. Filigree is authoritative. +Loomweave does not own issues, triage state, or workflow transitions. Filigree is authoritative. -**Why**: Loom federation — Filigree's territory. +**Why**: Weft federation — Filigree's territory. ### NG-03 — Not an IDE or editor -Clarion is invoked by existing editors (via MCP clients like Claude Code). It does not ship its own editor surface. +Loomweave is invoked by existing editors (via MCP clients like Claude Code). It does not ship its own editor surface. **Why**: Out of scope; users already have editors. ### NG-04 — Not a code-search tool -Clarion ships `search_*` MCP tools (`search_structural`, `search_semantic`) scoped to consult navigation. These are not a grep-replacement — external tools (ripgrep, GitHub code search, IDE search) remain the right answer for text-hunting. +Loomweave ships `search_*` MCP tools (`search_structural`, `search_semantic`) scoped to consult navigation. These are not a grep-replacement — external tools (ripgrep, GitHub code search, IDE search) remain the right answer for text-hunting. **Why**: Search serves consult-mode navigation, not general text search. ### NG-05 — Not a dataflow / taint analyser -Clarion does not implement dataflow or taint analysis. Wardline's concern; Clarion surfaces Wardline's findings via MCP. +Loomweave does not implement dataflow or taint analysis. Wardline's concern; Loomweave surfaces Wardline's findings via MCP. **Why**: Principle 5 + Wardline's territory. ### NG-06 — Not a hosted / cloud service -Clarion does not offer a hosted deployment, a cloud-managed service, or multi-tenant SaaS. Local-first, single-binary only. +Loomweave does not offer a hosted deployment, a cloud-managed service, or multi-tenant SaaS. Local-first, single-binary only. **Why**: `CON-LOCAL-01`; enterprise-at-lack-of-scale commitment. ### NG-07 — Not a change executor (Shuttle's territory) -Clarion does not execute code changes, propose edits, apply patches, or run tests as part of an edit workflow. Transactional scoped change execution is Shuttle's territory (see [../../suite/loom.md](../../suite/loom.md)), not Clarion's. +Loomweave does not execute code changes, propose edits, apply patches, or run tests as part of an edit workflow. Transactional scoped change execution is Shuttle's territory (see [../../suite/weft.md](../../suite/weft.md)), not Loomweave's. -**Why**: Loom federation — Shuttle, when built, owns this domain. Clarion observes; Shuttle executes. +**Why**: Weft federation — Shuttle, when built, owns this domain. Loomweave observes; Shuttle executes. ### NG-08 — Not a code transformer / refactoring suggester -Clarion does not rewrite source, propose refactorings, or emit code patches. "Clarion observes, it doesn't rewrite." +Loomweave does not rewrite source, propose refactorings, or emit code patches. "Loomweave observes, it doesn't rewrite." **Why**: Out of roadmap (never, not just deferred). ### NG-09 — Not a CI-time enforcer -Clarion emits findings to Filigree and structural signals to operators; it does not block CI pipelines, fail builds, or enforce gates. Enforcement belongs to Wardline (at commit cadence) and Filigree's triage policies. +Loomweave emits findings to Filigree and structural signals to operators; it does not block CI pipelines, fail builds, or enforce gates. Enforcement belongs to Wardline (at commit cadence) and Filigree's triage policies. -**Why**: Principle 5 + Loom federation. +**Why**: Principle 5 + Weft federation. ### NG-10 — Not a real-time file watcher -Clarion does not watch the filesystem and re-analyse on change. LLM spend model doesn't support it; `clarion analyze` is explicitly batch. +Loomweave does not watch the filesystem and re-analyse on change. LLM spend model doesn't support it; `loomweave analyze` is explicitly batch. **Why**: Cost posture. ### NG-11 — Not a multi-branch analyser -Clarion analyses one tree at a time. Comparing analyses across branches is an operator-level concern (running `clarion analyze` on each branch and diffing outputs). +Loomweave analyses one tree at a time. Comparing analyses across branches is an operator-level concern (running `loomweave analyze` on each branch and diffing outputs). **Why**: Out of scope for v0.1; overhead outweighs value. @@ -1237,19 +1237,19 @@ Incremental re-analysis (git-diff driven; per-file cache invalidation; partial r ### NG-13 — Deferred: static wiki UI -Semi-dynamic wiki (HTML served by `clarion serve`, live guidance editing, live finding lists, live filigree cross-links, consult entry points) is deferred to v0.2. V0.1 ships catalog artefacts (JSON + markdown) only. +Semi-dynamic wiki (HTML served by `loomweave serve`, live guidance editing, live finding lists, live filigree cross-links, consult entry points) is deferred to v0.2. V0.1 ships catalog artefacts (JSON + markdown) only. **Why**: Out of v0.1 scope; catalog artefacts cover the "I want to read the output" case. ### NG-14 — Deferred: `EntityAlias` rename tracking -Symbol-rename-without-file-move detaches cross-tool references in v0.1. Explicit `EntityAlias` table (with rename detection heuristics) is deferred to v0.2. V0.1 ships `clarion analyze --repair-aliases ` as a manual workaround. +Symbol-rename-without-file-move detaches cross-tool references in v0.1. Explicit `EntityAlias` table (with rename detection heuristics) is deferred to v0.2. V0.1 ships `loomweave analyze --repair-aliases ` as a manual workaround. **Why**: Rename detection is a proper research problem (AST similarity + git rename + name similarity); v0.1 accepts the limitation and names it in release notes. ### NG-15 — Deferred: second language plugin -Clarion v0.1 ships only a Python plugin. A second language plugin (Java, Rust, TypeScript) is v0.2+. +Loomweave v0.1 ships only a Python plugin. A second language plugin (Java, Rust, TypeScript) is v0.2+. **Why**: Proving the plugin protocol with one language before extending is prudent; elspeth is Python. @@ -1261,7 +1261,7 @@ Malicious-plugin threat (§10) is acknowledged; plugin binary hash-pinning in `p ### NG-17 — Deferred: triage-feedback loop from Filigree -Filigree's per-rule suppression rates and time-to-close metrics flowing back as Clarion rule-priority modifiers (e.g., rule suppressed >N% of time → `CLA-INFRA-RULE-LOW-VALUE`) is deferred to v0.2. V0.1 reads triage state one-way (for briefing enrichment, `REQ-BRIEFING-05`) but doesn't adjust emission policy. +Filigree's per-rule suppression rates and time-to-close metrics flowing back as Loomweave rule-priority modifiers (e.g., rule suppressed >N% of time → `LMWV-INFRA-RULE-LOW-VALUE`) is deferred to v0.2. V0.1 reads triage state one-way (for briefing enrichment, `REQ-BRIEFING-05`) but doesn't adjust emission policy. **Why**: Triage-feedback requires stable triage data over months; v0.1 establishes the primary emission path first. @@ -1277,21 +1277,21 @@ Build / assembly / runtime subsystem classification is v0.2. V0.1 clustering is **Why**: Non-trivial; out of v0.1 scope. -### NG-20 — Deferred: Wardline HTTP state-pull from Clarion +### NG-20 — Deferred: Wardline HTTP state-pull from Loomweave -Wardline consuming Clarion's HTTP read API via its own HTTP client + `ProjectIndex` abstraction is v0.2+. V0.1: Wardline keeps re-scanning; Clarion serves the API for future consumption. +Wardline consuming Loomweave's HTTP read API via its own HTTP client + `ProjectIndex` abstraction is v0.2+. V0.1: Wardline keeps re-scanning; Loomweave serves the API for future consumption. **Why**: Wardline-side refactor is significant; not a v0.1 timeline. ### NG-21 — Deferred: server-side per-entity dedup in Filigree -Clarion's `mark_unseen=true` workaround (`REQ-FINDING-06`) is the v0.1 dedup policy. Server-side per-entity dedup in Filigree's scan-results intake is a v0.2+ Filigree feature. +Loomweave's `mark_unseen=true` workaround (`REQ-FINDING-06`) is the v0.1 dedup policy. Server-side per-entity dedup in Filigree's scan-results intake is a v0.2+ Filigree feature. -**Why**: Filigree work; not a Clarion v0.1 deliverable. +**Why**: Filigree work; not a Loomweave v0.1 deliverable. -### NG-22 — Deferred: Clarion-native SARIF export +### NG-22 — Deferred: Loomweave-native SARIF export -Clarion emits findings to Filigree (native scan-results), not to SARIF. External SARIF export for GitHub code-scanning / CI reporters is v0.2; v0.1 relies on Wardline's existing SARIF output for that surface. +Loomweave emits findings to Filigree (native scan-results), not to SARIF. External SARIF export for GitHub code-scanning / CI reporters is v0.2; v0.1 relies on Wardline's existing SARIF output for that surface. **Why**: SARIF generation is non-trivial; v0.1 focuses on Filigree intake. @@ -1315,4 +1315,4 @@ Wardline shipping a YAML/JSON descriptor of its REGISTRY (instead of requiring d --- -**End of Clarion v1.0 requirements specification.** +**End of Loomweave v1.0 requirements specification.** diff --git a/docs/clarion/1.0/reviews/gap-analysis-2026-05-24.md b/docs/loomweave/1.0/reviews/gap-analysis-2026-05-24.md similarity index 83% rename from docs/clarion/1.0/reviews/gap-analysis-2026-05-24.md rename to docs/loomweave/1.0/reviews/gap-analysis-2026-05-24.md index cfb03587..721ebf20 100644 --- a/docs/clarion/1.0/reviews/gap-analysis-2026-05-24.md +++ b/docs/loomweave/1.0/reviews/gap-analysis-2026-05-24.md @@ -1,9 +1,9 @@ -# Clarion v1.0 — Requirements vs Implementation Gap Analysis +# Loomweave v1.0 — Requirements vs Implementation Gap Analysis **Date:** 2026-05-24 (live north-star — amendments applied as we work) -**Scope:** 125 requirement IDs from `docs/clarion/1.0/requirements.md` audited against shipped code in `crates/`, `plugins/python/`, `tests/`, and `.github/workflows/`. +**Scope:** 125 requirement IDs from `docs/loomweave/1.0/requirements.md` audited against shipped code in `crates/`, `plugins/python/`, `tests/`, and `.github/workflows/`. **Inventory:** 51 REQ-* · 28 NFR-* · 8 CON-* · 25 NG-* · 13 cross-cutting (REQ-INTEG-*). -**Status:** Supporting context only — not normative. See [`docs/clarion/1.0/README.md`](../README.md) for canonical-truth precedence. +**Status:** Supporting context only — not normative. See [`docs/loomweave/1.0/README.md`](../README.md) for canonical-truth precedence. This document is the working north-star for v1.0 publish-ready cleanup. It evolves as amendments land. It tells you, today, where shipped code agrees with the spec, where it diverges by intentional deferral, where the spec drifted away from shipped behaviour, and where there is genuine uncovered scope to backfill before publish. @@ -55,12 +55,12 @@ This document is the working north-star for v1.0 publish-ready cleanup. It evolv ## 1. Executive summary -Clarion v1.0 is a credible release for its **stated minimum viable surface** — entity ingestion, plugin host, secret scanning, Phase-3 clustering, persistent storage, HMAC-authenticated HTTP read API for Filigree federation, and a small MCP tool set. The initial audit read it as materially narrower than `requirements.md` because the requirement doc had not been updated to reflect the 2026-05-16 Sprint-2 scope amendment. As of §0 amendments 1–4, the MCP and HTTP requirement rows now correctly document the v1.0 subset. +Loomweave v1.0 is a credible release for its **stated minimum viable surface** — entity ingestion, plugin host, secret scanning, Phase-3 clustering, persistent storage, HMAC-authenticated HTTP read API for Filigree federation, and a small MCP tool set. The initial audit read it as materially narrower than `requirements.md` because the requirement doc had not been updated to reflect the 2026-05-16 Sprint-2 scope amendment. As of §0 amendments 1–4, the MCP and HTTP requirement rows now correctly document the v1.0 subset. After session 1's amendments, three patterns describe the remaining work: 1. **Doc-drift cluster (text-only fixes, not code).** The Sprint-2 amendment deferred WP6 (LLM pipeline), WP7 (guidance authoring), WP9-B (Filigree finding emission), WP10 (SARIF translator), narrowed WP8 (MCP) to 8 tools, and removed the catalog-artefact boxes B.4/B.5. `REQ-FINDING-03..06` and `REQ-INTEG-FILIGREE-01..05` carry the "Deferred to v0.2" blockquote correctly; `REQ-MCP-02/03` and `REQ-HTTP-01/02` now carry it. **Still missing the blockquote:** `REQ-ARTEFACT-01/02`, `REQ-CONFIG-02/03/04`, `REQ-GUIDANCE-01..06`, `REQ-BRIEFING-01/02/04/05/06`, `REQ-MCP-04/05/06`. These all describe surface that the amendment deferred but whose requirement rows still read as v1.0 contract — the obvious next bundle (see §5 action #1b). -2. **Whole pipeline phases absent (architectural deferral).** `clarion analyze` ends at Phase 3. Phase 0 (dry-run) and Phases 4–6 (LLM summarisation) drive `NFR-PERF-01`, `NFR-COST-01/03`, `REQ-BRIEFING-04/05`, the budget gate, and the preflight estimator. Summarisation happens lazily through MCP `summary` only per ADR-030. Once #1b lands, all of these reclassify Deferred — the work itself is genuinely v0.2. +2. **Whole pipeline phases absent (architectural deferral).** `loomweave analyze` ends at Phase 3. Phase 0 (dry-run) and Phases 4–6 (LLM summarisation) drive `NFR-PERF-01`, `NFR-COST-01/03`, `REQ-BRIEFING-04/05`, the budget gate, and the preflight estimator. Summarisation happens lazily through MCP `summary` only per ADR-030. Once #1b lands, all of these reclassify Deferred — the work itself is genuinely v0.2. 3. **Targeted code gaps outside any deferral.** A small set of v1.0-contract items that nobody amended out: `REQ-ANALYZE-03` (`--resume`), `REQ-CATALOG-04/07` (file git metadata, HEAD-SHA capture), `REQ-PLUGIN-05/06` (Python plugin import policy + decorator edges), `NFR-COMPAT-01` (Filigree schema-pin CI), `NFR-COMPAT-02` (Wardline probe pins wrong symbol), `NFR-OBSERV-01..04` (JSON logs / stats.json file / Prometheus / compat-report finding). These are the *actual* code work to publish v1.0 — once the amendments collapse the doc drift, this list is the punch list. **Disciplined areas (genuinely strong, no action):** plugin host (manifest enforcement, framing, jail, crash-loop breaker, structured host findings), secret scanner, Phase-3 Leiden clustering with seeded determinism, summary-cache 5-tuple key, HMAC + ADR-034 hardening surface, all 8 CON-* honoured (one with the minor `CodexCli` provider drift noted in §5), all 25 NG-* honoured (no scope creep into linter / file-watcher / multi-branch / SARIF / wiki territory). @@ -88,11 +88,11 @@ After session 1's amendments, three patterns describe the remaining work: Each requirement ID was traced via three sources, in this order: -1. **Requirement text** — `docs/clarion/1.0/requirements.md`, including the per-requirement `Verification:` line. -2. **Design trace bridge** — `**Addresses**:` headers in `docs/clarion/1.0/system-design.md` (twelve sections covering §2–§11; bridge density is good — every functional requirement in this audit had a navigable target). -3. **Implementation** — corresponding code under `crates/clarion-core/`, `crates/clarion-storage/`, `crates/clarion-cli/`, `crates/clarion-scanner/`, `crates/clarion-mcp/`, `plugins/python/`, plus `.github/workflows/` and `tests/`. +1. **Requirement text** — `docs/loomweave/1.0/requirements.md`, including the per-requirement `Verification:` line. +2. **Design trace bridge** — `**Addresses**:` headers in `docs/loomweave/1.0/system-design.md` (twelve sections covering §2–§11; bridge density is good — every functional requirement in this audit had a navigable target). +3. **Implementation** — corresponding code under `crates/loomweave-core/`, `crates/loomweave-storage/`, `crates/loomweave-cli/`, `crates/loomweave-scanner/`, `crates/loomweave-mcp/`, `plugins/python/`, plus `.github/workflows/` and `tests/`. -Each verdict was cross-checked against in-flight tracked work in Filigree (`get-ready` + `list-issues --status in_progress`, snapshot at `/tmp/clarion_gap_filigree_ready.json`) to avoid re-reporting known follow-ups as discoveries. Verdicts cite specific `file:line` evidence. Non-goals (NG-*) were checked **inverted** — confirming the codebase does *not* implement them — to detect scope creep. +Each verdict was cross-checked against in-flight tracked work in Filigree (`get-ready` + `list-issues --status in_progress`, snapshot at `/tmp/loomweave_gap_filigree_ready.json`) to avoid re-reporting known follow-ups as discoveries. Verdicts cite specific `file:line` evidence. Non-goals (NG-*) were checked **inverted** — confirming the codebase does *not* implement them — to detect scope creep. Verdict vocabulary: @@ -119,7 +119,7 @@ The next-bundle of probably-same-shape rows is documented as §5 action #1b — After session 2, the P1 list is much shorter and entirely "real code work" — no remaining doc-drift hidden as severity. -**Observability foundations (NFR-OBSERV-01..04).** Not deferred by any envelope. `tracing` initialised in plain text at `crates/clarion-cli/src/main.rs:69-76` with no `.json()`, no file sink, no rotation. The install template's `.gitignore` excludes `runs/*/log.jsonl` — a path nothing writes to. Stats live only in the SQLite `runs.stats` column; no `runs//stats.json` file lands. No Prometheus `/api/v1/metrics` endpoint *(this one will reclassify Deferred once REQ-HTTP-01 is propagated through NFR-OBSERV-03; it's part of the broader HTTP surface)*. No `CLA-INFRA-SUITE-COMPAT-REPORT` emitter. Three of four are direct v1.0 code work; one (`-03`) probably joins HTTP-01's deferral. +**Observability foundations (NFR-OBSERV-01..04).** Not deferred by any envelope. `tracing` initialised in plain text at `crates/loomweave-cli/src/main.rs:69-76` with no `.json()`, no file sink, no rotation. The install template's `.gitignore` excludes `runs/*/log.jsonl` — a path nothing writes to. Stats live only in the SQLite `runs.stats` column; no `runs//stats.json` file lands. No Prometheus `/api/v1/metrics` endpoint *(this one will reclassify Deferred once REQ-HTTP-01 is propagated through NFR-OBSERV-03; it's part of the broader HTTP surface)*. No `LMWV-INFRA-SUITE-COMPAT-REPORT` emitter. Three of four are direct v1.0 code work; one (`-03`) probably joins HTTP-01's deferral. **Schema/version compat (NFR-COMPAT-01/02).** Genuinely outside any deferral. NFR-COMPAT-01 = Filigree schema-pin CI test missing (one job). NFR-COMPAT-02 = Wardline probe pins `wardline.__version__` instead of `REGISTRY_VERSION`; named graceful-degradation findings have no emitter. @@ -127,7 +127,7 @@ After session 2, the P1 list is much shorter and entirely "real code work" — n **Three targeted REQ-* rows outside any envelope.** `REQ-ANALYZE-03` (`--resume` CLI flag + checkpoint reader), `REQ-CATALOG-07` (HEAD-SHA capture into `first_seen_commit`/`last_seen_commit` — columns exist, populated as `None`), `REQ-CATALOG-04` (file-entity git metadata). -**Ops gaps (NFR-OPS-03/04).** `clarion db export --textual` + `db merge-helper` don't exist (NFR-OPS-03 — either build them or narrow the requirement); PyPI publish + SLSA-over-sdist outstanding (NFR-OPS-04, tracked under `clarion-f530101222`). +**Ops gaps (NFR-OPS-03/04).** `loomweave db export --textual` + `db merge-helper` don't exist (NFR-OPS-03 — either build them or narrow the requirement); PyPI publish + SLSA-over-sdist outstanding (NFR-OPS-04, tracked under `clarion-f530101222`). ### 3.3 P1 — Targeted gaps outside any deferral envelope @@ -141,7 +141,7 @@ After session 2, the P1 list is much shorter and entirely "real code work" — n - NFR-PERF-02 (≤100 ms initialize, ≤50 ms p95 hot-cache summary): mechanisms correct (`lib.rs:176,256,788,1339-1367`), no harness. - NFR-SCALE-01 (elspeth ±20% entity count, no OOM): `EntityCountCap=500k` wired (`limits.rs:115`), no recorded elspeth run. -- NFR-SCALE-02 (`.clarion/clarion.db` ≤2 GB): no size reporter, no measured run. +- NFR-SCALE-02 (`.loomweave/loomweave.db` ≤2 GB): no size reporter, no measured run. - NFR-SCALE-03 (16-reader pool, no exhaustion): wired (`serve.rs:46`), saturation tested for max_size=1/2 only; no combined-load test. - NFR-COST-02 (≥95% summary-cache hit rate after 3 runs): mechanism correct (`cache.rs:48-110`), no `cache_hit_rate` aggregation in `stats.json`. @@ -163,23 +163,23 @@ Status legend: ✅ done this session · ⏳ still open. Per the standing "close already-done tickets" authorisation in user memory: - **`clarion-a4fb59a96a` — rollback runbook** — `docs/operator/v1.0-release-rollback.md` exists (141 lines). -- **`clarion-42f4fee904` — loopback trust banner** — emitted at `crates/clarion-cli/src/http_read.rs:244-248`; documented at `docs/operator/clarion-http-read-api.md:58-73`. +- **`clarion-42f4fee904` — loopback trust banner** — emitted at `crates/loomweave-cli/src/http_read.rs:244-248`; documented at `docs/operator/loomweave-http-read-api.md:58-73`. Both can transition through the full workflow to `closed`. ### 3.7 One CON drift (governance, not severity) -`CON-ANTHROPIC-01` says "Anthropic-only LLM provider in v0.1." `crates/clarion-mcp/src/config.rs:95-99` enumerates `OpenRouter`, `CodexCli`, `ClaudeCli`. `CodexCli` (`crates/clarion-cli/src/serve.rs:11`) shells out to OpenAI's Codex CLI — a non-Anthropic vendor surface. The constraint's prompt-caching rationale arguably doesn't apply to CLI shell-outs, which is probably why the drift went unflagged. Recommend either (a) amend the constraint to recognise local-CLI providers as a separate category, or (b) gate `CodexCli` behind a default-off cargo feature. +`CON-ANTHROPIC-01` says "Anthropic-only LLM provider in v0.1." `crates/loomweave-mcp/src/config.rs:95-99` enumerates `OpenRouter`, `CodexCli`, `ClaudeCli`. `CodexCli` (`crates/loomweave-cli/src/serve.rs:11`) shells out to OpenAI's Codex CLI — a non-Anthropic vendor surface. The constraint's prompt-caching rationale arguably doesn't apply to CLI shell-outs, which is probably why the drift went unflagged. Recommend either (a) amend the constraint to recognise local-CLI providers as a separate category, or (b) gate `CodexCli` behind a default-off cargo feature. --- ## 4. Strongest areas (no action required) -- **Plugin host (REQ-PLUGIN-01..04, REQ-FINDING-01/02, NFR-SEC-01/05).** Content-Length framing (`transport.rs:113,287`), manifest enforcement (`host.rs:897,1043`), undeclared-kind drop tests (`host.rs:1586,2679`), crash-loop breaker (`breaker.rs:16`), entity cap and OOM kill (`limits.rs:115`), path-escape jail. Findings vocabulary (`Defect | Fact | Classification | Metric | Suggestion`) round-trips through a CHECK-constrained `findings` table; per-plugin `rule_id_prefix` works as designed. `.clarion/.gitignore` shipped by `install.rs:97`. +- **Plugin host (REQ-PLUGIN-01..04, REQ-FINDING-01/02, NFR-SEC-01/05).** Content-Length framing (`transport.rs:113,287`), manifest enforcement (`host.rs:897,1043`), undeclared-kind drop tests (`host.rs:1586,2679`), crash-loop breaker (`breaker.rs:16`), entity cap and OOM kill (`limits.rs:115`), path-escape jail. Findings vocabulary (`Defect | Fact | Classification | Metric | Suggestion`) round-trips through a CHECK-constrained `findings` table; per-plugin `rule_id_prefix` works as designed. `.loomweave/.gitignore` shipped by `install.rs:97`. - **Phase-3 Leiden clustering (REQ-CATALOG-05).** Seeded determinism asserted by `tests/analyze.rs:730 analyze_phase3_is_deterministic_across_two_runs`. Subsystem entities and `in_subsystem` edges flow through the writer-actor; e2e at `tests/e2e/phase3_subsystems.sh`. - **Summary-cache 5-tuple key (REQ-BRIEFING-03).** PK = `entity_id + content_hash + prompt_template_id + model_tier + guidance_fingerprint` exactly per ADR-007 (`cache.rs:9-72`; `migrations/0001_initial_schema.sql:151-164`). Round-trip tested. - **HMAC + ADR-034 federation surface (REQ-HTTP-03, NFR-SEC-03).** Six tests at `tests/serve.rs:1160..1342` cover loopback default, non-loopback-without-auth refusal, HMAC-required path, legacy bearer path, identity-env missing refusal. Code in `http_read.rs:179-251,389-498`. -- **Secret scanner (NFR-SEC-01).** `crates/clarion-scanner/` + `crates/clarion-cli/src/secret_scan.rs` (574 LOC) with baseline justification + dedicated e2e at `tests/e2e/wp5_secret_scan.sh`. +- **Secret scanner (NFR-SEC-01).** `crates/loomweave-scanner/` + `crates/loomweave-cli/src/secret_scan.rs` (574 LOC) with baseline justification + dedicated e2e at `tests/e2e/wp5_secret_scan.sh`. - **All 25 NG-* honoured.** No rule engine, no taint, no SARIF, no file watchers, no multi-branch, no wiki UI, no rename detection, no coverage ingestion, no second plugin, no plugin hash-pinning. The codebase shows real scope discipline. - **7 of 8 CON-* satisfied** (`CON-ANTHROPIC-01` partial drift noted in §3.7; CON-FILIGREE-01 properly Deferred). @@ -196,11 +196,11 @@ Sorted by leverage. Status legend: ✅ done · ⏳ pending. Each item names a ta | 1c | ✅ | **Final amendment sweep.** `REQ-INTEG-WARDLINE-02..06` row-level blockquotes added; `CHANGELOG.md` known-limitations now enumerates Wardline state-file ingest alongside the REGISTRY-import asterisk; `NFR-SEC-03` verification swapped from stale line numbers to test-name citations. Done 2026-05-24 (checklist phase 1). | done | Closed the last amendment-deferred ambiguity; doc-drift workstream complete. | | 2 | ⏳ | Backfill the three uncovered REQ rows: `REQ-ANALYZE-03` (`--resume`), `REQ-CATALOG-07` (HEAD-SHA capture into `first_seen_commit` / `last_seen_commit`), `REQ-CATALOG-04` (git metadata on file entities). | v1.0 publish-ready | None deferred by any carve-out; storage substrate already shaped. | | 3 | ⏳ | Python plugin: implement `REQ-PLUGIN-05` (TYPE_CHECKING exclusion, src-prefix canonicalisation, `python:unresolved:*` placeholders, `alias_of` edges) + `REQ-PLUGIN-06` (`decorated_by`/`inherits_from`/`uses_type` edges). | v1.0 publish-ready | Not in any defer list; core ontology surface advertised at v1.0. | -| 4 | ⏳ | Wardline probe — pin `REGISTRY_VERSION` (not `__version__`), emit `CLA-INFRA-WARDLINE-REGISTRY-ADDITIVE-SKEW` and `-MIRRORED` findings on version drift. | v1.0 publish-ready | Cheap fix, named in detailed-design.md:1169-1170, currently invisible. | +| 4 | ⏳ | Wardline probe — pin `REGISTRY_VERSION` (not `__version__`), emit `LMWV-INFRA-WARDLINE-REGISTRY-ADDITIVE-SKEW` and `-MIRRORED` findings on version drift. | v1.0 publish-ready | Cheap fix, named in detailed-design.md:1169-1170, currently invisible. | | 5 | ⏳ | Add Filigree schema-pin CI job (`NFR-COMPAT-01`). | v1.0 publish-ready | One job; closes a P1 with low effort. | -| 6 | ⏳ | Observability foundations: JSON-formatted tracing layer with file sink + rotation; `runs//stats.json` file emission alongside the SQLite column; `CLA-INFRA-SUITE-COMPAT-REPORT` emitter. `NFR-OBSERV-03` (Prometheus `/api/v1/metrics`) probably becomes Deferred — depends on broader HTTP surface already deferred via REQ-HTTP-01. | v1.0 publish-ready | Three NFR-OBSERV-* rows; one cohesive workstream. | -| 7 | ⏳ | Add `clarion db export --textual` + `clarion db merge-helper` subcommands, or narrow `NFR-OPS-03` to reflect the commit-by-default-only scope. | v1.0 publish-ready | Currently the text overpromises. | -| 8 | ⏳ | Publish `clarion-plugin-python` to PyPI and extend SLSA provenance to the sdist (existing ticket `clarion-f530101222`). | v1.0 publish-ready | Closes `pipx install clarion-plugin-python` per `NFR-OPS-04`. | +| 6 | ⏳ | Observability foundations: JSON-formatted tracing layer with file sink + rotation; `runs//stats.json` file emission alongside the SQLite column; `LMWV-INFRA-SUITE-COMPAT-REPORT` emitter. `NFR-OBSERV-03` (Prometheus `/api/v1/metrics`) probably becomes Deferred — depends on broader HTTP surface already deferred via REQ-HTTP-01. | v1.0 publish-ready | Three NFR-OBSERV-* rows; one cohesive workstream. | +| 7 | ⏳ | Add `loomweave db export --textual` + `loomweave db merge-helper` subcommands, or narrow `NFR-OPS-03` to reflect the commit-by-default-only scope. | v1.0 publish-ready | Currently the text overpromises. | +| 8 | ⏳ | Publish `loomweave-plugin-python` to PyPI and extend SLSA provenance to the sdist (existing ticket `clarion-f530101222`). | v1.0 publish-ready | Closes `pipx install loomweave-plugin-python` per `NFR-OPS-04`. | | 9 | ⏳ | Decide on `CON-ANTHROPIC-01` × `CodexCliProvider`: either amend constraint or gate behind feature flag. | ADR or constraint amendment | Governance, not severity, but currently invisible drift. | | 10 | ⏳ | Close already-done Filigree issues per standing authorisation: `clarion-a4fb59a96a` (rollback runbook), `clarion-42f4fee904` (loopback banner). | Immediate | Both ship in HEAD; tickets are stale. | | 11 | ⏳ | Backlog: elspeth-scale measurement task — cargo bench harness for MCP latency, single recorded elspeth run capturing entity count + DB size + cache hit rate + stats.json. | post-publish, before 1.1 | Flips five P2 "implemented but unmeasured" verdicts to Satisfied without product code. | @@ -217,9 +217,9 @@ Sorted by leverage. Status legend: ✅ done · ⏳ pending. Each item names a ta | REQ-ANALYZE-02 | Partial | `analyze.rs:308`; writer serial | No LLM parallelism | — | deferred:scope-amendment | | REQ-ANALYZE-03 | **Missing** | `cli.rs:30` no flag | No `--resume`; no checkpoint reader | P1 | new | | REQ-ANALYZE-04 | Built (v1.1) | deletion findings in the SEI mint pass (`analyze.rs` `emit_deletion_findings`); `--no-sei` disables | — (was: Phase-7 entity-set diff) | — | built:v1.1 | -| REQ-ANALYZE-05 | Built (v1.1) | `analyze.rs` `emit_tier_subsystem_findings` (tier × subsystem, function→subsystem resolution); conditional on Wardline ingest | — (was: Phase-7 `CLA-*` rules) | — | built:v1.1 | -| REQ-ANALYZE-06 | Partial | `breaker.rs:16`, `host_findings.rs`, `limits.rs` | Named rules `CLA-PY-PARSE-ERROR`/`-TIMEOUT`/`CLA-INFRA-LLM-ERROR`/`-BUDGET-WARNING` absent | P2 | new | -| REQ-ANALYZE-07 | Partial | `tests/analyze.rs:730` | No `clarion db export --textual`; whole-catalog byte-id not verified | P3 | new | +| REQ-ANALYZE-05 | Built (v1.1) | `analyze.rs` `emit_tier_subsystem_findings` (tier × subsystem, function→subsystem resolution); conditional on Wardline ingest | — (was: Phase-7 `LMWV-*` rules) | — | built:v1.1 | +| REQ-ANALYZE-06 | Partial | `breaker.rs:16`, `host_findings.rs`, `limits.rs` | Named rules `LMWV-PY-PARSE-ERROR`/`-TIMEOUT`/`LMWV-INFRA-LLM-ERROR`/`-BUDGET-WARNING` absent | P2 | new | +| REQ-ANALYZE-07 | Partial | `tests/analyze.rs:730` | No `loomweave db export --textual`; whole-catalog byte-id not verified | P3 | new | | REQ-ARTEFACT-01 | Deferred | no `catalog.json` emit | Doc drift: requirement lacks blockquote | — | deferred:scope-amendment | | REQ-ARTEFACT-02 | Deferred | no per-subsystem markdown | Doc drift: requirement lacks blockquote | — | deferred:scope-amendment | | REQ-BRIEFING-01 | Deferred (✅ amended §0 #14) | `llm_provider.rs:762-789` ships 4-field on-demand summary | rich 9-field `EntityBriefing` v0.2 per ADR-030 | — | deferred:scope-amendment | @@ -235,7 +235,7 @@ Sorted by leverage. Status legend: ✅ done · ⏳ pending. Each item names a ta | REQ-CATALOG-05 | Satisfied | `analyze.rs:657`; `clustering.rs`; `tests/analyze.rs:730` | — | — | — | | REQ-CATALOG-06 | Satisfied | `entity_id.rs`; `qualname.py` | Move-without-rename test missing | P3 | new | | REQ-CATALOG-07 | **Missing** | `commands.rs:66`; call-sites write `None` | No HEAD-SHA capture | P1 | new | -| REQ-CONFIG-01 | Partial | `config.rs:16` | No `~/.config/clarion/defaults.yaml` merge; no `version:` field | P2 | new | +| REQ-CONFIG-01 | Partial | `config.rs:16` | No `~/.config/loomweave/defaults.yaml` merge; no `version:` field | P2 | new | | REQ-CONFIG-02 | Deferred | no `profile`/`budget`/`default`/`deep` enum | WP6 deferral | — | deferred:scope-amendment | | REQ-CONFIG-03 | Deferred | no dry-run estimator | WP6 + WP11 | — | deferred:scope-amendment | | REQ-CONFIG-04 | Deferred | no `LlmPolicyConfig` | WP6 | — | deferred:scope-amendment | @@ -255,7 +255,7 @@ Sorted by leverage. Status legend: ✅ done · ⏳ pending. Each item names a ta | REQ-HTTP-01 | Deferred (✅ amended §0 #3) | `http_read.rs:364-372` ships ADR-014 subset | broader catalogue v0.2 per amendment §4 | — | deferred:scope-amendment | | REQ-HTTP-02 | Deferred (✅ amended §0 #4) | `:resolve` handles file_path scheme only | multi-scheme oracle v0.2 (depends on WP9-B Wardline ingest) | — | deferred:scope-amendment | | REQ-HTTP-03 | Satisfied | `http_read.rs:179-251,389-498`; `tests/serve.rs:1160..1342` | — | — | tracked:adr-034-refresh | -| REQ-HTTP-04 | Partial | `http_read.rs:735-816` | Uses `ETag` per-file vs spec'd `X-Clarion-State` run-level | P2 | new | +| REQ-HTTP-04 | Partial | `http_read.rs:735-816` | Uses `ETag` per-file vs spec'd `X-Loomweave-State` run-level | P2 | new | | REQ-MCP-01 | Deferred (✅ amended §0 #6) | `lib.rs:187-211` no cursor/breadcrumb state | cursor session model v0.2 (B.6 narrowed surface) | — | deferred:scope-amendment | | REQ-MCP-02 | Deferred (✅ amended §0 #1) | `lib.rs:52-129` ships 8-tool MVP subset per amendment B.6 | broader catalogue v0.2 | — | deferred:scope-amendment | | REQ-MCP-03 | Deferred (✅ amended §0 #2) | no `find_entry_points` etc.; depends on Phase-7 pre-compute | shortcuts v0.2 per ADR-030 + amendment §4 | — | deferred:scope-amendment | @@ -282,17 +282,17 @@ Sorted by leverage. Status legend: ✅ done · ⏳ pending. Each item names a ta | REQ-INTEG-WARDLINE-02 | Deferred | no `wardline.yaml` reader | WP9-B Wardline-config ingest | P2 | deferred:scope-amendment (row-level blockquote in `requirements.md:702`) | | REQ-INTEG-WARDLINE-03 | Deferred | `entities.wardline_json` always `None` | WP9-B | P2 | deferred:scope-amendment (row-level blockquote in `requirements.md:712`) | | REQ-INTEG-WARDLINE-04 | Deferred | no `wardline.exceptions.json` reader | WP9-B | P2 | deferred:scope-amendment (row-level blockquote in `requirements.md:722`) | -| REQ-INTEG-WARDLINE-05 | Deferred | no `clarion sarif import` | WP10 | P2 | deferred:scope-amendment (row-level blockquote in `requirements.md:732`) | +| REQ-INTEG-WARDLINE-05 | Deferred | no `loomweave sarif import` | WP10 | P2 | deferred:scope-amendment (row-level blockquote in `requirements.md:732`) | | REQ-INTEG-WARDLINE-06 | Deferred | no resolve oracle for wardline schemes | Depends on -03/-04 | P2 | deferred:scope-amendment (row-level blockquote in `requirements.md:742`) | ### 6.3 NFR-* | ID | Verdict | Evidence | Gap | Sev | Tracked | |----|---------|----------|-----|-----|---------| -| NFR-SEC-01 | Satisfied | `clarion-scanner/src/lib.rs`; `secret_scan.rs:212-263`; tests | — | — | — | +| NFR-SEC-01 | Satisfied | `loomweave-scanner/src/lib.rs`; `secret_scan.rs:212-263`; tests | — | — | — | | NFR-SEC-02 | Deferred | no ``, no schema validation | ADR-009 Backlog + WP6/WP7 | — | deferred:scope-amendment | | NFR-SEC-03 | Satisfied | `tests/serve.rs:1160..1317`; HMAC helper :2231 | Verification line citations stale | — | tracked:adr-034-refresh | -| NFR-SEC-04 | Partial | `secret_scan/findings.rs:17`; `secret_scan.rs:36`; `breaker.rs:16` | `CLA-INFRA-TOKEN-STORAGE-DEGRADED`, `CLA-INFRA-BRIEFING-INVALID`, `CLA-SEC-VOCABULARY-CANDIDATE-NOVEL` absent | P2 | deferred:scope-amendment | +| NFR-SEC-04 | Partial | `secret_scan/findings.rs:17`; `secret_scan.rs:36`; `breaker.rs:16` | `LMWV-INFRA-TOKEN-STORAGE-DEGRADED`, `LMWV-INFRA-BRIEFING-INVALID`, `LMWV-SEC-VOCABULARY-CANDIDATE-NOVEL` absent | P2 | deferred:scope-amendment | | NFR-SEC-05 | Satisfied | `install.rs:97`; `tests/install.rs:40,45` | — | — | — | | NFR-RELIABILITY-01 | Partial | `pragma.rs:17-30`; writer-actor; `analyze_lock.rs` | No `--resume`; no SIGKILL+reopen test | P2 | partly deferred; tracked:STO-04 (clarion-ee22d1d72c) | | NFR-RELIABILITY-02 | Missing | no `--no-filigree`/`--no-wardline` flags; no `findings.jsonl` fallback | WP9-B | P2 | deferred:scope-amendment | @@ -308,12 +308,12 @@ Sorted by leverage. Status legend: ✅ done · ⏳ pending. Each item names a ta | NFR-COST-03 | Deferred (✅ amended §0 #17) | no preflight/dry-run code | Phase 0 deferred per ADR-030 | — | deferred:scope-amendment | | NFR-OPS-01 | Satisfied | `.github/workflows/release.yml:163,180,306,391` | Matrix 3 of 5 targets | P2 | new | | NFR-OPS-02 | Satisfied | no telemetry import; CHANGELOG | — | — | — | -| NFR-OPS-03 | Partial | `install.rs:78-97` | No `clarion db export --textual` or `db merge-helper` subcommand | P1 | new | +| NFR-OPS-03 | Partial | `install.rs:78-97` | No `loomweave db export --textual` or `db merge-helper` subcommand | P1 | new | | NFR-OPS-04 | Partial | `pyproject.toml`; `release.yml:241` | Not on PyPI; SLSA covers rust only | P1 | tracked:slsa-python-sdist (clarion-f530101222) | | NFR-OBSERV-01 | Partial | `main.rs:69-76` plain-text tracing | No JSON, no file sink, no rotation, no per-run log | P1 | new | | NFR-OBSERV-02 | Partial | `analyze.rs:174,527,563,670`; `writer.rs:234,328` | `runs//stats.json` file never written | P1 | new | | NFR-OBSERV-03 | **Missing** | no Prometheus surface | — | P1 | new | -| NFR-OBSERV-04 | **Missing** | no `CLA-INFRA-SUITE-COMPAT-REPORT` emitter | — | P1 | new | +| NFR-OBSERV-04 | **Missing** | no `LMWV-INFRA-SUITE-COMPAT-REPORT` emitter | — | P1 | new | | NFR-COMPAT-01 | **Missing** | no Filigree schema-pin CI job | — | P1 | new | | NFR-COMPAT-02 | Partial | `wardline_probe.py:35`; `plugin.toml:50`; tests | Pins `__version__` not `REGISTRY_VERSION`; no `MIRRORED`/`ADDITIVE-SKEW` findings; no mirror fallback | P1 | new | | NFR-COMPAT-03 | Missing | no Anthropic SDK dependency | LLM path deferred | P3 | deferred:scope-amendment | @@ -322,10 +322,10 @@ Sorted by leverage. Status legend: ✅ done · ⏳ pending. Each item names a ta | ID | Verdict | Evidence | Gap | Sev | Tracked | |----|---------|----------|-----|-----|---------| -| CON-LOOM-01 | Satisfied | no cross-product mediator; `filigree.rs` is read-only client | — | — | — | +| CON-WEFT-01 | Satisfied | no cross-product mediator; `filigree.rs` is read-only client | — | — | — | | CON-FILIGREE-01 | Deferred | no `scan-results` POST | WP9-B | — | deferred:scope-amendment | | CON-FILIGREE-02 | Satisfied | no `RegistryProtocol` impl; shadow-registry only | — | — | — | -| CON-WARDLINE-01 | Satisfied | `wardline_probe.py:38-43` direct import | Asterisk per loom.md §5 | — | — | +| CON-WARDLINE-01 | Satisfied | `wardline_probe.py:38-43` direct import | Asterisk per weft.md §5 | — | — | | CON-ANTHROPIC-01 | Partial | `mcp/src/config.rs:95-99`; `cli/src/serve.rs:11` | `CodexCliProvider` is non-Anthropic vendor surface | Med | new (recommend ticket) | | CON-LOCAL-01 | Satisfied | CLI-only; LLM is only network egress | — | — | — | | CON-RUST-01 | Satisfied | trivially | — | — | — | @@ -333,7 +333,7 @@ Sorted by leverage. Status legend: ✅ done · ⏳ pending. Each item names a ta ### 6.5 NG-* (inverted: all 25 honoured) -All 25 non-goals returned the expected absence pattern. No drift detected. The codebase ships no rule engine, no taint, no SARIF export, no file watchers, no multi-branch analysis, no rename detection, no wiki UI, no second language plugin, no coverage ingestion, no advanced git analysis, no plugin hash-pinning, no triage-feedback loop, no Wardline HTTP state-pull, no BAR awareness, no Wardline annotation descriptor, no `EntityAlias`, no Phase-7 cross-cutting analyses, no incremental analysis, no Filigree server-side dedup. The two named asterisks (`docs/suite/loom.md` §5: Wardline REGISTRY import, Wardline pipeline coupling) remain exactly where the federation axiom documents them with the named retirement conditions still standing. +All 25 non-goals returned the expected absence pattern. No drift detected. The codebase ships no rule engine, no taint, no SARIF export, no file watchers, no multi-branch analysis, no rename detection, no wiki UI, no second language plugin, no coverage ingestion, no advanced git analysis, no plugin hash-pinning, no triage-feedback loop, no Wardline HTTP state-pull, no BAR awareness, no Wardline annotation descriptor, no `EntityAlias`, no Phase-7 cross-cutting analyses, no incremental analysis, no Filigree server-side dedup. The two named asterisks (`docs/suite/weft.md` §5: Wardline REGISTRY import, Wardline pipeline coupling) remain exactly where the federation axiom documents them with the named retirement conditions still standing. --- @@ -341,8 +341,8 @@ All 25 non-goals returned the expected absence pattern. No drift detected. The c Source artefacts: -- Per-agent partial reports: `/tmp/clarion_gap_{A,B,C,D,E,F,G}.md` (transient; regenerated each run). -- In-flight Filigree snapshot: `/tmp/clarion_gap_filigree_ready.json` and `…_inprogress.json`. -- Trace bridge: `**Addresses**:` headers in `docs/clarion/1.0/system-design.md` lines 38, 122, 243, 389, 482, 575, 664, 749, 846, 1056, 1139. +- Per-agent partial reports: `/tmp/loomweave_gap_{A,B,C,D,E,F,G}.md` (transient; regenerated each run). +- In-flight Filigree snapshot: `/tmp/loomweave_gap_filigree_ready.json` and `…_inprogress.json`. +- Trace bridge: `**Addresses**:` headers in `docs/loomweave/1.0/system-design.md` lines 38, 122, 243, 389, 482, 575, 664, 749, 846, 1056, 1139. -The audit was executed by seven concurrent subagents (general-purpose, capped at three in flight) over `docs/clarion/1.0/requirements.md` + `docs/clarion/1.0/system-design.md` + `docs/clarion/1.0/detailed-design.md` + the workspace source. Verdicts cite specific `file:line` evidence so each row is independently re-verifiable. +The audit was executed by seven concurrent subagents (general-purpose, capped at three in flight) over `docs/loomweave/1.0/requirements.md` + `docs/loomweave/1.0/system-design.md` + `docs/loomweave/1.0/detailed-design.md` + the workspace source. Verdicts cite specific `file:line` evidence so each row is independently re-verifiable. diff --git a/docs/clarion/1.0/reviews/v1.0-publish-checklist-2026-05-24.md b/docs/loomweave/1.0/reviews/v1.0-publish-checklist-2026-05-24.md similarity index 70% rename from docs/clarion/1.0/reviews/v1.0-publish-checklist-2026-05-24.md rename to docs/loomweave/1.0/reviews/v1.0-publish-checklist-2026-05-24.md index d8af08a2..20bbd9db 100644 --- a/docs/clarion/1.0/reviews/v1.0-publish-checklist-2026-05-24.md +++ b/docs/loomweave/1.0/reviews/v1.0-publish-checklist-2026-05-24.md @@ -1,4 +1,4 @@ -# Clarion v1.0 — Publish-Ready Master Checklist +# Loomweave v1.0 — Publish-Ready Master Checklist **Date:** 2026-05-24 **Source of truth:** [`gap-analysis-2026-05-24.md`](./gap-analysis-2026-05-24.md) @@ -21,7 +21,7 @@ The amendment work is 18 of 23 done. Five rows remain. - [x] **1.6 — Update `CHANGELOG.md` known-limitations** (S) — appended after REGISTRY-import asterisk; lists -02..-06 and names WP9-B + WP10 as the v0.2 landing surfaces (`CHANGELOG.md:123-128`). - [x] **1.7 — Fix `NFR-SEC-03` verification line citations** (S) — replaced 1457/1495/1547/1579/1614 line numbers with test-name citations (`serve_rejects_non_loopback_http_bind_before_binding_without_opt_in`, `serve_http_refuses_startup_on_non_loopback_without_token`, `serve_http_refuses_startup_when_identity_env_is_missing`, `serve_http_files_endpoint_requires_hmac_identity_when_configured` + wrong-secret companion, `serve_http_files_endpoint_requires_bearer_token_when_configured` + wrong-token + batch companion, and the `serve_http_capabilities_does_not_require_token` carve-out). The three already-tracked ADR-034 refresh tickets (`clarion-7913f950d7`, `clarion-272b5bc1ec`, `clarion-461e78616f`) remain open for their own scope. -**Exit criteria:** `grep -L "Deferred to v0.2" docs/clarion/1.0/requirements.md` matches expectations; no row claims v1.0 contract for deferred surface. +**Exit criteria:** `grep -L "Deferred to v0.2" docs/loomweave/1.0/requirements.md` matches expectations; no row claims v1.0 contract for deferred surface. --- @@ -29,9 +29,9 @@ The amendment work is 18 of 23 done. Five rows remain. Small, well-scoped Rust + Python changes. Storage substrate already shaped for each. -- [ ] **2.1 — `REQ-ANALYZE-03` — `--resume`** (M) — add `--resume ` flag to `crates/clarion-cli/src/cli.rs:30` Analyze variant; implement checkpoint reader; verify `kill -9` mid-run → resume continues. Also closes NFR-RELIABILITY-01's verification surface. → [gap §3.3](./gap-analysis-2026-05-24.md#33-p1--targeted-gaps-outside-any-deferral-envelope) -- [ ] **2.2 — `REQ-CATALOG-07` — HEAD-SHA capture** (S) — populate `first_seen_commit` / `last_seen_commit` columns in `crates/clarion-storage/src/commands.rs:66`. Call-sites currently write `None` at `crates/clarion-cli/src/analyze.rs` lines 813, 1619, 1692, 2156, 2348, 2417. Read `HEAD` once at BeginRun. -- [ ] **2.3 — `REQ-CATALOG-04` — file-entity git metadata** (M) — populate `git_churn_count` / `git_last_modified` / `git_last_modified_sha` / `git_authors` / `size_bytes` / `line_count` / `mime_type` in `crates/clarion-cli/src/analyze.rs:1544` `core_file_entity_record`. `git_churn_count` is already a generated column in `migrations/0001_initial_schema.sql:266` — wire the source-JSON path. +- [ ] **2.1 — `REQ-ANALYZE-03` — `--resume`** (M) — add `--resume ` flag to `crates/loomweave-cli/src/cli.rs:30` Analyze variant; implement checkpoint reader; verify `kill -9` mid-run → resume continues. Also closes NFR-RELIABILITY-01's verification surface. → [gap §3.3](./gap-analysis-2026-05-24.md#33-p1--targeted-gaps-outside-any-deferral-envelope) +- [ ] **2.2 — `REQ-CATALOG-07` — HEAD-SHA capture** (S) — populate `first_seen_commit` / `last_seen_commit` columns in `crates/loomweave-storage/src/commands.rs:66`. Call-sites currently write `None` at `crates/loomweave-cli/src/analyze.rs` lines 813, 1619, 1692, 2156, 2348, 2417. Read `HEAD` once at BeginRun. +- [ ] **2.3 — `REQ-CATALOG-04` — file-entity git metadata** (M) — populate `git_churn_count` / `git_last_modified` / `git_last_modified_sha` / `git_authors` / `size_bytes` / `line_count` / `mime_type` in `crates/loomweave-cli/src/analyze.rs:1544` `core_file_entity_record`. `git_churn_count` is already a generated column in `migrations/0001_initial_schema.sql:266` — wire the source-JSON path. --- @@ -39,12 +39,12 @@ Small, well-scoped Rust + Python changes. Storage substrate already shaped for e The single largest code-shaped P1 in the audit. Splits into two independent changes. -- [ ] **3.1 — `REQ-PLUGIN-05` — import resolution policy** (L) — in `plugins/python/src/clarion_plugin_python/reference_resolver.py`: +- [ ] **3.1 — `REQ-PLUGIN-05` — import resolution policy** (L) — in `plugins/python/src/loomweave_plugin_python/reference_resolver.py`: - Detect and exclude `if TYPE_CHECKING:` blocks from runtime-import edges. - Canonicalise `src.`-prefixed imports (strip when project layout uses src/). - Emit `python:unresolved:` placeholder entities for unresolvable imports. - Emit `alias_of` edges for `__init__.py` re-exports (definition site wins). -- [ ] **3.2 — `REQ-PLUGIN-06` — decorator detection policy** (L) — in `plugins/python/src/clarion_plugin_python/extractor.py`: +- [ ] **3.2 — `REQ-PLUGIN-06` — decorator detection policy** (L) — in `plugins/python/src/loomweave_plugin_python/extractor.py`: - Add `decorated_by` to `plugins/python/plugin.toml:40` edge kinds list. - Detect decorators including factory invocations (`@app.route("/health")`), stacked (preserve order), class decorators, aliases (`validates = validates_shape`). - Emit `decorated_by` edges with `properties` carrying decorator arguments. @@ -56,24 +56,24 @@ The single largest code-shaped P1 in the audit. Splits into two independent chan Wardline probe + Filigree schema pin + observability foundations. -- [ ] **4.1 — `NFR-COMPAT-02` — Wardline probe pinning fix** (S) — `plugins/python/src/clarion_plugin_python/wardline_probe.py:35` currently pins `wardline.__version__`; should pin `wardline.core.registry.REGISTRY_VERSION` per `detailed-design.md:1169-1170`. Also emit `CLA-INFRA-WARDLINE-REGISTRY-MIRRORED` on out-of-range and `CLA-INFRA-WARDLINE-REGISTRY-ADDITIVE-SKEW` on additive-newer skew. Add test in `plugins/python/tests/test_wardline_probe.py`. +- [ ] **4.1 — `NFR-COMPAT-02` — Wardline probe pinning fix** (S) — `plugins/python/src/loomweave_plugin_python/wardline_probe.py:35` currently pins `wardline.__version__`; should pin `wardline.core.registry.REGISTRY_VERSION` per `detailed-design.md:1169-1170`. Also emit `LMWV-INFRA-WARDLINE-REGISTRY-MIRRORED` on out-of-range and `LMWV-INFRA-WARDLINE-REGISTRY-ADDITIVE-SKEW` on additive-newer skew. Add test in `plugins/python/tests/test_wardline_probe.py`. - [ ] **4.2 — `NFR-COMPAT-01` — Filigree schema-pin CI job** (S) — add a job to `.github/workflows/ci.yml` that fetches `GET /api/files/_schema` from a tagged Filigree release and asserts `valid_severities`, `valid_finding_statuses`, `valid_association_types` match the pinned fixture. Fixture location: `tests/fixtures/filigree-schema-pin.json`. -- [ ] **4.3 — `NFR-OBSERV-01` — JSON structured tracing** (M) — switch `crates/clarion-cli/src/main.rs:69-76` from default `tracing_subscriber::fmt` to `.json()` formatter; add file sink writing to `.clarion/clarion.log`; add per-run sink writing to `.clarion/runs//log.jsonl` (path already in install template's `.gitignore` per `install.rs:97`). Rotation: 100MB × 5. -- [ ] **4.4 — `NFR-OBSERV-02` — stats.json file emission** (S) — stats already computed at `crates/clarion-cli/src/analyze.rs:174,527,563,670` and persisted to `runs.stats` SQLite column. Add a tee that writes the same JSON to `.clarion/runs//stats.json` at CommitRun. -- [ ] **4.5 — `NFR-OBSERV-04` — `CLA-INFRA-SUITE-COMPAT-REPORT` emitter** (M) — collect: Filigree availability (already probed), Wardline REGISTRY status (from 4.1), HMAC/loopback posture, SARIF schema (when Wardline ingest lands; for now report "not configured"). Emit one consolidated finding per `clarion analyze` run. +- [ ] **4.3 — `NFR-OBSERV-01` — JSON structured tracing** (M) — switch `crates/loomweave-cli/src/main.rs:69-76` from default `tracing_subscriber::fmt` to `.json()` formatter; add file sink writing to `.loomweave/loomweave.log`; add per-run sink writing to `.loomweave/runs//log.jsonl` (path already in install template's `.gitignore` per `install.rs:97`). Rotation: 100MB × 5. +- [ ] **4.4 — `NFR-OBSERV-02` — stats.json file emission** (S) — stats already computed at `crates/loomweave-cli/src/analyze.rs:174,527,563,670` and persisted to `runs.stats` SQLite column. Add a tee that writes the same JSON to `.loomweave/runs//stats.json` at CommitRun. +- [ ] **4.5 — `NFR-OBSERV-04` — `LMWV-INFRA-SUITE-COMPAT-REPORT` emitter** (M) — collect: Filigree availability (already probed), Wardline REGISTRY status (from 4.1), HMAC/loopback posture, SARIF schema (when Wardline ingest lands; for now report "not configured"). Emit one consolidated finding per `loomweave analyze` run. - [-] **4.6 — `NFR-OBSERV-03` — Prometheus `/api/v1/metrics`** — **decision: defer**. Depends on the broader HTTP surface already deferred via REQ-HTTP-01 amendment. Bundle into a 1d amendment when convenient: add row-level "Deferred to v0.2 — depends on REQ-HTTP-01 broader HTTP surface" blockquote. --- ## Phase 5 — Ops / packaging -- [ ] **5.1 — `NFR-OPS-03` decision — `clarion db` subcommand** (M, or amend to L decision) — either: - - **Build it:** add `clarion db export --textual` (deterministic SQL dump) + `clarion db merge-helper` (subcommand stub) to `crates/clarion-cli/src/`. +- [ ] **5.1 — `NFR-OPS-03` decision — `loomweave db` subcommand** (M, or amend to L decision) — either: + - **Build it:** add `loomweave db export --textual` (deterministic SQL dump) + `loomweave db merge-helper` (subcommand stub) to `crates/loomweave-cli/src/`. - **Narrow it:** amend `NFR-OPS-03` text to scope to "commit-by-default" only; defer export/merge-helper to v0.2. Recommend the amendment path; the `db export --textual` surface is also referenced by REQ-ANALYZE-07 (determinism verification) and both could land together post-1.0. - [ ] **5.2 — `NFR-OPS-04` — PyPI publish + SLSA over sdist** (M) — tracked in Filigree as `clarion-f530101222`. Two parts: - - Publish `clarion-plugin-python` to PyPI (operator-doc'd install path changes from GitHub Release URL to `pipx install clarion-plugin-python`). + - Publish `loomweave-plugin-python` to PyPI (operator-doc'd install path changes from GitHub Release URL to `pipx install loomweave-plugin-python`). - Extend `release-subjects` job at `.github/workflows/release.yml:201` to include the sdist; SLSA generator at `:391` picks it up. - [ ] **5.3 — `NFR-OPS-01` matrix** (S, optional) — current 3-target matrix at `release.yml:163` covers macOS x86_64/aarch64 + Linux x86_64. `requirements.md:828` promises 5 targets (adds aarch64-linux + windows-x86_64). Either expand the matrix or narrow the requirement to the shipped 3. - [ ] **5.4 — Existing v1.0 blocker tickets to verify on this pass:** @@ -83,7 +83,7 @@ Wardline probe + Filigree schema pin + observability foundations. - `clarion-316e9feef9` — Verify-published-release job after create-release. - `clarion-ee22d1d72c` — PRAGMA `integrity_check` in e2e + documented backup procedure. - `clarion-04ec1044e9` — Storage deployment constraints doc (NFS, double-analyze, backup). - - `clarion-d59fc0b798` — Pre-WP5 `.clarion/` upgrade requirement doc. + - `clarion-d59fc0b798` — Pre-WP5 `.loomweave/` upgrade requirement doc. --- @@ -91,8 +91,8 @@ Wardline probe + Filigree schema pin + observability foundations. - [ ] **6.1 — Close already-done Filigree issues** (S) — per standing authorisation in user memory; verify in HEAD before closing. - `clarion-a4fb59a96a` — rollback runbook ships at `docs/operator/v1.0-release-rollback.md` (141 lines). - - `clarion-42f4fee904` — loopback trust banner emitted at `crates/clarion-cli/src/http_read.rs:244-248`; documented at `docs/operator/clarion-http-read-api.md:58-73`. -- [ ] **6.2 — `CON-ANTHROPIC-01` × `CodexCliProvider` decision** (S, governance) — `crates/clarion-mcp/src/config.rs:95-99` enumerates `CodexCli` (non-Anthropic). Two options: + - `clarion-42f4fee904` — loopback trust banner emitted at `crates/loomweave-cli/src/http_read.rs:244-248`; documented at `docs/operator/loomweave-http-read-api.md:58-73`. +- [ ] **6.2 — `CON-ANTHROPIC-01` × `CodexCliProvider` decision** (S, governance) — `crates/loomweave-mcp/src/config.rs:95-99` enumerates `CodexCli` (non-Anthropic). Two options: - **Amend constraint:** widen `CON-ANTHROPIC-01` in `requirements.md` to recognise local-CLI providers as a separate category alongside API providers. - **Gate code:** put `CodexCli` behind a default-off `cargo` feature. - Recommend amend — the constraint's prompt-caching rationale doesn't apply to CLI shell-outs. @@ -103,9 +103,9 @@ Wardline probe + Filigree schema pin + observability foundations. Flips the five "implemented but unmeasured" P2 verdicts to Satisfied without writing product code. -- [ ] **7.1 — Elspeth-slice measurement run** (M) — single recorded `clarion analyze /home/john/elspeth` run; capture entity count + DB size + cache hit rate + wall clock; commit `runs//stats.json` snapshot to `tests/fixtures/elspeth-scale-2026-05.json`. Satisfies NFR-SCALE-01, NFR-SCALE-02, NFR-COST-02 measurement gap. -- [ ] **7.2 — Criterion benchmark harness for MCP latency** (M) — add `crates/clarion-mcp/benches/mcp_latency.rs` measuring `initialize` (≤100ms) + hot-cache `summary` p95 (≤50ms). Satisfies NFR-PERF-02. -- [ ] **7.3 — Combined-load test for ReaderPool** (S) — extend `crates/clarion-storage/tests/reader_pool.rs` with a 16-reader scenario simulating the "1 consult agent + 1 Wardline-equivalent puller" load described in NFR-SCALE-03 design notes. +- [ ] **7.1 — Elspeth-slice measurement run** (M) — single recorded `loomweave analyze /home/john/elspeth` run; capture entity count + DB size + cache hit rate + wall clock; commit `runs//stats.json` snapshot to `tests/fixtures/elspeth-scale-2026-05.json`. Satisfies NFR-SCALE-01, NFR-SCALE-02, NFR-COST-02 measurement gap. +- [ ] **7.2 — Criterion benchmark harness for MCP latency** (M) — add `crates/loomweave-mcp/benches/mcp_latency.rs` measuring `initialize` (≤100ms) + hot-cache `summary` p95 (≤50ms). Satisfies NFR-PERF-02. +- [ ] **7.3 — Combined-load test for ReaderPool** (S) — extend `crates/loomweave-storage/tests/reader_pool.rs` with a 16-reader scenario simulating the "1 consult agent + 1 Wardline-equivalent puller" load described in NFR-SCALE-03 design notes. --- @@ -146,6 +146,6 @@ Phases 1, 4.1, 4.2, 5.3, 6.1, 6.2 can run in parallel and unlock most of the vis - Full verdict + evidence per requirement: [`gap-analysis-2026-05-24.md` §6](./gap-analysis-2026-05-24.md#6-full-rtm). - Amendment history: [`gap-analysis-2026-05-24.md` §0](./gap-analysis-2026-05-24.md#0-amendments-applied-this-session). - Sprint-2 scope amendment (the load-bearing precedent doc): [`docs/implementation/sprint-2/scope-amendment-2026-05.md`](../../implementation/sprint-2/scope-amendment-2026-05.md). -- ADR-030 (on-demand summary scope, narrows WP6): [`docs/clarion/adr/ADR-030-on-demand-summary-scope.md`](../adr/ADR-030-on-demand-summary-scope.md). -- ADR-014 (Filigree registry-backend HTTP read API scope): [`docs/clarion/adr/ADR-014-filigree-registry-backend.md`](../adr/ADR-014-filigree-registry-backend.md). -- ADR-034 (HTTP read API hardening): [`docs/clarion/adr/ADR-034-federation-http-read-api-hardening.md`](../adr/ADR-034-federation-http-read-api-hardening.md). +- ADR-030 (on-demand summary scope, narrows WP6): [`docs/loomweave/adr/ADR-030-on-demand-summary-scope.md`](../adr/ADR-030-on-demand-summary-scope.md). +- ADR-014 (Filigree registry-backend HTTP read API scope): [`docs/loomweave/adr/ADR-014-filigree-registry-backend.md`](../adr/ADR-014-filigree-registry-backend.md). +- ADR-034 (HTTP read API hardening): [`docs/loomweave/adr/ADR-034-federation-http-read-api-hardening.md`](../adr/ADR-034-federation-http-read-api-hardening.md). diff --git a/docs/clarion/1.0/system-design.md b/docs/loomweave/1.0/system-design.md similarity index 67% rename from docs/clarion/1.0/system-design.md rename to docs/loomweave/1.0/system-design.md index 3775c2a7..dc3c0889 100644 --- a/docs/clarion/1.0/system-design.md +++ b/docs/loomweave/1.0/system-design.md @@ -1,4 +1,4 @@ -# Clarion v1.0 — System Design +# Loomweave v1.0 — System Design **Status**: Baselined for v1.0 release (carried forward from the v0.1 post-ADR-sprint baseline) — mid-level technical companion to requirements **Baseline**: 2026-04-17 · **Last updated**: 2026-05-19 @@ -6,7 +6,7 @@ **Companion documents**: - [requirements.md](./requirements.md) — requirements (the *what*) - [detailed-design.md](./detailed-design.md) — detailed design reference (implementation-level) -- [../../suite/loom.md](../../suite/loom.md) — Loom family doctrine (federation axiom, composition law, go/no-go test) +- [../../suite/weft.md](../../suite/weft.md) — Weft family doctrine (federation axiom, composition law, go/no-go test) --- @@ -14,7 +14,7 @@ ### What this document is -This is Clarion v1.0's **system design** at mid-level technical depth. It describes how Clarion realises the requirements: component topology, data structures at a conceptual level, key mechanisms, integration contracts, and architectural decisions. It stops before implementation detail — SQL schemas, Rust crate choices, exact rule-ID catalogues, full YAML config examples, and JSON-RPC wire specifics live in the detailed-design reference. +This is Loomweave v1.0's **system design** at mid-level technical depth. It describes how Loomweave realises the requirements: component topology, data structures at a conceptual level, key mechanisms, integration contracts, and architectural decisions. It stops before implementation detail — SQL schemas, Rust crate choices, exact rule-ID catalogues, full YAML config examples, and JSON-RPC wire specifics live in the detailed-design reference. ### How to read this @@ -25,39 +25,39 @@ This is Clarion v1.0's **system design** at mid-level technical depth. It descri ### Layered docs -Requirements (what), system-design (how, mid-level), detailed-design (implementation). A reader asking "does Clarion resume after a crash?" looks in requirements. A reader asking "how does it resume?" looks here. A reader asking "what's the exact `busy_timeout` setting?" looks in the detailed-design. +Requirements (what), system-design (how, mid-level), detailed-design (implementation). A reader asking "does Loomweave resume after a crash?" looks in requirements. A reader asking "how does it resume?" looks here. A reader asking "what's the exact `busy_timeout` setting?" looks in the detailed-design. -### Loom framing +### Weft framing -Clarion is one product in the Loom suite (see [../../suite/loom.md](../../suite/loom.md)). Every integration in this design satisfies the Loom federation axiom — Clarion is useful standalone, composes pairwise with each sibling, and is enrich-only with respect to sibling data. Sibling absence reduces Clarion's capability; it does not alter Clarion's semantics. +Loomweave is one product in the Weft suite (see [../../suite/weft.md](../../suite/weft.md)). Every integration in this design satisfies the Weft federation axiom — Loomweave is useful standalone, composes pairwise with each sibling, and is enrich-only with respect to sibling data. Sibling absence reduces Loomweave's capability; it does not alter Loomweave's semantics. --- ## 1. Context & Boundaries -**Addresses**: REQ-CATALOG-01, REQ-ARTEFACT-01, REQ-ARTEFACT-02, NFR-OPS-01, NFR-OPS-02, NFR-OPS-03, CON-LOOM-01, CON-LOCAL-01, NG-03. +**Addresses**: REQ-CATALOG-01, REQ-ARTEFACT-01, REQ-ARTEFACT-02, NFR-OPS-01, NFR-OPS-02, NFR-OPS-03, CON-WEFT-01, CON-LOCAL-01, NG-03. -### The Loom family and Clarion's place in it +### The Weft family and Loomweave's place in it -Clarion is one of four products in the Loom suite, each authoritative for one bounded concern. The v0.1 suite is Clarion + Filigree + Wardline; Shuttle is proposed. +Loomweave is one of four products in the Weft suite, each authoritative for one bounded concern. The v0.1 suite is Loomweave + Filigree + Wardline; Shuttle is proposed. ```mermaid flowchart LR - subgraph Loom["Loom suite (federation, not monolith)"] + subgraph Weft["Weft suite (federation, not monolith)"] direction LR - Clarion["Clarion
structural truth
about the codebase"] + Loomweave["Loomweave
structural truth
about the codebase"] Filigree["Filigree
work state
+ workflow lifecycle"] Wardline["Wardline
trust policy
+ rule enforcement"] Shuttle["Shuttle (proposed)
transactional scoped
change execution"] end - Clarion -.->|"findings via
POST /api/v1/scan-results"| Filigree - Clarion -.->|"observations"| Filigree - Filigree -.->|"triage state
(read-only)"| Clarion + Loomweave -.->|"findings via
POST /api/v1/scan-results"| Filigree + Loomweave -.->|"observations"| Filigree + Filigree -.->|"triage state
(read-only)"| Loomweave - Wardline -->|"wardline.yaml
+ fingerprint + exceptions
+ SARIF baseline"| Clarion - Wardline -->|"REGISTRY
(direct import)"| Clarion - Wardline -.->|"SARIF → Filigree
via clarion sarif import (v0.1)"| Filigree + Wardline -->|"wardline.yaml
+ fingerprint + exceptions
+ SARIF baseline"| Loomweave + Wardline -->|"REGISTRY
(direct import)"| Loomweave + Wardline -.->|"SARIF → Filigree
via loomweave sarif import (v0.1)"| Filigree Shuttle -.->|"(v0.2+)
change telemetry"| Filigree @@ -65,26 +65,26 @@ flowchart LR class Shuttle proposed ``` -Integration is narrow, additive, and point-to-point. No shared runtime, no shared store, no central orchestrator — integration is what the federation axiom ([../../suite/loom.md](../../suite/loom.md) §3-§6) permits and nothing more. +Integration is narrow, additive, and point-to-point. No shared runtime, no shared store, no central orchestrator — integration is what the federation axiom ([../../suite/weft.md](../../suite/weft.md) §3-§6) permits and nothing more. ### Process topology -Clarion exposes three process types: +Loomweave exposes three process types: ```mermaid flowchart TB - subgraph Install["clarion install (single binary)"] + subgraph Install["loomweave install (single binary)"] direction TB - Analyze["clarion analyze
one-shot batch"] - Serve["clarion serve
long-running"] + Analyze["loomweave analyze
one-shot batch"] + Serve["loomweave serve
long-running"] end subgraph PluginSub["Language plugins (subprocess)"] direction TB - PyPlugin["clarion-plugin-python
LSP-style JSON-RPC"] + PyPlugin["loomweave-plugin-python
LSP-style JSON-RPC"] end - Store[("`.clarion/clarion.db`
SQLite WAL
committed to git")] + Store[("`.loomweave/loomweave.db`
SQLite WAL
committed to git")] Analyze -.->|"spawn per run"| PyPlugin Serve -.->|"spawn on demand
for consult queries"| PyPlugin @@ -93,27 +93,27 @@ flowchart TB Serve <-->|"stdio: MCP
loopback: HTTP"| External["MCP clients
HTTP consumers
(Wardline in CI)"] ``` -`clarion analyze` populates the store in a single batch. `clarion serve` hosts MCP over stdio (for consult-mode LLM agents) plus a read-only HTTP API over loopback (for sibling tools). Plugins are subprocesses spawned by either — they own the language-specific parsing and ontology. +`loomweave analyze` populates the store in a single batch. `loomweave serve` hosts MCP over stdio (for consult-mode LLM agents) plus a read-only HTTP API over loopback (for sibling tools). Plugins are subprocesses spawned by either — they own the language-specific parsing and ontology. ### UX modes | Mode | Surface | Purpose | v0.1 status | |---|---|---|---| -| MCP-for-LLM | `clarion serve` over stdio | First-class product surface — consult-mode agents hold a cursor, navigate the graph, emit observations to Filigree | Primary | -| Catalog artefacts | `clarion analyze` writes `.clarion/catalog.json` + per-subsystem markdown | "I want to read the output" cases | v1.1 (deferred — Sprint 2 amendment §3 removed boxes B.4/B.5; see [REQ-ARTEFACT-01](requirements.md#req-artefact-01--json-catalog-output) / [REQ-ARTEFACT-02](requirements.md#req-artefact-02--per-subsystem-markdown--top-level-index)) | -| Semi-dynamic wiki | HTML served by `clarion serve` | Live finding list, in-browser guidance editing, consult entry points | v1.1 (deferred — NG-13) | +| MCP-for-LLM | `loomweave serve` over stdio | First-class product surface — consult-mode agents hold a cursor, navigate the graph, emit observations to Filigree | Primary | +| Catalog artefacts | `loomweave analyze` writes `.loomweave/catalog.json` + per-subsystem markdown | "I want to read the output" cases | v1.1 (deferred — Sprint 2 amendment §3 removed boxes B.4/B.5; see [REQ-ARTEFACT-01](requirements.md#req-artefact-01--json-catalog-output) / [REQ-ARTEFACT-02](requirements.md#req-artefact-02--per-subsystem-markdown--top-level-index)) | +| Semi-dynamic wiki | HTML served by `loomweave serve` | Live finding list, in-browser guidance editing, consult entry points | v1.1 (deferred — NG-13) | -### Boundary contracts with the Loom siblings +### Boundary contracts with the Weft siblings -Clarion's integration posture is **enrich-only**. Clarion works standalone (NFR-RELIABILITY-02's `--no-filigree` and `--no-wardline`); with siblings present, Clarion's briefings, guidance, and findings gain context. No sibling is required for Clarion's own data to be coherent. +Loomweave's integration posture is **enrich-only**. Loomweave works standalone (NFR-RELIABILITY-02's `--no-filigree` and `--no-wardline`); with siblings present, Loomweave's briefings, guidance, and findings gain context. No sibling is required for Loomweave's own data to be coherent. -- **Filigree** (enrich-only). Clarion emits findings and observations to Filigree; Clarion reads Filigree's triage state (`REQ-BRIEFING-05`) to enrich briefings. Filigree's absence degrades Clarion to local-only finding writes — semantics intact, convenience reduced. -- **Wardline** (enrich-only). Clarion ingests `wardline.yaml` + overlays + fingerprint + exceptions at analyse time; imports `wardline.core.registry.REGISTRY` at plugin startup. Wardline's absence degrades Clarion's annotations to "confidence_basis: clarion_augmentation" — Clarion still extracts what the code declares, it just doesn't cross-check against Wardline's canonical vocabulary. -- **Shuttle** (not yet). Scope explicitly disclaimed (NG-07). Clarion does not execute changes. +- **Filigree** (enrich-only). Loomweave emits findings and observations to Filigree; Loomweave reads Filigree's triage state (`REQ-BRIEFING-05`) to enrich briefings. Filigree's absence degrades Loomweave to local-only finding writes — semantics intact, convenience reduced. +- **Wardline** (enrich-only). Loomweave ingests `wardline.yaml` + overlays + fingerprint + exceptions at analyse time; imports `wardline.core.registry.REGISTRY` at plugin startup. Wardline's absence degrades Loomweave's annotations to "confidence_basis: loomweave_augmentation" — Loomweave still extracts what the code declares, it just doesn't cross-check against Wardline's canonical vocabulary. +- **Shuttle** (not yet). Scope explicitly disclaimed (NG-07). Loomweave does not execute changes. -### What Clarion is NOT +### What Loomweave is NOT -Clarion is not a linter (NG-01, Wardline's territory), not a workflow tracker (NG-02, Filigree's territory), not an IDE (NG-03), not a grep-replacement (NG-04), not a dataflow/taint analyser (NG-05, Wardline's territory), not a hosted service (NG-06, `CON-LOCAL-01`), not a change executor (NG-07, Shuttle's territory). +Loomweave is not a linter (NG-01, Wardline's territory), not a workflow tracker (NG-02, Filigree's territory), not an IDE (NG-03), not a grep-replacement (NG-04), not a dataflow/taint analyser (NG-05, Wardline's territory), not a hosted service (NG-06, `CON-LOCAL-01`), not a change executor (NG-07, Shuttle's territory). --- @@ -134,7 +134,7 @@ This mirrors tree-sitter and LSP: adding a language (or a new semantic axis in a | Clustering (Leiden on imports + calls at module level) | Decorator / annotation detection | | LLM provider abstraction + policy engine + prompt caching | Prompt template rendering (via `build_prompt` RPC) | | MCP server + HTTP read API | Language-specific categorisation (entry points, HTTP routes, data models, test functions) | -| Finding emission to Filigree + compat probe | Language-specific rule emission (`CLA-PY-*`) | +| Finding emission to Filigree + compat probe | Language-specific rule emission (`LMWV-PY-*`) | | Guidance composition + fingerprinting | Identity reconciliation for sibling-tool qualnames | ### Plugin protocol @@ -152,7 +152,7 @@ Content-Length: \r\n\r\n ``` -The core maintains one subprocess per plugin per process. Plugin supervision uses `tokio::process::Child` with explicit `wait()` to reap zombies; SIGPIPE is ignored so a dead plugin doesn't crash the core when the core writes to its stdin; a crash-loop circuit breaker disables a plugin after >3 crashes in 60 seconds (emits `CLA-INFRA-PLUGIN-DISABLED-CRASH-LOOP`). +The core maintains one subprocess per plugin per process. Plugin supervision uses `tokio::process::Child` with explicit `wait()` to reap zombies; SIGPIPE is ignored so a dead plugin doesn't crash the core when the core writes to its stdin; a crash-loop circuit breaker disables a plugin after >3 crashes in 60 seconds (emits `LMWV-INFRA-PLUGIN-DISABLED-CRASH-LOOP`). ### Plugin lifecycle @@ -161,10 +161,10 @@ Plugins respond to two phases of lifecycle calls. ```mermaid sequenceDiagram participant Core as Rust core - participant Plugin as clarion-plugin-python + participant Plugin as loomweave-plugin-python Note over Core,Plugin: Startup - Core->>Plugin: initialize { project_root, clarion_version } + Core->>Plugin: initialize { project_root, loomweave_version } Plugin->>Core: { manifest: { kinds, edge_kinds, tags, capabilities,
supported_rule_ids, prompt_templates } } Note over Core,Plugin: Batch phase — analyze @@ -193,7 +193,7 @@ Key fields: - `kinds` — every entity kind the plugin can emit; the v1.0 Python plugin declares only `function`, `class`, and `module` - `edge_kinds` — plugin-defined edge kinds; the v1.0 Python plugin declares only `contains`, `calls`, `references`, and `imports`. Core reserves `guides`, `emits_finding`, `in_subsystem`, and core-owned containment/source anchors. - `tags` — declared tag vocabulary -- `capabilities` — boolean flags per capability (`calls`, `imports`, `inherits_from`, ...) with `confidence_basis` per capability (`ast_match`, `name_match`, `clarion_augmentation`, ...) +- `capabilities` — boolean flags per capability (`calls`, `imports`, `inherits_from`, ...) with `confidence_basis` per capability (`ast_match`, `name_match`, `loomweave_augmentation`, ...) - `supported_rule_ids` — rule IDs this plugin may emit, namespaced by prefix - `prompt_templates` — list of named templates (`python:class:v1`, `python:module:v1`, ...) with per-segment slot specifications @@ -202,7 +202,7 @@ Key fields: v1.0 plugins distribute as GitHub Release assets. The Python plugin ships as a source distribution installed with `pipx install ` into an isolated venv so plugin dependencies don't conflict with the analysed project's. -A user-level `~/.config/clarion/plugins.toml` records the resolved executable +A user-level `~/.config/loomweave/plugins.toml` records the resolved executable path plus the Python version requirement. Public registry publishing and plugin hash-pinning are deferred (NG-16). @@ -210,7 +210,7 @@ hash-pinning are deferred (NG-16). The Python plugin is the v0.1 validating plugin and the reference implementation of the manifest contract. -**Parser dispatch**. The plugin parses with the standard-library `ast` module — there is **no tree-sitter and no LibCST dependency**. Structural extraction (`function` / `class` / `module` entities, qualnames, and `imports` edges) walks the `ast` tree directly. Decorator source ranges are retained in entity definition metadata, but decorator semantics are not emitted as edges in v1.0. Call-graph and reference resolution is delegated to a managed **pyright** subprocess session (`PyrightSession`, recycled every 25 files), which serves as both the call resolver and the reference resolver. There is no `CLA-PY-PARTIAL-PARSE` fallback path. +**Parser dispatch**. The plugin parses with the standard-library `ast` module — there is **no tree-sitter and no LibCST dependency**. Structural extraction (`function` / `class` / `module` entities, qualnames, and `imports` edges) walks the `ast` tree directly. Decorator source ranges are retained in entity definition metadata, but decorator semantics are not emitted as edges in v1.0. Call-graph and reference resolution is delegated to a managed **pyright** subprocess session (`PyrightSession`, recycled every 25 files), which serves as both the call resolver and the reference resolver. There is no `LMWV-PY-PARTIAL-PARSE` fallback path. **Import extraction** (REQ-PLUGIN-05). `imports` edges are emitted from `import` / `from ... import` statements via the `ast` walk. Relative imports are normalized against the current module, `__init__.py` collapses to the package module name, `TYPE_CHECKING`-guarded imports carry `properties.type_only = true`, and function-local imports carry `properties.scope = "function"`. Graph algorithms filter those properties when they need runtime-only imports. Calls and references that pyright cannot resolve are recorded as **unresolved call / reference sites** (counted in run stats; unresolved *call* sites persist to `entity_unresolved_call_sites` for query-time inferred dispatch) rather than dropped. The plugin does **not** mint `python:unresolved:*` placeholder entities and does not emit `alias_of` edges for package re-exports in v1.0. @@ -220,12 +220,12 @@ The Python plugin is the v0.1 validating plugin and the reference implementation ### Observe-vs-enforce boundary (Principle 5) -The Python plugin is not Wardline-aware in v1.0. Future Wardline-aware extraction may detect *that* a Wardline annotation is present on a function (e.g., `@validates_shape`), while Wardline's enforcer remains responsible for deciding *whether* the function actually validates what it claims. Clarion must not tag entities with `wardline.groups` / `wardline.annotations` until the manifest advertises that capability and the extractor emits the corresponding signals. +The Python plugin is not Wardline-aware in v1.0. Future Wardline-aware extraction may detect *that* a Wardline annotation is present on a function (e.g., `@validates_shape`), while Wardline's enforcer remains responsible for deciding *whether* the function actually validates what it claims. Loomweave must not tag entities with `wardline.groups` / `wardline.annotations` until the manifest advertises that capability and the extractor emits the corresponding signals. This boundary is preserved by: -- Clarion not redefining Wardline's decorator vocabulary in v1.0 -- Clarion's plugin keeping `wardline_aware = false` until it emits usable Wardline-derived signals -- `CLA-FACT-TIER-SUBSYSTEM-MIXING` (structural observation) being core-emitted (uses clustering) — Clarion is flagging that tiers disagree; Wardline would be the tool that decides *which* tier is correct +- Loomweave not redefining Wardline's decorator vocabulary in v1.0 +- Loomweave's plugin keeping `wardline_aware = false` until it emits usable Wardline-derived signals +- `LMWV-FACT-TIER-SUBSYSTEM-MIXING` (structural observation) being core-emitted (uses clustering) — Loomweave is flagging that tiers disagree; Wardline would be the tool that decides *which* tier is correct --- @@ -235,7 +235,7 @@ This boundary is preserved by: ### Property-graph generic model -Clarion's data model is a property graph with three record types: `Entity`, `Edge`, `Finding`. Plugin-defined strings in the `kind` field keep the core language-agnostic. +Loomweave's data model is a property graph with three record types: `Entity`, `Edge`, `Finding`. Plugin-defined strings in the `kind` field keep the core language-agnostic. ```mermaid erDiagram @@ -279,7 +279,7 @@ erDiagram } Finding { FindingId id PK - string rule_id "CLA-*, WL-*, COV-*, SEC-*" + string rule_id "LMWV-*, WL-*, COV-*, SEC-*" FindingKind kind "Defect/Fact/Classification/Metric/Suggestion" string severity "internal: INFO/WARN/ERROR/CRITICAL/NONE" float confidence "optional" @@ -301,7 +301,7 @@ erDiagram ### Entity -Every addressable thing in Clarion's catalog is an `Entity`: functions, classes, modules, packages, subsystems, guidance sheets, files. The kind is a plugin-defined string; the core does not enum it. +Every addressable thing in Loomweave's catalog is an `Entity`: functions, classes, modules, packages, subsystems, guidance sheets, files. The kind is a plugin-defined string; the core does not enum it. Key invariants: - `id` is stable across file moves that don't change the canonical qualified name (REQ-CATALOG-06). @@ -325,9 +325,9 @@ A structured claim-with-evidence — Principle 4. One record shape covers five k - **Metric** — quantitative measurement - **Suggestion** — non-enforced recommendation -Findings carry `supports` / `supported_by` chains for cross-tool reference: Clarion's `CLA-FACT-TIER-SUBSYSTEM-MIXING` may be `supported_by` a Wardline `WL-TIER-INCONSISTENT` finding on one of the members. +Findings carry `supports` / `supported_by` chains for cross-tool reference: Loomweave's `LMWV-FACT-TIER-SUBSYSTEM-MIXING` may be `supported_by` a Wardline `WL-TIER-INCONSISTENT` finding on one of the members. -Status vocabulary is **Clarion-internal** (`open | acknowledged | suppressed | promoted_to_issue`); Filigree has its own vocabulary (`open | acknowledged | fixed | false_positive | unseen_in_latest`). The mapping is bidirectional (see §9). +Status vocabulary is **Loomweave-internal** (`open | acknowledged | suppressed | promoted_to_issue`); Filigree has its own vocabulary (`open | acknowledged | fixed | false_positive | unseen_in_latest`). The mapping is bidirectional (see §9). ### Entity Briefing @@ -357,21 +357,21 @@ Core-emitted (§6 Phase 3). Properties include `cluster_algorithm` (`leiden`), ` ### Identity reconciliation -Three independent identity schemes coexist across Clarion and Wardline: +Three independent identity schemes coexist across Loomweave and Wardline: | Scheme | Example | Owner | |---|---|---| -| Clarion `EntityId` | `python:class:auth.tokens::TokenManager` | Clarion | +| Loomweave `EntityId` | `python:class:auth.tokens::TokenManager` | Loomweave | | Wardline `qualname` | `TokenManager.verify` | Wardline's `FingerprintEntry` | | Wardline exception-register `location` | `src/wardline/scanner/engine.py::ScanEngine._scan_file` | `wardline.exceptions.json` | -None are byte-equal for the same underlying symbol. Clarion maintains the translation layer — not a centralised identity service (that would violate the Loom federation axiom). Each reconciliation direction uses explicit mechanics: +None are byte-equal for the same underlying symbol. Loomweave maintains the translation layer — not a centralised identity service (that would violate the Weft federation axiom). Each reconciliation direction uses explicit mechanics: -- **Wardline fingerprint → Clarion**. For each `FingerprintEntry`, Clarion computes `(file_path, qualname) → EntityId` via Wardline's `module_file_map` (available at scan time from `ScanContext`). The reverse mapping is stored on the entity as `wardline_qualname`. -- **Wardline exception → Clarion**. The `location` string is parsed (`split("::", 1)` yields `{file_path, qualname}`); same mapping rule applies. Unresolvable entries emit `CLA-INFRA-WARDLINE-EXCEPTION-UNRESOLVED`. -- **SARIF → Clarion** (in the translator path). SARIF's `artifactLocation.uri` + `logicalLocations[].fullyQualifiedName` combine to produce an `EntityId`; unresolved results carry `metadata.clarion.unresolved: true`. +- **Wardline fingerprint → Loomweave**. For each `FingerprintEntry`, Loomweave computes `(file_path, qualname) → EntityId` via Wardline's `module_file_map` (available at scan time from `ScanContext`). The reverse mapping is stored on the entity as `wardline_qualname`. +- **Wardline exception → Loomweave**. The `location` string is parsed (`split("::", 1)` yields `{file_path, qualname}`); same mapping rule applies. Unresolvable entries emit `LMWV-INFRA-WARDLINE-EXCEPTION-UNRESOLVED`. +- **SARIF → Loomweave** (in the translator path). SARIF's `artifactLocation.uri` + `logicalLocations[].fullyQualifiedName` combine to produce an `EntityId`; unresolved results carry `metadata.loomweave.unresolved: true`. -The `GET /api/v1/entities/resolve` endpoint (§9) exposes this translation layer as a public API so every sibling doesn't re-implement it. Wardline keeps its scheme; Clarion produces the join. +The `GET /api/v1/entities/resolve` endpoint (§9) exposes this translation layer as a public API so every sibling doesn't re-implement it. Wardline keeps its scheme; Loomweave produces the join. --- @@ -381,18 +381,18 @@ The `GET /api/v1/entities/resolve` endpoint (§9) exposes this translation layer ### SQLite with writer-actor concurrency -Clarion v0.1 uses SQLite in WAL mode as its sole persistence layer. The key decisions: +Loomweave v0.1 uses SQLite in WAL mode as its sole persistence layer. The key decisions: - **Single writer, multiple readers**: one writable connection lives in a dedicated tokio task (the *writer actor*); all mutations route through a bounded `mpsc::Sender`. -- **Per-N-files transactions**: `clarion analyze` commits on a rolling boundary (default N=50, configurable). A long single transaction would pin the WAL and starve readers. +- **Per-N-files transactions**: `loomweave analyze` commits on a rolling boundary (default N=50, configurable). A long single transaction would pin the WAL and starve readers. - **Read pool**: `deadpool-sqlite` with default 16 read-only connections for plugin processes, MCP handlers, HTTP endpoints, and the markdown renderer. WAL lets them read the committed snapshot without blocking writers. ```mermaid flowchart LR subgraph Writers["Write-path sources"] direction TB - Analyze["clarion analyze
(entity/edge commits)"] - ConsultWrites["clarion serve
(summary-cache writes,
session state)"] + Analyze["loomweave analyze
(entity/edge commits)"] + ConsultWrites["loomweave serve
(summary-cache writes,
session state)"] end subgraph WriterActor["Writer actor (tokio task)"] @@ -409,7 +409,7 @@ flowchart LR Renderer["Markdown renderer"] end - DB[("clarion.db (WAL mode)
journal_mode=WAL
synchronous=NORMAL
busy_timeout=5000ms")] + DB[("loomweave.db (WAL mode)
journal_mode=WAL
synchronous=NORMAL
busy_timeout=5000ms")] Writers -->|"WriteOp"| Mpsc Mpsc --> SoleConn @@ -425,36 +425,36 @@ flowchart LR **Why not a single giant transaction**: Long transactions pin the WAL, prevent checkpoints from completing, and produce unbounded WAL growth. Per-batch transactions are the industry-standard posture for this workload. -**Writer-actor vs. shadow-DB**. The writer-actor model is the v0.1 default. A shadow-DB alternative (`clarion analyze --shadow-db` writes to `.clarion/clarion.db.new`, atomic-renames on completion) is available for users wanting zero-stale reads from a concurrent `clarion serve` during long analyze runs. See ADR-011. +**Writer-actor vs. shadow-DB**. The writer-actor model is the v0.1 default. A shadow-DB alternative (`loomweave analyze --shadow-db` writes to `.loomweave/loomweave.db.new`, atomic-renames on completion) is available for users wanting zero-stale reads from a concurrent `loomweave serve` during long analyze runs. See ADR-011. ### Crash safety SQLite WAL + writer-actor transactions + explicit `PRAGMA synchronous=NORMAL` give crash-safe storage semantics: a SIGKILL during analyze must not corrupt -`.clarion/clarion.db`, and committed rows survive. Per ADR-041, v1.x -`clarion analyze --resume ` reopens the existing run id and re-walks +`.loomweave/loomweave.db`, and committed rows survive. Per ADR-041, v1.x +`loomweave analyze --resume ` reopens the existing run id and re-walks idempotently; it does not read `checkpoints.jsonl` or continue from a phase/file checkpoint. ### Git-friendly storage -`.clarion/clarion.db` is committable to git (NFR-OPS-03). SQLite files diff poorly — Clarion ships two features to handle this: +`.loomweave/loomweave.db` is committable to git (NFR-OPS-03). SQLite files diff poorly — Loomweave ships two features to handle this: -1. **Textual export**: `clarion db export --textual ` produces deterministic JSON-lines dumps of entities, edges, guidance, findings. Sorted by id / (kind, from, to) so a one-entity change produces a one-line diff. Summary cache is excluded (rebuilds cheaply on next run). +1. **Textual export**: `loomweave db export --textual ` produces deterministic JSON-lines dumps of entities, edges, guidance, findings. Sorted by id / (kind, from, to) so a one-entity change produces a one-line diff. Summary cache is excluded (rebuilds cheaply on next run). -2. **Merge helper**: `clarion db merge-helper ` acts as a git merge driver, producing a deterministic merge: union of entities / edges / findings (last-writer-wins on `updated_at`), guidance conflicts surfaced with a `CONFLICT` marker per affected sheet, summary cache cleared. Registration via `.gitattributes` + `.git/config` (see detailed-design §4 for exact config). +2. **Merge helper**: `loomweave db merge-helper ` acts as a git merge driver, producing a deterministic merge: union of entities / edges / findings (last-writer-wins on `updated_at`), guidance conflicts surfaced with a `CONFLICT` marker per affected sheet, summary cache cleared. Registration via `.gitattributes` + `.git/config` (see detailed-design §4 for exact config). ### File layout ``` -/.clarion/ - clarion.db # main store (plus WAL files beside it) +/.loomweave/ + loomweave.db # main store (plus WAL files beside it) config.json # schema version, last run IDs - clarion.log # process log + loomweave.log # process log runs// # per-run artefacts (config snapshot, log.jsonl, stats.json, partial.json) .gitignore # default-excludes runs/*/log.jsonl -~/.config/clarion/ +~/.config/loomweave/ providers.toml # API keys, model tier mappings plugins.toml # plugin registry defaults.yaml # user-level policy overrides @@ -469,7 +469,7 @@ For the elspeth target (~1,100 files, ~100-200k code entities, ~500k-1M edges, h - **Raw source code** — stored by reference; plugins read files on demand. - **Compiled ASTs** — plugins regenerate per session; cheaper than keeping an opaque blob. - **Raw LLM request/response bodies** — logged to `runs//log.jsonl` for audit (default git-excluded; NFR-SEC-05). -- **Filigree issue content** — Clarion stores association IDs; Filigree is authoritative for issue data. +- **Filigree issue content** — Loomweave stores association IDs; Filigree is authoritative for issue data. --- @@ -479,21 +479,21 @@ For the elspeth target (~1,100 files, ~100-200k code entities, ~500k-1M edges, h ### Purpose -The policy engine decides, per unit of LLM work: whether to do it, which model, when (batch or on-demand), and how much it can cost. It reads `clarion.yaml` merged with user defaults and CLI flags, then dispatches work to the LLM orchestrator. +The policy engine decides, per unit of LLM work: whether to do it, which model, when (batch or on-demand), and how much it can cost. It reads `loomweave.yaml` merged with user defaults and CLI flags, then dispatches work to the LLM orchestrator. ### Config hierarchy ``` -~/.config/clarion/defaults.yaml (user-level) +~/.config/loomweave/defaults.yaml (user-level) └──┐ - ├─► merged → /clarion.yaml + ├─► merged → /loomweave.yaml │ └──┐ │ ├─► merged → CLI flags (highest precedence) │ │ │ │ │ └─► effective config for this run ``` -Three-tier merge order (user defaults → project → CLI) matches user expectations. Every `clarion analyze` run snapshots the effective config into `runs//config.yaml` for audit. +Three-tier merge order (user defaults → project → CLI) matches user expectations. Every `loomweave analyze` run snapshots the effective config into `runs//config.yaml` for audit. ### Per-level policy @@ -514,7 +514,7 @@ Named profiles compress the per-level configuration into one switch: > **Deferred to v1.1** (`NFR-COST-01` / `NFR-COST-03`, per [ADR-030](../adr/ADR-030-on-demand-summary-scope.md) + [Sprint 2 scope amendment §4](../../implementation/sprint-2/scope-amendment-2026-05.md)). The dry-run preflight estimate and batched-run budget watcher described below belong to the batched pipeline (Phases 4–6), which v1.0 does not run — v1.0 reaches LLM cost only lazily through the on-demand MCP `summary` tool, bounded by a **session-local token ceiling** (`BudgetLedger`). The full preflight + on-exceed machinery lands when the batched pipeline does. -Every `clarion analyze` does a **dry-run estimate first** (default `dry_run_first: true`) — counts entities per level, applies per-tier pricing, prints the estimate, confirms with the user. At run time, a budget watcher tracks running totals; `on_exceed: stop` halts dispatch and writes `runs//partial.json`; `on_exceed: warn` logs and continues. +Every `loomweave analyze` does a **dry-run estimate first** (default `dry_run_first: true`) — counts entities per level, applies per-tier pricing, prints the estimate, confirms with the user. At run time, a budget watcher tracks running totals; `on_exceed: stop` halts dispatch and writes `runs//partial.json`; `on_exceed: warn` logs and continues. Estimate accuracy: target ±50% (NFR-COST-03). The known error sources are subsystem synthesis cost (varies with cluster sizes) and prompt-cache hit rates (which depend on guidance composition). ±50% is tight enough to be useful, loose enough to be achievable without full per-call simulation. @@ -554,15 +554,15 @@ Cache key: `(entity_id, content_hash, prompt_template_id, model_tier, guidance_f 2. **Model-identity drift**. A tier name (`sonnet`) mapping to a new concrete model. The cache row already stores the concrete model; the tier resolver compares on write and treats a mismatch as a miss. No special handling needed. -3. **Guidance-worldview drift**. Guidance sheet's text is unchanged but underlying assumptions have gone stale. No automated signal; surfaced via staleness review (§7) and git-churn-based findings (`CLA-FACT-GUIDANCE-CHURN-STALE`). +3. **Guidance-worldview drift**. Guidance sheet's text is unchanged but underlying assumptions have gone stale. No automated signal; surfaced via staleness review (§7) and git-churn-based findings (`LMWV-FACT-GUIDANCE-CHURN-STALE`). **TTL backstop**. Cache rows older than `max_age_days` (default 180) are invalidated unconditionally on next query. Bounds silently-stale briefings. -**Churn-triggered eager invalidation**. When `CLA-FACT-GUIDANCE-CHURN-STALE` fires for a guidance sheet, cache rows whose `guidance_fingerprint` includes that sheet invalidate eagerly (not at TTL). Operators see stale-guidance findings *and* feel cost pressure to act. +**Churn-triggered eager invalidation**. When `LMWV-FACT-GUIDANCE-CHURN-STALE` fires for a guidance sheet, cache rows whose `guidance_fingerprint` includes that sheet invalidate eagerly (not at TTL). Operators see stale-guidance findings *and* feel cost pressure to act. ### Observability -Per-run `stats.json` records total cost, per-level breakdown, per-model breakdown, cache hit rate, phase durations. Per-entity provenance in the `summary_cache` row records what was spent to produce each summary. `cost_report(since)` MCP tool returns structured cost data. Budget events emit `CLA-INFRA-BUDGET-WARNING` / `CLA-INFRA-BUDGET-EXCEEDED` findings. +Per-run `stats.json` records total cost, per-level breakdown, per-model breakdown, cache hit rate, phase durations. Per-entity provenance in the `summary_cache` row records what was spent to produce each summary. `cost_report(since)` MCP tool returns structured cost data. Budget events emit `LMWV-INFRA-BUDGET-WARNING` / `LMWV-INFRA-BUDGET-EXCEEDED` findings. --- @@ -599,7 +599,7 @@ flowchart TB ``` Per ADR-041, v1.x phase transitions do not write a durable checkpoint file. -`clarion analyze --resume ` reuses the run id, re-walks safely, and +`loomweave analyze --resume ` reuses the run id, re-walks safely, and relies on existing caches where they independently apply. A future checkpoint ADR may reintroduce phase/file skipping with explicit provider-call accounting. @@ -623,36 +623,36 @@ Every failure category emits a structured finding: | Failure | Finding | Recovery | |---|---|---| -| Plugin parse error on file | `CLA-PY-PARSE-ERROR` | skip file, continue | -| Plugin timeout (default 30s) | `CLA-PY-TIMEOUT` | skip file, continue | -| Plugin process crash | `CLA-INFRA-PLUGIN-CRASH` | core restarts plugin, resume at next file | +| Plugin parse error on file | `LMWV-PY-PARSE-ERROR` | skip file, continue | +| Plugin timeout (default 30s) | `LMWV-PY-TIMEOUT` | skip file, continue | +| Plugin process crash | `LMWV-INFRA-PLUGIN-CRASH` | core restarts plugin, resume at next file | | LLM rate limit | (no finding; retry) | exponential backoff + jitter, up to `max_retries` | -| LLM non-transient error | `CLA-INFRA-LLM-ERROR` | skip entity, continue | -| Budget exceeded (`warn`) | `CLA-INFRA-BUDGET-WARNING` | log, continue | -| Budget exceeded (`stop`) | `CLA-INFRA-BUDGET-EXCEEDED` | halt dispatch, write partial manifest | -| Plugin crashes >10% of files | `CLA-INFRA-ANALYSIS-ABORTED` | abort, mark run failed | -| Plugin crashes >3 in 60s | `CLA-INFRA-PLUGIN-DISABLED-CRASH-LOOP` | circuit-breaker disables plugin for the run | +| LLM non-transient error | `LMWV-INFRA-LLM-ERROR` | skip entity, continue | +| Budget exceeded (`warn`) | `LMWV-INFRA-BUDGET-WARNING` | log, continue | +| Budget exceeded (`stop`) | `LMWV-INFRA-BUDGET-EXCEEDED` | halt dispatch, write partial manifest | +| Plugin crashes >10% of files | `LMWV-INFRA-ANALYSIS-ABORTED` | abort, mark run failed | +| Plugin crashes >3 in 60s | `LMWV-INFRA-PLUGIN-DISABLED-CRASH-LOOP` | circuit-breaker disables plugin for the run | No silent fallbacks. Every failure has a finding. Aggregate failure counts appear in `runs//stats.json`. ### Entity-set diff (deletion detection) -At Phase 7, Clarion compares the current run's entity IDs against the prior run's (read from `runs//stats.json`). For each entity present before and absent now: +At Phase 7, Loomweave compares the current run's entity IDs against the prior run's (read from `runs//stats.json`). For each entity present before and absent now: -- Emit `CLA-FACT-ENTITY-DELETED` against a synthetic deletion marker (`core:deleted:{former_entity_id}`). +- Emit `LMWV-FACT-ENTITY-DELETED` against a synthetic deletion marker (`core:deleted:{former_entity_id}`). - Surface the deletion via Filigree so issues carrying the orphan ID can be triaged. -- Guidance sheets with explicit-entity `match_rules` pointing at the deleted ID emit `CLA-FACT-GUIDANCE-ORPHAN`. +- Guidance sheets with explicit-entity `match_rules` pointing at the deleted ID emit `LMWV-FACT-GUIDANCE-ORPHAN`. - Invalidate summary cache rows for the deleted entity. Silent deletion is the no-go mode — before Rev 4, this path was silent. Two findings now guarantee the transition is visible (REQ-ANALYZE-04). ### Phase-7 structural findings -These combine signals Clarion uniquely holds — clusters from Phase 3, Wardline tier declarations, and prior-run state — into findings no sibling tool can compute alone: +These combine signals Loomweave uniquely holds — clusters from Phase 3, Wardline tier declarations, and prior-run state — into findings no sibling tool can compute alone: -- **`CLA-FACT-TIER-SUBSYSTEM-MIXING`** (WARN, heuristic). A subsystem has members declared across disagreeing Wardline tiers. Either a misclassification Wardline can't see or a latent tier boundary worth naming. Threshold: `min_outlier_count: 2` AND `min_outlier_fraction: 0.1` (configurable). -- **`CLA-FACT-SUBSYSTEM-TIER-UNANIMOUS`** (INFO, deterministic). Subsystem members share a uniform declared tier. Positive signal for tier-consistency reports. -- **`CLA-FACT-ENTITY-DELETED`** (INFO, deterministic). See above. +- **`LMWV-FACT-TIER-SUBSYSTEM-MIXING`** (WARN, heuristic). A subsystem has members declared across disagreeing Wardline tiers. Either a misclassification Wardline can't see or a latent tier boundary worth naming. Threshold: `min_outlier_count: 2` AND `min_outlier_fraction: 0.1` (configurable). +- **`LMWV-FACT-SUBSYSTEM-TIER-UNANIMOUS`** (INFO, deterministic). Subsystem members share a uniform declared tier. Positive signal for tier-consistency reports. +- **`LMWV-FACT-ENTITY-DELETED`** (INFO, deterministic). See above. These belong in Phase 7 (not the plugin's Phase 1 emission) because they depend on clustering output and prior-run state — core-side concerns. Emitting them from the plugin would require the plugin to know about subsystems and prior runs (Principle 3 violation). @@ -668,7 +668,7 @@ Institutional knowledge — what code means in context, known subtleties, review ### Composition algorithm -Given `(entity_id, query_type, model_tier)`, Clarion composes the applicable guidance: +Given `(entity_id, query_type, model_tier)`, Loomweave composes the applicable guidance: ```mermaid flowchart TB @@ -689,19 +689,19 @@ flowchart TB | Surface | Workflow | |---|---| -| CLI | `clarion guidance create --match path=src/auth/** --scope-level subsystem` | -| CLI | `clarion guidance edit ` / `show` / `delete` | -| CLI | `clarion guidance list [--for-entity ]` / `--stale` / `--expired` | +| CLI | `loomweave guidance create --match path=src/auth/** --scope-level subsystem` | +| CLI | `loomweave guidance edit ` / `show` / `delete` | +| CLI | `loomweave guidance list [--for-entity ]` / `--stale` / `--expired` | | MCP | `propose_guidance(entity_id, content, match_rules)` — produces a Filigree **observation**, not a sheet | -| CLI | `clarion guidance promote ` — promotes observation to sheet | -| Wardline | Automatic on every `clarion analyze` with `wardline.yaml` present | -| Export / import | `clarion guidance export --to ` / `clarion guidance import ` | +| CLI | `loomweave guidance promote ` — promotes observation to sheet | +| Wardline | Automatic on every `loomweave analyze` with `wardline.yaml` present | +| Export / import | `loomweave guidance export --to ` / `loomweave guidance import ` | The `propose_guidance` → observation → explicit promote flow is the v0.1 defence against guidance-poisoning via adversarial LLM output (`NFR-SEC-02`). A single compromised LLM call cannot poison every future prompt because promotion requires operator action. ### Wardline-derived guidance -On every `clarion analyze` run with `wardline.yaml` present: +On every `loomweave analyze` run with `wardline.yaml` present: - **Per declared tier assignment** → module-scope sheet: "This module contains declared Tier-N entities (list). Summaries should reflect Tier-N posture." - **Per boundary contract** → subsystem-scope sheet: "Data crossing boundary `` carries Tier N; downstream users must not …" @@ -709,7 +709,7 @@ On every `clarion analyze` run with `wardline.yaml` present: All auto-generated sheets tagged `provenance: wardline_derived`, `pinned: true`. Regenerated every analyse; user-edited overrides preserved by ID and marked `provenance: wardline_derived_overridden`. -Drift between `wardline.yaml` and derived guidance (Wardline runs at commit cadence; Clarion at batch cadence) surfaces as `CLA-FACT-GUIDANCE-STALE` finding. +Drift between `wardline.yaml` and derived guidance (Wardline runs at commit cadence; Loomweave at batch cadence) surfaces as `LMWV-FACT-GUIDANCE-STALE` finding. ### Staleness signals tied to code churn @@ -718,26 +718,26 @@ Guidance is an accumulating stock with no intrinsic quality signal — especiall On every analyze, for each guidance sheet: - Compute aggregate `git_churn_count` delta over matched entities since `authored_at` or `reviewed_at`. -- Threshold exceeded (default 50 commits; 20 for `pinned: true` sheets) → emit `CLA-FACT-GUIDANCE-CHURN-STALE` with `confidence: 0.7, confidence_basis: heuristic`. +- Threshold exceeded (default 50 commits; 20 for `pinned: true` sheets) → emit `LMWV-FACT-GUIDANCE-CHURN-STALE` with `confidence: 0.7, confidence_basis: heuristic`. - Asymmetric threshold is deliberate: pinned sheets shape LLM output most, so their staleness matters most. Auto-expiry is NOT the design — the stale signal pushes operators toward review; the decision stays with humans. ### Triage-state feedback into briefings -Filigree's finding-lifecycle state (suppressed, acknowledged) is institutional knowledge in the same shape as guidance. When rendering a briefing for an entity, Clarion queries Filigree for findings matching the entity: +Filigree's finding-lifecycle state (suppressed, acknowledged) is institutional knowledge in the same shape as guidance. When rendering a briefing for an entity, Loomweave queries Filigree for findings matching the entity: - If the briefing's `notes` field is empty and there are ≤3 suppressed/acknowledged findings with reasons → render inline as `"Operator-acknowledged: (Filigree finding )"` per line. - If >3 or notes is already populated → synthesise one `RiskItem` with `tag: "operator-acknowledged"`, `severity: INFO`, `description: "N findings on this entity have been triaged by operators — see Filigree for suppression reasons."` The guidance fingerprint (§5 Caching) incorporates the set of acknowledged finding IDs so cache invalidates when triage state changes. -This is a rendering change on an existing data path (the consult tool already fetches findings for entity pages). No new storage, no new sync mechanism. Read-only from Clarion's side. +This is a rendering change on an existing data path (the consult tool already fetches findings for entity pages). No new storage, no new sync mechanism. Read-only from Loomweave's side. ### Lifecycle -- **Expiry**: expired sheets excluded from composition but kept in store (`clarion guidance list --expired`); `CLA-FACT-GUIDANCE-EXPIRED` per run. -- **Review cadence**: optional `reviewed_at`; `clarion guidance list --stale` for sheets not touched in N days. +- **Expiry**: expired sheets excluded from composition but kept in store (`loomweave guidance list --expired`); `LMWV-FACT-GUIDANCE-EXPIRED` per run. +- **Review cadence**: optional `reviewed_at`; `loomweave guidance list --stale` for sheets not touched in N days. - **Change tracking**: edit → new `content_hash` + updated `updated_at` → dependent summary cache entries invalidate at next query. - **Conflict**: sheets are additive; inner sheets override outer by scope-rank ordering; all sheets presented to the LLM in scope-rank order with level labels so intended overrides are visible. @@ -769,7 +769,7 @@ The scope lens shapes neighbour queries without changing their signatures: ### Tool catalogue by category -> **The MCP server registers 35 tools** (`clarion-mcp/src/lib.rs::list_tools`). The categories below name the actual shipped tools, not the original aspirational cursor-based catalogue. The earlier 8-tool subset has been superseded as WP4/WP5 navigation, catalog filters, and analyze-control tools landed; the tools are **stateless and id-based** (no `goto`/`back`/`zoom` cursor session), so an agent passes an `EntityId` (obtained from `find_entity` / `entity_at`) into each call. Tools that produce LLM summaries remain on-demand per [ADR-030](../adr/ADR-030-on-demand-summary-scope.md). +> **The MCP server registers 35 tools** (`loomweave-mcp/src/lib.rs::list_tools`). The categories below name the actual shipped tools, not the original aspirational cursor-based catalogue. The earlier 8-tool subset has been superseded as WP4/WP5 navigation, catalog filters, and analyze-control tools landed; the tools are **stateless and id-based** (no `goto`/`back`/`zoom` cursor session), so an agent passes an `EntityId` (obtained from `find_entity` / `entity_at`) into each call. Tools that produce LLM summaries remain on-demand per [ADR-030](../adr/ADR-030-on-demand-summary-scope.md). **Location & lookup**: `entity_at(file, line)`, `find_entity(pattern)`, `source_for_entity(id)`, `orientation_pack(...)`, `project_status()` @@ -791,7 +791,7 @@ Every common explore-agent question is a pre-computed shortcut. **Most ship toda `find_entry_points(scope?)`, `find_http_routes(scope?)`, `find_data_models(scope?)`, `find_tests(scope?)`, `find_deprecations(scope?)`, `find_todos(scope?)`, `find_circular_imports(scope?)`, `find_coupling_hotspots(scope?)`, `find_dead_code(scope?)`, `recently_changed(since?, scope?)`, `high_churn(limit?, scope?)`, `what_tests_this(id)` -> **`find_dead_code(scope?)`** is registered (WS5b): a conservative reachability query that flags entities unreachable from the root set (entry points ∪ exported API ∪ tests ∪ HTTP routes ∪ CLI commands ∪ data models) over call+import edges. It fails toward "live" — counts all edge confidence tiers, honours reflection/dynamic-dispatch barrier tags, excludes framework-magic kinds, and emits heuristic `CLA-FACT-DEAD-CODE-CANDIDATE` results (confidence < 1). The root categorisation tags it consumes are **not emitted by any active plugin today**, so it returns an honest signal-unavailable result (never a flood of false positives) until the root-tag emission pipeline lands (tracked follow-up). Emitting those tags during analyze is the trigger that makes it light up. +> **`find_dead_code(scope?)`** is registered (WS5b): a conservative reachability query that flags entities unreachable from the root set (entry points ∪ exported API ∪ tests ∪ HTTP routes ∪ CLI commands ∪ data models) over call+import edges. It fails toward "live" — counts all edge confidence tiers, honours reflection/dynamic-dispatch barrier tags, excludes framework-magic kinds, and emits heuristic `LMWV-FACT-DEAD-CODE-CANDIDATE` results (confidence < 1). The root categorisation tags it consumes are **not emitted by any active plugin today**, so it returns an honest signal-unavailable result (never a flood of false positives) until the root-tag emission pipeline lands (tracked follow-up). Emitting those tags during analyze is the trigger that makes it light up. > > **Three shortcuts remain deferred to a later release**: `find_cli_commands`, `find_config_loaders`, `find_fixtures` are not yet registered in `list_tools`. @@ -831,7 +831,7 @@ Budget targets per tool: | `source` | paginated if >2,000 tokens | | `search_*` | ≤10 results | -Configurable per-session via `set_budget(tool, max_tokens)`. Unbounded responses re-introduce the context-pollution problem Clarion exists to solve (Principle 2). +Configurable per-session via `set_budget(tool, max_tokens)`. Unbounded responses re-introduce the context-pollution problem Loomweave exists to solve (Principle 2). ### Consent gates @@ -841,8 +841,8 @@ Write-effect tools (`emit_observation`, `promote_observation`, `propose_guidance - Created on MCP `initialize` (≤100ms, NFR-PERF-02). - Default idle timeout: 1 hour. -- State persisted to `.clarion/sessions/.json` for reconnection. -- Admin surface: `clarion sessions list` / `clarion sessions close `. +- State persisted to `.loomweave/sessions/.json` for reconnection. +- Admin surface: `loomweave sessions list` / `loomweave sessions close `. --- @@ -852,15 +852,15 @@ Write-effect tools (`emit_observation`, `promote_observation`, `propose_guidance ### Filigree -**Posture**: Clarion owns file registry and code entities; Filigree owns workflow, issues, observations, findings lifecycle. Integration is via Filigree's native `POST /api/v1/scan-results` intake plus (when supported) a pluggable `RegistryProtocol` where Filigree consults Clarion for file resolution. +**Posture**: Loomweave owns file registry and code entities; Filigree owns workflow, issues, observations, findings lifecycle. Integration is via Filigree's native `POST /api/v1/scan-results` intake plus (when supported) a pluggable `RegistryProtocol` where Filigree consults Loomweave for file resolution. #### Wire format -Clarion POSTs to `POST /api/v1/scan-results`: +Loomweave POSTs to `POST /api/v1/scan-results`: ```json { - "scan_source": "clarion", + "scan_source": "loomweave", "scan_run_id": "run-2026-04-17-153002", "mark_unseen": true, "create_observations": false, @@ -868,7 +868,7 @@ Clarion POSTs to `POST /api/v1/scan-results`: "findings": [ { "path": "src/auth/tokens.py", - "rule_id": "CLA-PY-STRUCTURE-001", + "rule_id": "LMWV-PY-STRUCTURE-001", "message": "Circular import detected between auth.tokens and auth.sessions", "severity": "medium", "line_start": 12, @@ -878,7 +878,7 @@ Clarion POSTs to `POST /api/v1/scan-results`: "kind": "defect", "confidence": 0.95, "confidence_basis": "ast_match", - "clarion": { + "loomweave": { "entity_id": "python:class:auth.tokens::TokenManager", "related_entities": ["python:class:auth.sessions::SessionStore"], "internal_severity": "WARN", @@ -892,57 +892,57 @@ Clarion POSTs to `POST /api/v1/scan-results`: Key contract properties: -- **Extension slot is `metadata`**, not `properties`. Top-level keys outside the enumerated set are silently dropped by Filigree. Clarion's richer fields nest under `metadata.clarion.*`. +- **Extension slot is `metadata`**, not `properties`. Top-level keys outside the enumerated set are silently dropped by Filigree. Loomweave's richer fields nest under `metadata.loomweave.*`. - **Line fields are `line_start` + `line_end`** (not a single `line`). -- **Severity is lowercase `{critical, high, medium, low, info}`** on the wire. Unknown values coerce to `info` and surface in `response.warnings[]` — Clarion inspects `warnings[]` on every POST (REQ-INTEG-FILIGREE-01). -- **`scan_run_id` lifecycle**: Clarion creates the run at Phase 0 via `create_scan_run` MCP tool; intermediate posts use `complete_scan_run=false`; final post uses `complete_scan_run=true`. Resume (`--resume`) reuses the same `run_id` and posts with `mark_unseen=false`. +- **Severity is lowercase `{critical, high, medium, low, info}`** on the wire. Unknown values coerce to `info` and surface in `response.warnings[]` — Loomweave inspects `warnings[]` on every POST (REQ-INTEG-FILIGREE-01). +- **`scan_run_id` lifecycle**: Loomweave creates the run at Phase 0 via `create_scan_run` MCP tool; intermediate posts use `complete_scan_run=false`; final post uses `complete_scan_run=true`. Resume (`--resume`) reuses the same `run_id` and posts with `mark_unseen=false`. #### Severity mapping -| Clarion internal | Filigree wire | Reverse (read-back) | +| Loomweave internal | Filigree wire | Reverse (read-back) | |---|---|---| | `CRITICAL` | `critical` | `CRITICAL` | | `ERROR` | `high` | `ERROR` | | `WARN` | `medium` | `WARN` | | `INFO` | `info` | `INFO` | -| `NONE` (facts) | `info` (with `metadata.clarion.kind = "fact"`) | `NONE` | +| `NONE` (facts) | `info` (with `metadata.loomweave.kind = "fact"`) | `NONE` | -Clarion's internal value is preserved in `metadata.clarion.internal_severity` for lossless round-trip. Read-back consults that field first; falls back to `severity` only if absent (e.g., for findings from Wardline that don't set Clarion-specific metadata). +Loomweave's internal value is preserved in `metadata.loomweave.internal_severity` for lossless round-trip. Read-back consults that field first; falls back to `severity` only if absent (e.g., for findings from Wardline that don't set Loomweave-specific metadata). #### Dedup policy -Filigree dedups by `(file_id, scan_source, rule_id, coalesce(line_start, -1))`. Entities moving within a file produce two findings — v0.1 workaround is `mark_unseen=true` (old-position findings transition to `unseen_in_latest` on next run). `clarion analyze --prune-unseen` removes stale `unseen_in_latest` findings older than 30 days (configurable). +Filigree dedups by `(file_id, scan_source, rule_id, coalesce(line_start, -1))`. Entities moving within a file produce two findings — v0.1 workaround is `mark_unseen=true` (old-position findings transition to `unseen_in_latest` on next run). `loomweave analyze --prune-unseen` removes stale `unseen_in_latest` findings older than 30 days (configurable). Server-side per-entity dedup (a Filigree feature that supersedes this workaround) is deferred (NG-21). #### Registry-backend consumption -When Filigree ships `registry_backend: clarion`, Clarion serves as Filigree's file registry: Filigree's three auto-create paths (scan-results ingest, observation creation with `file_path`, `trigger_scan`) route through `RegistryProtocol` to Clarion's HTTP read API for `file_id` resolution. +When Filigree ships `registry_backend: loomweave`, Loomweave serves as Filigree's file registry: Filigree's three auto-create paths (scan-results ingest, observation creation with `file_path`, `trigger_scan`) route through `RegistryProtocol` to Loomweave's HTTP read API for `file_id` resolution. -Absent the flag, Clarion operates in **shadow-registry mode**: findings POSTed normally, Filigree auto-creates `file_records` under its native rules, `CLA-INFRA-FILIGREE-SHADOW-REGISTRY` appears in the compat report. Clarion's "owns the file registry" claim downgrades to "owns the entity catalog; Filigree shadows the file mapping" — functional, but the identity story is looser. +Absent the flag, Loomweave operates in **shadow-registry mode**: findings POSTed normally, Filigree auto-creates `file_records` under its native rules, `LMWV-INFRA-FILIGREE-SHADOW-REGISTRY` appears in the compat report. Loomweave's "owns the file registry" claim downgrades to "owns the entity catalog; Filigree shadows the file mapping" — functional, but the identity story is looser. See §11 and `CON-FILIGREE-02`. #### Observation transport -Observations created from `clarion analyze` (auto-generated on Wardline-derived guidance events, etc.) and from `clarion serve` MCP `emit_observation` tool go to Filigree. +Observations created from `loomweave analyze` (auto-generated on Wardline-derived guidance events, etc.) and from `loomweave serve` MCP `emit_observation` tool go to Filigree. -- **v0.1 path** (ADR-016): Clarion spawns `filigree mcp` as a subprocess and uses Filigree's existing MCP `create_observation` tool over stdio. One subprocess per `clarion analyze` invocation; one long-lived subprocess for `clarion serve`. Observation HTTP endpoint deferred to v0.2 per Q1 scope commitment. -- **v0.2 retirement**: Filigree adds `POST /api/v1/observations` with a schema parallel to the MCP tool. Clarion's capability probe detects presence via `HEAD /api/v1/observations`; emit path switches to HTTP and the subprocess-spawn path retires. +- **v0.1 path** (ADR-016): Loomweave spawns `filigree mcp` as a subprocess and uses Filigree's existing MCP `create_observation` tool over stdio. One subprocess per `loomweave analyze` invocation; one long-lived subprocess for `loomweave serve`. Observation HTTP endpoint deferred to v0.2 per Q1 scope commitment. +- **v0.2 retirement**: Filigree adds `POST /api/v1/observations` with a schema parallel to the MCP tool. Loomweave's capability probe detects presence via `HEAD /api/v1/observations`; emit path switches to HTTP and the subprocess-spawn path retires. #### Schema compatibility CI runs a schema-pin test against a tagged Filigree release's `GET /api/files/_schema` output. Pins `valid_severities`, `valid_finding_statuses`, `valid_association_types`, and sort-field lists. Mismatch fails CI (NFR-COMPAT-01). -`scan_source` is free-form server-side (Filigree's `_schema` doesn't enumerate it). Suite-wide reserved values: `clarion`, `wardline`, `cov`, `sec`. +`scan_source` is free-form server-side (Filigree's `_schema` doesn't enumerate it). Suite-wide reserved values: `loomweave`, `wardline`, `cov`, `sec`. #### `metadata` nesting convention -Clarion → `metadata.clarion.*`. Wardline SARIF-translated findings → `metadata.wardline_properties.*`. A future tool (e.g., `cov`) would nest under `metadata.cov.*`. Published in Filigree docs to prevent collisions (v0.1 prerequisite, see §11 "Prerequisites named here"). +Loomweave → `metadata.loomweave.*`. Wardline SARIF-translated findings → `metadata.wardline_properties.*`. A future tool (e.g., `cov`) would nest under `metadata.cov.*`. Published in Filigree docs to prevent collisions (v0.1 prerequisite, see §11 "Prerequisites named here"). ### Wardline -**Posture**: Wardline runs at commit cadence. The long-term vision is Wardline pulling current state from Clarion; the v0.1 reality is narrower because Wardline has no HTTP client yet. Clarion ingests Wardline state from files on disk in v0.1. +**Posture**: Wardline runs at commit cadence. The long-term vision is Wardline pulling current state from Loomweave; the v0.1 reality is narrower because Wardline has no HTTP client yet. Loomweave ingests Wardline state from files on disk in v0.1. #### State-file ingest @@ -952,21 +952,21 @@ Clarion → `metadata.clarion.*`. Wardline SARIF-translated findings → `metada | `wardline.fingerprint.json` | YES | Per-function annotation hash + qualname → `WardlineMeta.annotation_hash`, `wardline_qualname` | | `wardline.exceptions.json` | YES | Active exceptions → entities tagged `wardline.excepted` | | `wardline.sarif.baseline.json` | YES (read-only) | Source for SARIF→Filigree translator | -| Other Wardline state files (compliance, conformance, perimeter, retrospective) | NO (v0.2) | Not yet relevant to Clarion's catalog shape | +| Other Wardline state files (compliance, conformance, perimeter, retrospective) | NO (v0.2) | Not yet relevant to Loomweave's catalog shape | #### Direct REGISTRY import -Clarion's Python plugin imports `wardline.core.registry.REGISTRY` at startup (requires the `wardline` package installed in the plugin's pipx venv). `REGISTRY_VERSION` is pinned at Clarion release time. Skew behaviour (REQ-INTEG-WARDLINE-01 + NFR-COMPAT-02): +Loomweave's Python plugin imports `wardline.core.registry.REGISTRY` at startup (requires the `wardline` package installed in the plugin's pipx venv). `REGISTRY_VERSION` is pinned at Loomweave release time. Skew behaviour (REQ-INTEG-WARDLINE-01 + NFR-COMPAT-02): - Exact match → proceed normally. -- Additive-newer (same major, same or higher minor) → proceed with warning; decorators in the installed REGISTRY not in Clarion's pin detected with `confidence_basis: "clarion_augmentation"`. -- Major-bump or older → fall back to hardcoded registry mirror (`wardline_registry_v.py`); emit `CLA-INFRA-WARDLINE-REGISTRY-MIRRORED`; findings carry `confidence_basis: "mirror_only"`. +- Additive-newer (same major, same or higher minor) → proceed with warning; decorators in the installed REGISTRY not in Loomweave's pin detected with `confidence_basis: "loomweave_augmentation"`. +- Major-bump or older → fall back to hardcoded registry mirror (`wardline_registry_v.py`); emit `LMWV-INFRA-WARDLINE-REGISTRY-MIRRORED`; findings carry `confidence_basis: "mirror_only"`. The YAML/JSON descriptor export of REGISTRY (enabling non-Python plugins) is a Wardline v0.2 prerequisite (see §11 "Prerequisites named here", NG-25). #### SARIF → Filigree translator -`clarion sarif import [--scan-source ]`. A general-purpose feature, not a Wardline-specific bridge — Semgrep, CodeQL, Trivy, and every future SARIF emitter land through the same translator. +`loomweave sarif import [--scan-source ]`. A general-purpose feature, not a Wardline-specific bridge — Semgrep, CodeQL, Trivy, and every future SARIF emitter land through the same translator. Translator behaviour: @@ -977,16 +977,16 @@ Translator behaviour: #### Wardline-sourced flow evolution -- **v0.1**: Wardline emits SARIF to disk; `clarion sarif import wardline.sarif.baseline.json --scan-source wardline` posts to Filigree. Translator owned Clarion-side. +- **v0.1**: Wardline emits SARIF to disk; `loomweave sarif import wardline.sarif.baseline.json --scan-source wardline` posts to Filigree. Translator owned Loomweave-side. - **v0.2+** (NG-20 / ADR-015): Wardline gains a native `POST /api/v1/scan-results` emitter. What moves is *who owns the Wardline-specific mapping* — not whether SARIF translation exists. The translator stays (other SARIF sources remain). ### HTTP Read API -`clarion serve` hosts a read-only HTTP API on `127.0.0.1` (configurable bind). +`loomweave serve` hosts a read-only HTTP API on `127.0.0.1` (configurable bind). #### Endpoints -> **The pinned, authoritative wire surface is [`docs/federation/contracts.md`](../../federation/contracts.md)** (with normative fixtures under `docs/federation/fixtures/`). The illustrative catalogue below is the broader v1.1 *target*; consult contracts.md for what is actually contracted today. The shipped routes (`crates/clarion-cli/src/http_read.rs::router`) are: the [ADR-014](../adr/ADR-014-filigree-registry-backend.md) file-registry subset (`GET /api/v1/files`, `POST /api/v1/files:resolve`, `POST /api/v1/files/batch`), entity caller/callee reads (`GET /api/v1/entities/{id}/callers|callees` + their `:batch-get` POST forms), the [ADR-038](../adr/ADR-038-sei-token-and-signature.md) SEI identity-resolution endpoints (`POST /api/v1/identity/resolve`, `…:batch`, `GET /api/v1/identity/sei/{sei}`, `…/lineage/{sei}`), the [ADR-036](../adr/ADR-036-wardline-taint-fact-store.md) Wardline taint-store routes (`/api/wardline/*`), and `GET /api/v1/_capabilities` (unauthenticated probe) — all under the [ADR-034](../adr/ADR-034-federation-http-read-api-hardening.md) authentication surface. **Still deferred**: the broad `/entities?…` query + `/entities/{id}/{summary,guidance,findings,neighbors}`, `/findings`, `/wardline/declared`, `/state`, `/health`, `/metrics`, and the **multi-scheme `GET /api/v1/entities/resolve` oracle** below. Identity translation today is split across `POST /api/v1/files:resolve` (file scheme) and `POST /api/v1/identity/resolve` (SEI). See [REQ-HTTP-01](requirements.md#req-http-01--read-endpoints-for-entities-findings-wardline-state) and [REQ-HTTP-02](requirements.md#req-http-02--entity-resolution-oracle) for the row-level deferral notices. +> **The pinned, authoritative wire surface is [`docs/federation/contracts.md`](../../federation/contracts.md)** (with normative fixtures under `docs/federation/fixtures/`). The illustrative catalogue below is the broader v1.1 *target*; consult contracts.md for what is actually contracted today. The shipped routes (`crates/loomweave-cli/src/http_read.rs::router`) are: the [ADR-014](../adr/ADR-014-filigree-registry-backend.md) file-registry subset (`GET /api/v1/files`, `POST /api/v1/files:resolve`, `POST /api/v1/files/batch`), entity caller/callee reads (`GET /api/v1/entities/{id}/callers|callees` + their `:batch-get` POST forms), the [ADR-038](../adr/ADR-038-sei-token-and-signature.md) SEI identity-resolution endpoints (`POST /api/v1/identity/resolve`, `…:batch`, `GET /api/v1/identity/sei/{sei}`, `…/lineage/{sei}`), the [ADR-036](../adr/ADR-036-wardline-taint-fact-store.md) Wardline taint-store routes (`/api/wardline/*`), and `GET /api/v1/_capabilities` (unauthenticated probe) — all under the [ADR-034](../adr/ADR-034-federation-http-read-api-hardening.md) authentication surface. **Still deferred**: the broad `/entities?…` query + `/entities/{id}/{summary,guidance,findings,neighbors}`, `/findings`, `/wardline/declared`, `/state`, `/health`, `/metrics`, and the **multi-scheme `GET /api/v1/entities/resolve` oracle** below. Identity translation today is split across `POST /api/v1/files:resolve` (file scheme) and `POST /api/v1/identity/resolve` (SEI). See [REQ-HTTP-01](requirements.md#req-http-01--read-endpoints-for-entities-findings-wardline-state) and [REQ-HTTP-02](requirements.md#req-http-02--entity-resolution-oracle) for the row-level deferral notices. ``` GET /api/v1/entities?file=&kind=&tag= @@ -1009,13 +1009,13 @@ GET /api/v1/metrics # Prometheus-compatible `GET /api/v1/entities/resolve?scheme=&value=` — the identity-translation layer exposed as a public API. Target schemes: `wardline_qualname`, `wardline_exception_location`, `file_path`, `sarif_logical_location`. Response includes `resolution_confidence` (`exact | heuristic | none`) and alternative candidates for non-exact matches. -Why this exists: every sibling tool consuming Clarion should ask in *their* native identity scheme, not embed Clarion's ID format. The alternative is every sibling re-implementing Clarion's ID generation, which couples them all to Clarion's ID scheme changes. `resolve` lets them stay decoupled (enrichment, not load-bearing). +Why this exists: every sibling tool consuming Loomweave should ask in *their* native identity scheme, not embed Loomweave's ID format. The alternative is every sibling re-implementing Loomweave's ID generation, which couples them all to Loomweave's ID scheme changes. `resolve` lets them stay decoupled (enrichment, not load-bearing). -404 behaviour: returns 200 with `resolution_confidence: "none"` and empty `entity_id` — distinguishes "Clarion doesn't know this" from "Clarion is down." +404 behaviour: returns 200 with `resolution_confidence: "none"` and empty `entity_id` — distinguishes "Loomweave doesn't know this" from "Loomweave is down." #### Authentication — ADR-014 / ADR-034 / ADR-042 registry-backend read API -ADR-014 supersedes ADR-012 for the Filigree `registry_backend: clarion` +ADR-014 supersedes ADR-012 for the Filigree `registry_backend: loomweave` HTTP read surface. The registry-backend API is loopback-only by default and may run unauthenticated only in that local sidecar posture. ADR-034 closes the non-loopback gap: a non-loopback bind requires both @@ -1057,9 +1057,9 @@ TLS is out of scope for v0.1. Operators wanting network exposure terminate TLS a #### ETag-style caching -Every response carries `X-Clarion-State: `. Clients can supply `If-None-Match: ` for cheap revalidation; unchanged → 304. +Every response carries `X-Loomweave-State: `. Clients can supply `If-None-Match: ` for cheap revalidation; unchanged → 304. -Wardline in CI polls at commit cadence — ETag revalidation reduces load on Clarion and bandwidth for the consumer. State hash is per-run (not per-entity) for v0.1 simplicity. +Wardline in CI polls at commit cadence — ETag revalidation reduces load on Loomweave and bandwidth for the consumer. State hash is per-run (not per-entity) for v0.1 simplicity. --- @@ -1067,7 +1067,7 @@ Wardline in CI polls at commit cadence — ETag revalidation reduces load on Cla **Addresses**: NFR-SEC-01 through NFR-SEC-05, REQ-CONFIG-05. -Security is a first-class concern in v0.1 because Clarion sends source code to a third-party LLM provider, persists LLM-derived content in a git-committed store, and exposes an HTTP API that Wardline consumes cross-process. The threats are concrete and the defences belong in v0.1, not later. +Security is a first-class concern in v0.1 because Loomweave sends source code to a third-party LLM provider, persists LLM-derived content in a git-committed store, and exposes an HTTP API that Wardline consumes cross-process. The threats are concrete and the defences belong in v0.1, not later. ### Threat model (v0.1 scope) @@ -1076,25 +1076,25 @@ Security is a first-class concern in v0.1 because Clarion sends source code to a | Secret exfiltration to LLM provider | Critical | `.env`, test fixtures, committed API keys → entities → Anthropic API | Pre-ingest secret scanner; findings block LLM dispatch | | Prompt injection via source | Critical | Adversarial docstrings / comments → briefing field values → future-prompt poisoning via cache | Schema validation + untrusted-content delimiters + `knowledge_basis: static_only` | | Guidance poisoning via LLM-proposed sheets | High | `propose_guidance` MCP tool promotes attacker text into prompts | Manual promotion gate — proposals create observations, not sheets | -| HTTP API reachable by other local processes | Medium | `clarion serve` on shared dev host / container | ADR-014 registry-backend API is unauthenticated but loopback-only by default; non-loopback binds are refused unless explicitly allowed and protected by operator-managed access control. | -| DB tampering via committed `.clarion/clarion.db` | Medium | Bad actor edits DB, commits, poisons teammate briefings | Content-hash cross-check on load (v0.2); `clarion db verify` CLI | +| HTTP API reachable by other local processes | Medium | `loomweave serve` on shared dev host / container | ADR-014 registry-backend API is unauthenticated but loopback-only by default; non-loopback binds are refused unless explicitly allowed and protected by operator-managed access control. | +| DB tampering via committed `.loomweave/loomweave.db` | Medium | Bad actor edits DB, commits, poisons teammate briefings | Content-hash cross-check on load (v0.2); `loomweave db verify` CLI | | LLM audit-log leakage via git | Medium | `runs//log.jsonl` contains request/response bodies | Default-excluded from git | | Personal API key charged when committing team DB | Medium (operator) | Developer commits DB generated with personal key | Operator guidance; `--audit-key` hint | | Plugin subprocess compromise | Medium | Malicious third-party plugin reads source or exhausts host resources | Hybrid authority per ADR-021: path jail, Content-Length ceiling, entity-count cap, per-plugin `prlimit` RSS. Full syscall sandbox + plugin hash-pinning deferred to v0.2 (NG-16) | ### Pre-ingest redaction -Before any file content reaches the LLM (Phases 4, 5, 6), Clarion runs a pre-ingest secret scanner on the file buffer: +Before any file content reaches the LLM (Phases 4, 5, 6), Loomweave runs a pre-ingest secret scanner on the file buffer: - **Tool**: bundled `detect-secrets` (or equivalent Rust-native scanner). Bundled binary — does not depend on the analysed project having `detect-secrets` installed. - **Scope**: every file in `analysis.include` is scanned; exclusion globs apply *after* scanning (excluded files never reach the LLM regardless). - **Policy on finding**: - - Unredacted secret → `CLA-SEC-SECRET-DETECTED` (severity: ERROR) + **block LLM dispatch for that file**. Entities in the file still land in the store with summaries marked `briefing_blocked: secret_present`. - - False-positive whitelist: `.clarion/secrets-baseline.yaml` (same format as `detect-secrets`' baseline; committable and reviewable). - - Override: `clarion analyze --allow-unredacted-secrets` requires explicit confirmation prompt and records the override in `stats.json`. + - Unredacted secret → `LMWV-SEC-SECRET-DETECTED` (severity: ERROR) + **block LLM dispatch for that file**. Entities in the file still land in the store with summaries marked `briefing_blocked: secret_present`. + - False-positive whitelist: `.loomweave/secrets-baseline.yaml` (same format as `detect-secrets`' baseline; committable and reviewable). + - Override: `loomweave analyze --allow-unredacted-secrets` requires explicit confirmation prompt and records the override in `stats.json`. - **Coverage**: high-entropy strings, common API key patterns (AWS, GitHub, Anthropic, Stripe, etc.), RSA private key headers, JWT-looking tokens. -This is a defensive measure, not a vulnerability scanner. False negatives exist; operators should not commit secrets and rely on Clarion to catch them. +This is a defensive measure, not a vulnerability scanner. False negatives exist; operators should not commit secrets and rely on Loomweave to catch them. ### Prompt-injection containment @@ -1104,13 +1104,13 @@ Layered defences: 1. **Segment-boundary discipline**. Prompt-caching (§5) places system prompt + project-level guidance in Segment 1 (stable, trusted) and untrusted entity content in Segment 4 (varies per call, untrusted). Plugin templates render file content inside explicit `...` delimiters; templates are reviewed as part of plugin release. -2. **Structured-output schema validation**. Every briefing round-trips through the `EntityBriefing` JSON schema. An injected "output plain text instead" instruction fails validation; after one failed retry the run emits `CLA-INFRA-BRIEFING-INVALID` and skips the entity. +2. **Structured-output schema validation**. Every briefing round-trips through the `EntityBriefing` JSON schema. An injected "output plain text instead" instruction fails validation; after one failed retry the run emits `LMWV-INFRA-BRIEFING-INVALID` and skips the entity. -3. **Controlled-vocabulary enforcement**. `patterns` and `antipatterns` fields are constrained to the core + plugin vocabulary. Novel tags are logged as `CLA-FACT-VOCABULARY-CANDIDATE` for human review; they don't promote into future prompts silently. +3. **Controlled-vocabulary enforcement**. `patterns` and `antipatterns` fields are constrained to the core + plugin vocabulary. Novel tags are logged as `LMWV-FACT-VOCABULARY-CANDIDATE` for human review; they don't promote into future prompts silently. -4. **`knowledge_basis` propagation**. Briefings derived from LLM output (no human review) carry `knowledge_basis: static_only`. Agents consuming Clarion briefings should treat `static_only` claims as hypotheses, not assertions. +4. **`knowledge_basis` propagation**. Briefings derived from LLM output (no human review) carry `knowledge_basis: static_only`. Agents consuming Loomweave briefings should treat `static_only` claims as hypotheses, not assertions. -5. **Guidance promotion gate**. `propose_guidance(entity_id, content, rules?)` creates a **Filigree observation**, not a sheet. Promotion to a sheet requires explicit `clarion guidance promote ` CLI action. A single adversarial LLM call cannot poison every future prompt. +5. **Guidance promotion gate**. `propose_guidance(entity_id, content, rules?)` creates a **Filigree observation**, not a sheet. Promotion to a sheet requires explicit `loomweave guidance promote ` CLI action. A single adversarial LLM call cannot poison every future prompt. 6. **Prompt-cache poisoning countermeasures**. Summary-cache hits are keyed on `guidance_fingerprint`; a newly-promoted guidance sheet changes the fingerprint and forces re-summarisation. A compromised briefing never re-enters the cache without the prompt that produced it re-running. @@ -1118,7 +1118,7 @@ Layered defences: - **Full syscall sandbox** (seccomp/landlock). v0.1 enforces path jail, Content-Length ceiling, per-run entity-count cap, and per-plugin RSS limit per ADR-021; syscall-level isolation defers to v0.2 alongside plugin hash-pinning (NG-16). - **Per-endpoint HTTP auth scoping**. v0.1 token is project-wide. -- **DB content-hash verification on load**. Malicious DB edit caught by the next `clarion analyze` (content hashes re-computed) but not at `clarion serve` boot time. +- **DB content-hash verification on load**. Malicious DB edit caught by the next `loomweave analyze` (content hashes re-computed) but not at `loomweave serve` boot time. - **Automatic redaction** of secrets *within* files (v0.1 blocks the whole file; v0.2+ may replace secret substrings with `` placeholders). - **TLS on HTTP API**. Operators wanting network exposure run behind a reverse proxy terminating TLS. @@ -1126,21 +1126,21 @@ Layered defences: Every security-relevant event emits a finding: -- `CLA-SEC-SECRET-DETECTED` — unredacted secret blocked LLM dispatch -- `CLA-SEC-UNREDACTED-SECRETS-ALLOWED` — operator overrode block -- `CLA-INFRA-HTTP-NON-LOOPBACK-UNAUTHENTICATED` — `clarion serve` running the ADR-014 HTTP read API on a non-loopback bind with `serve.http.allow_non_loopback: true`; per-startup WARN log -- `CLA-INFRA-BRIEFING-INVALID` — schema validation failed twice (possible injection) -- `CLA-SEC-VOCABULARY-CANDIDATE-NOVEL` — novel vocabulary tag proposed by LLM (light signal; mostly harmless) +- `LMWV-SEC-SECRET-DETECTED` — unredacted secret blocked LLM dispatch +- `LMWV-SEC-UNREDACTED-SECRETS-ALLOWED` — operator overrode block +- `LMWV-INFRA-HTTP-NON-LOOPBACK-UNAUTHENTICATED` — `loomweave serve` running the ADR-014 HTTP read API on a non-loopback bind with `serve.http.allow_non_loopback: true`; per-startup WARN log +- `LMWV-INFRA-BRIEFING-INVALID` — schema validation failed twice (possible injection) +- `LMWV-SEC-VOCABULARY-CANDIDATE-NOVEL` — novel vocabulary tag proposed by LLM (light signal; mostly harmless) Findings feed Filigree via the normal exchange. Security-focused operators can `filigree list --label=security --since 7d` across the suite. ### Operator guidance (non-code security) -Some risks sit outside Clarion's code but inside the operator's responsibility: +Some risks sit outside Loomweave's code but inside the operator's responsibility: -- **Use project-scoped API keys, not personal ones, when committing the DB**. Briefings in `.clarion/clarion.db` were paid for by whoever ran analyze; a teammate pulling your committed DB benefits from calls your personal key paid for. Use an Anthropic project key, not your personal key. +- **Use project-scoped API keys, not personal ones, when committing the DB**. Briefings in `.loomweave/loomweave.db` were paid for by whoever ran analyze; a teammate pulling your committed DB benefits from calls your personal key paid for. Use an Anthropic project key, not your personal key. - **Rotate tokens when a committed DB exposes a stale model's output**. If the DB was generated with a leaked or exposed API key, the token is already used; briefings aren't themselves secret but the key's usage fingerprint is. -- **Review `.clarion/.gitignore` before first commit**. Default excludes `runs/*/log.jsonl` (raw LLM request/response bodies); opting in to commit logs ships source excerpts to the repo — a choice, not an oversight. +- **Review `.loomweave/.gitignore` before first commit**. Default excludes `runs/*/log.jsonl` (raw LLM request/response bodies); opting in to commit logs ships source excerpts to the repo — a choice, not an oversight. Operator-guidance documentation lives in the detailed-design §10 for procedural depth. @@ -1148,42 +1148,42 @@ Operator-guidance documentation lives in the detailed-design §10 for procedural ## 11. Suite Bootstrap -**Addresses**: REQ-INTEG-FILIGREE-05, NFR-RELIABILITY-02, NFR-OBSERV-04, CON-LOOM-01, CON-FILIGREE-02. +**Addresses**: REQ-INTEG-FILIGREE-05, NFR-RELIABILITY-02, NFR-OBSERV-04, CON-WEFT-01, CON-FILIGREE-02. ### What this section is -Clarion v0.1 is not joining an existing Loom fabric — it is the work that weaves the fabric for the v0.1 suite of Clarion + Filigree + Wardline. The integration reconnaissance (`2026-04-17-clarion-integration-recon.md`) verified that cross-tool surface area the Rev-1 design assumed is mostly not yet implemented: Wardline has no HTTP client, Filigree has no `registry_backend` flag or pluggable file registry, no cross-tool fixtures exist. +Loomweave v0.1 is not joining an existing Weft fabric — it is the work that weaves the fabric for the v0.1 suite of Loomweave + Filigree + Wardline. The integration reconnaissance (`2026-04-17-loomweave-integration-recon.md`) verified that cross-tool surface area the Rev-1 design assumed is mostly not yet implemented: Wardline has no HTTP client, Filigree has no `registry_backend` flag or pluggable file registry, no cross-tool fixtures exist. -This section describes **Clarion's side** of the coordination: the capability-probe it runs at every analyse, the degraded modes it falls back into when sibling capabilities are absent, and what Clarion asks of sibling products as prerequisites. Per the Loom federation axiom ([../../suite/loom.md](../../suite/loom.md) §3, §6), there is **no central orchestrator** — each product handles its own integration surface, and sibling absence is a graceful degradation, not a failure. +This section describes **Loomweave's side** of the coordination: the capability-probe it runs at every analyse, the degraded modes it falls back into when sibling capabilities are absent, and what Loomweave asks of sibling products as prerequisites. Per the Weft federation axiom ([../../suite/weft.md](../../suite/weft.md) §3, §6), there is **no central orchestrator** — each product handles its own integration surface, and sibling absence is a graceful degradation, not a failure. -### Capability negotiation at `clarion analyze` startup +### Capability negotiation at `loomweave analyze` startup -Before Phase 0 (discovery + dry-run), Clarion probes each sibling and emits **exactly one** `CLA-INFRA-SUITE-COMPAT-REPORT` finding summarising the discovered state. One finding collapses scattered runtime surprises into a single auditable signal. +Before Phase 0 (discovery + dry-run), Loomweave probes each sibling and emits **exactly one** `LMWV-INFRA-SUITE-COMPAT-REPORT` finding summarising the discovered state. One finding collapses scattered runtime surprises into a single auditable signal. Probes: 1. **Filigree presence and version**: `GET /api/files/_schema`. Record version, enums, flag presence. 2. **Filigree `registry_backend`**: look for the flag in `_schema.config_flags`. Absent → shadow-registry fallback. -3. **Filigree `/api/v1/observations`**: `HEAD` request. In v0.1 the absence is expected (MCP-spawn is the v0.1 path per ADR-016); presence signals v0.2 is installed and Clarion switches to HTTP. -4. **Wardline REGISTRY version**: `import wardline.core.registry; registry.REGISTRY_VERSION`. Compared against Clarion's pin. +3. **Filigree `/api/v1/observations`**: `HEAD` request. In v0.1 the absence is expected (MCP-spawn is the v0.1 path per ADR-016); presence signals v0.2 is installed and Loomweave switches to HTTP. +4. **Wardline REGISTRY version**: `import wardline.core.registry; registry.REGISTRY_VERSION`. Compared against Loomweave's pin. 5. **Wardline SARIF schema version**: read `wardline.sarif.baseline.json` header; check `wardline.propertyBagVersion`. -The `CLA-INFRA-SUITE-COMPAT-REPORT` carries `metadata.clarion.*` with every probe result — operators read one finding to understand how this run will behave. +The `LMWV-INFRA-SUITE-COMPAT-REPORT` carries `metadata.loomweave.*` with every probe result — operators read one finding to understand how this run will behave. ### Per-component fallbacks | Prerequisite not satisfied | Fallback mode | Signal | |---|---|---| -| Filigree unreachable entirely | Local-only: findings write to `runs//findings.jsonl`; no observations | `--no-filigree` explicit; `CLA-INFRA-FILIGREE-UNAVAILABLE` per batch | -| Filigree present; `registry_backend` absent | Shadow-registry: POSTs normally; Filigree auto-creates `file_records` under native rules | `CLA-INFRA-FILIGREE-SHADOW-REGISTRY` | -| `filigree` binary missing from `PATH` | Observation emission disabled; observations queued to `runs//deferred_observations.jsonl` for later replay | `CLA-INFRA-FILIGREE-BINARY-MISSING` | -| Wardline package not installable in plugin venv | Registry-mirror: hardcoded `wardline_registry_v.py` | `--no-wardline` explicit; `CLA-INFRA-WARDLINE-REGISTRY-MIRRORED` | -| Wardline installed; `REGISTRY_VERSION` additive-newer | Proceed with warning; additive decorators `clarion_augmentation` | `CLA-INFRA-WARDLINE-REGISTRY-ADDITIVE-SKEW` | -| Wardline installed; `REGISTRY_VERSION` major-bump or older | Mirror-mode | `CLA-INFRA-WARDLINE-REGISTRY-MIRRORED` | -| SARIF with unknown property-bag version | Best-effort translation; per-removed-key findings | `CLA-INFRA-SARIF-SCHEMA-UNKNOWN`, `CLA-INFRA-SARIF-KEY-REMOVED` | -| `clarion sarif import` not installed | Wardline findings don't reach Filigree via Clarion; operators use Wardline's existing SARIF→GitHub Security path | Manual — no auto-findings | +| Filigree unreachable entirely | Local-only: findings write to `runs//findings.jsonl`; no observations | `--no-filigree` explicit; `LMWV-INFRA-FILIGREE-UNAVAILABLE` per batch | +| Filigree present; `registry_backend` absent | Shadow-registry: POSTs normally; Filigree auto-creates `file_records` under native rules | `LMWV-INFRA-FILIGREE-SHADOW-REGISTRY` | +| `filigree` binary missing from `PATH` | Observation emission disabled; observations queued to `runs//deferred_observations.jsonl` for later replay | `LMWV-INFRA-FILIGREE-BINARY-MISSING` | +| Wardline package not installable in plugin venv | Registry-mirror: hardcoded `wardline_registry_v.py` | `--no-wardline` explicit; `LMWV-INFRA-WARDLINE-REGISTRY-MIRRORED` | +| Wardline installed; `REGISTRY_VERSION` additive-newer | Proceed with warning; additive decorators `loomweave_augmentation` | `LMWV-INFRA-WARDLINE-REGISTRY-ADDITIVE-SKEW` | +| Wardline installed; `REGISTRY_VERSION` major-bump or older | Mirror-mode | `LMWV-INFRA-WARDLINE-REGISTRY-MIRRORED` | +| SARIF with unknown property-bag version | Best-effort translation; per-removed-key findings | `LMWV-INFRA-SARIF-SCHEMA-UNKNOWN`, `LMWV-INFRA-SARIF-KEY-REMOVED` | +| `loomweave sarif import` not installed | Wardline findings don't reach Filigree via Loomweave; operators use Wardline's existing SARIF→GitHub Security path | Manual — no auto-findings | -The degraded modes exist so Clarion ships on its own timeline, not the slowest of three. Per-run compat state is recorded in `runs//stats.json` so historical regressions in suite capability are visible. +The degraded modes exist so Loomweave ships on its own timeline, not the slowest of three. Per-run compat state is recorded in `runs//stats.json` so historical regressions in suite capability are visible. ### Prerequisites named here (full detail in detailed-design §11) @@ -1197,7 +1197,7 @@ The degraded modes exist so Clarion ships on its own timeline, not the slowest o **Wardline side** (ADRs 015, 018 in detailed-design): - Stable `REGISTRY_VERSION` export + `LEGACY_DECORATOR_ALIASES` table - YAML/JSON descriptor of REGISTRY (enables non-Python plugins — v0.2) -- Native `POST /api/v1/scan-results` emitter (v0.2; v0.1 uses Clarion-side SARIF translator) +- Native `POST /api/v1/scan-results` emitter (v0.2; v0.1 uses Loomweave-side SARIF translator) **Joint**: - Cross-tool test fixtures (mock Filigree server, Wardline SARIF corpus, schema-pin test) @@ -1205,7 +1205,7 @@ The degraded modes exist so Clarion ships on its own timeline, not the slowest o ### Why no central orchestrator -Per [../../suite/loom.md](../../suite/loom.md) §6, Loom does not own a coordination runtime. Capability negotiation is point-to-point — Clarion probes Filigree and Wardline directly, each sibling exposes its own surfaces, no "Loom bus" mediates. This section *is* that posture, expressed as Clarion's own behaviour. +Per [../../suite/weft.md](../../suite/weft.md) §6, Weft does not own a coordination runtime. Capability negotiation is point-to-point — Loomweave probes Filigree and Wardline directly, each sibling exposes its own surfaces, no "Weft bus" mediates. This section *is* that posture, expressed as Loomweave's own behaviour. If a future requirement emerged for "what if more tools join the suite and the probe matrix explodes?", the answer per the federation axiom is not "build a shared registry" — it is "each joining tool implements its own probe; each existing tool adds handling for it." That keeps the pairwise composition rule honest even as the suite grows. @@ -1224,23 +1224,23 @@ The parallel listing in [detailed-design.md §11](./detailed-design.md#11-archit | ADR-001 | Rust for the core | Accepted | P0 | Primary author directive. Single-binary ship, mature ecosystem (axum/rusqlite/tokio), plugin interop via subprocess. Not subject to alternatives analysis. | | ADR-002 | Plugin transport: Content-Length framed JSON-RPC 2.0 subprocess | Accepted | P0 | Binary-safe framing, resumability after crash, alignment with LSP patterns. Alternatives: newline-delimited JSON (unsafe for content with embedded newlines), Wasm (too early for plugin authoring ergonomics), embedded Python (couples core to Python runtime). | | ADR-003 | Entity ID scheme: symbolic canonical-name; file path as property; EntityAlias v0.2 | Accepted | P0 | Cross-tool identity must survive file moves. Path-embedded IDs silently detach every reference; symbolic IDs survive 80% case (file move without rename). Rename tracking via EntityAlias deferred; manual `--repair-aliases` workaround in v0.1. | -| ADR-004 | Finding-exchange format: Filigree-native intake; `metadata.clarion.*` nesting | Accepted | P0 | Filigree's `POST /api/v1/scan-results` is production path; SARIF requires either translation or Filigree-side work. Nesting convention under `metadata` dict (verified verbatim preservation) avoids silent drops of extension fields. | -| ADR-005 | `.clarion/` git-committable by default; DB included, run logs excluded | To author | P1 | Shared-analysis-state story benefits small teams; run logs may contain source excerpts appropriate to Anthropic but not git. Default-exclude run logs via `.gitignore`; opt-in to commit. | +| ADR-004 | Finding-exchange format: Filigree-native intake; `metadata.loomweave.*` nesting | Accepted | P0 | Filigree's `POST /api/v1/scan-results` is production path; SARIF requires either translation or Filigree-side work. Nesting convention under `metadata` dict (verified verbatim preservation) avoids silent drops of extension fields. | +| ADR-005 | `.loomweave/` git-committable by default; DB included, run logs excluded | To author | P1 | Shared-analysis-state story benefits small teams; run logs may contain source excerpts appropriate to Anthropic but not git. Default-exclude run logs via `.gitignore`; opt-in to commit. | | [ADR-006](../adr/ADR-006-clustering-algorithm.md), [ADR-032](../adr/ADR-032-weighted-components-clustering-fallback.md) | Clustering algorithm: Leiden (with weighted-components fallback) on imports + calls subgraph | Accepted | P0 | Leiden's connected-community guarantee fixes disconnected-cluster defects. Directed, weighted (reference_count); module-level. `weighted_components` is the deterministic fallback selectable via config when a local component cut is preferred. Modularity score recorded, not enforced (v0.1); weak threshold is reported via finding. | -| [ADR-007](../adr/ADR-007-summary-cache-key.md) | Summary cache key design: `(entity_id, content_hash, prompt_template_id, model_tier, guidance_fingerprint)` + TTL backstop + churn-eager invalidation | Accepted | P0 | Full 5-part key captures all syntactic staleness paths; TTL backstop (180d default) bounds semantic staleness the key alone doesn't see; churn-eager invalidation on `CLA-FACT-GUIDANCE-CHURN-STALE` makes stale-guidance pressure visible via cost. Neighborhood-drift flag (`stale_semantic: true`) rather than forced miss preserves NFR-COST-02's 95% hit-rate target. Block C1 spike validates the assumption. | +| [ADR-007](../adr/ADR-007-summary-cache-key.md) | Summary cache key design: `(entity_id, content_hash, prompt_template_id, model_tier, guidance_fingerprint)` + TTL backstop + churn-eager invalidation | Accepted | P0 | Full 5-part key captures all syntactic staleness paths; TTL backstop (180d default) bounds semantic staleness the key alone doesn't see; churn-eager invalidation on `LMWV-FACT-GUIDANCE-CHURN-STALE` makes stale-guidance pressure visible via cost. Neighborhood-drift flag (`stale_semantic: true`) rather than forced miss preserves NFR-COST-02's 95% hit-rate target. Block C1 spike validates the assumption. | | ADR-008 | (Superseded by ADR-014.) | Superseded | — | Initial Filigree file-registry displacement design was "feature flag"; recon showed it's schema surgery. See ADR-014. | | ADR-009 | Structured briefings vs free-form prose | To author | P2 | Principle 2 requires bounded, composable responses; prose is neither. Schema validation also enables prompt-injection detection (schema-invalid → possible injection). | | ADR-010 | MCP as first-class surface — lock-in cost vs ecosystem reach | To author | P2 | Anthropic's MCP standard is the ecosystem's current centre of gravity for LLM tool integrations; lock-in cost is acknowledged but the ecosystem reach outweighs it for v0.1. Strategic review at v0.3+. | | [ADR-011](../adr/ADR-011-writer-actor-concurrency.md) | Writer-actor concurrency model (vs shadow-DB swap) | Accepted | P0 | Single writer actor + per-N-files transactions (default N=50) is the committed shape; `--shadow-db` opt-in for zero-stale-read scenarios. Design-review §2.2 CRITICAL flag retires. SQLite-concurrency-under-load assumption named as v0.2 validation task (`NG-28` proposed). | | [ADR-012](../adr/ADR-012-http-auth-default.md) | Historical HTTP read-API auth proposal: UDS default with TCP+token fallback | Superseded for ADR-014 registry-backend API | P0 | ADR-014 now owns the registry-backend HTTP read API posture: unauthenticated loopback-only by default, non-loopback refused unless explicitly allowed and protected externally. ADR-012 remains context for the earlier broad HTTP API proposal. | -| [ADR-013](../adr/ADR-013-pre-ingest-secret-scanner.md) | Pre-ingest secret scanner with LLM-dispatch block | Accepted | P0 | Rust-native port of detect-secrets rule set (preserves NFR-OPS-04 single-binary). File-level block on detection; structural extraction preserved; briefings marked `briefing_blocked: secret_present`. `.clarion/secrets-baseline.yaml` for false-positives. `--allow-unredacted-secrets` requires TTY confirm OR explicit `--confirm-allow-unredacted-secrets=yes-i-understand` in CI. | -| [ADR-014](../adr/ADR-014-filigree-registry-backend.md) | Filigree `registry_backend` flag + pluggable `RegistryProtocol` — schema surgery, not config flip | Accepted | P0 | Four NOT-NULL foreign keys on `file_records(id)` + three auto-create paths require a real interface, not a flag. Clarion's shadow-registry fallback preserves v0.1 shipability when Filigree hasn't landed the surgery. | -| [ADR-015](../adr/ADR-015-wardline-filigree-emission.md) | Wardline→Filigree emission ownership: Clarion-side SARIF translator (v0.1), native Wardline POST (v0.2) | Accepted | P0 | Wardline has no HTTP client today (`integration-recon:339`); adding one is a refactor not on the v0.1 timeline. Clarion-side translator ships independently; translator stays permanent for Semgrep / CodeQL / etc. `loom.md` §5 asterisk 1 retires when native emitter lands. Revision trigger: Block C2 spike showing emitter is ≤1 day of work promotes to v0.1. | -| [ADR-016](../adr/ADR-016-observation-transport.md) | Observation transport: `filigree mcp` subprocess spawn (v0.1); `POST /api/v1/observations` HTTP (v0.2) | Accepted | P0 | Per Q1 scope commitment, observation HTTP transport deferred to v0.2. v0.1 emits via Clarion spawning `filigree mcp` subprocess and calling existing `create_observation` MCP tool over stdio. v0.2 HTTP endpoint is the retirement trigger; capability probe detects via `HEAD /api/v1/observations`. | -| [ADR-017](../adr/ADR-017-severity-and-dedup.md) | Severity mapping + rule-ID round-trip + dedup via `mark_unseen=true` | Accepted | P0 | Clarion internal severity (`INFO/WARN/ERROR/CRITICAL`) maps to Filigree wire (`critical/high/medium/low/info`). Round-trip preserves internal via `metadata.clarion.internal_severity`. `mark_unseen=true` is the v0.1 dedup workaround for entities moving within files; server-side per-entity dedup deferred to v0.2 (NG-21). Fixes `CLA-INFRA-PARSE-ERROR` vs `CLA-PY-PARSE-ERROR` drift (Issue 7). | -| [ADR-018](../adr/ADR-018-identity-reconciliation.md) | Identity reconciliation: Clarion maintains translation layer; Wardline owns its qualnames; direct REGISTRY import with version pinning | Accepted | P0 | Three identity schemes exist (Clarion EntityId, Wardline qualname, exception-register location). Clarion is the translator — it owns the catalog that makes the qualnames meaningful. Forcing Wardline to adopt Clarion's scheme would be centralisation drift (loom.md §6). Plugin-level REGISTRY import is a named initialization-coupling asterisk (loom.md §5) retiring when Wardline publishes a YAML/JSON descriptor (NG-25, v0.2). | +| [ADR-013](../adr/ADR-013-pre-ingest-secret-scanner.md) | Pre-ingest secret scanner with LLM-dispatch block | Accepted | P0 | Rust-native port of detect-secrets rule set (preserves NFR-OPS-04 single-binary). File-level block on detection; structural extraction preserved; briefings marked `briefing_blocked: secret_present`. `.loomweave/secrets-baseline.yaml` for false-positives. `--allow-unredacted-secrets` requires TTY confirm OR explicit `--confirm-allow-unredacted-secrets=yes-i-understand` in CI. | +| [ADR-014](../adr/ADR-014-filigree-registry-backend.md) | Filigree `registry_backend` flag + pluggable `RegistryProtocol` — schema surgery, not config flip | Accepted | P0 | Four NOT-NULL foreign keys on `file_records(id)` + three auto-create paths require a real interface, not a flag. Loomweave's shadow-registry fallback preserves v0.1 shipability when Filigree hasn't landed the surgery. | +| [ADR-015](../adr/ADR-015-wardline-filigree-emission.md) | Wardline→Filigree emission ownership: Loomweave-side SARIF translator (v0.1), native Wardline POST (v0.2) | Accepted | P0 | Wardline has no HTTP client today (`integration-recon:339`); adding one is a refactor not on the v0.1 timeline. Loomweave-side translator ships independently; translator stays permanent for Semgrep / CodeQL / etc. `weft.md` §5 asterisk 1 retires when native emitter lands. Revision trigger: Block C2 spike showing emitter is ≤1 day of work promotes to v0.1. | +| [ADR-016](../adr/ADR-016-observation-transport.md) | Observation transport: `filigree mcp` subprocess spawn (v0.1); `POST /api/v1/observations` HTTP (v0.2) | Accepted | P0 | Per Q1 scope commitment, observation HTTP transport deferred to v0.2. v0.1 emits via Loomweave spawning `filigree mcp` subprocess and calling existing `create_observation` MCP tool over stdio. v0.2 HTTP endpoint is the retirement trigger; capability probe detects via `HEAD /api/v1/observations`. | +| [ADR-017](../adr/ADR-017-severity-and-dedup.md) | Severity mapping + rule-ID round-trip + dedup via `mark_unseen=true` | Accepted | P0 | Loomweave internal severity (`INFO/WARN/ERROR/CRITICAL`) maps to Filigree wire (`critical/high/medium/low/info`). Round-trip preserves internal via `metadata.loomweave.internal_severity`. `mark_unseen=true` is the v0.1 dedup workaround for entities moving within files; server-side per-entity dedup deferred to v0.2 (NG-21). Fixes `LMWV-INFRA-PARSE-ERROR` vs `LMWV-PY-PARSE-ERROR` drift (Issue 7). | +| [ADR-018](../adr/ADR-018-identity-reconciliation.md) | Identity reconciliation: Loomweave maintains translation layer; Wardline owns its qualnames; direct REGISTRY import with version pinning | Accepted | P0 | Three identity schemes exist (Loomweave EntityId, Wardline qualname, exception-register location). Loomweave is the translator — it owns the catalog that makes the qualnames meaningful. Forcing Wardline to adopt Loomweave's scheme would be centralisation drift (weft.md §6). Plugin-level REGISTRY import is a named initialization-coupling asterisk (weft.md §5) retiring when Wardline publishes a YAML/JSON descriptor (NG-25, v0.2). | | ADR-019 | SARIF property-bag preservation: Wardline's 44 `wardline.*` extension keys round-trip through `metadata.wardline_properties.*` | To author | P1 | Filigree preserves `metadata` verbatim. Literal pass-through handles Wardline's extensive extension keys; unknown schema versions use best-effort mapping with per-removed-key findings. | -| ADR-020 | Degraded-mode policy: Clarion ships on its own timeline; `--no-filigree` / `--no-wardline` explicit flags | To author | P1 | Per `CON-LOOM-01`, sibling absence reduces Clarion's capability but must not alter its semantics. Explicit flags name the degradation; the compat probe report makes per-run state auditable. | +| ADR-020 | Degraded-mode policy: Loomweave ships on its own timeline; `--no-filigree` / `--no-wardline` explicit flags | To author | P1 | Per `CON-WEFT-01`, sibling absence reduces Loomweave's capability but must not alter its semantics. Explicit flags name the degradation; the compat probe report makes per-run state auditable. | | [ADR-021](../adr/ADR-021-plugin-authority-hybrid.md) | Plugin authority model: hybrid — plugin declares capabilities in manifest, core enforces minimum-safe controls (path jail, Content-Length ceiling, entity-count cap, per-plugin `prlimit` RSS) | Accepted | P0 | The design currently assumes "trusted extension" but ships to a security-conscious audience that will assume "validated input". Full sandbox is over-investment for a single first-party plugin; trusted-extension default leaves four threats open (T-01, T-08, T-11, T-12 per panel `09-threat-model.md` §7). Hybrid closes T-08/T-11/T-12 and raises the floor on T-01 at tractable v0.1 cost. | | [ADR-022](../adr/ADR-022-core-plugin-ontology.md) | Core/plugin ontology ownership boundary: plugins declare entity kinds and edge kinds; core validates shape without embedding ontology | Accepted | P0 | Principle 3 requires "plugin owns ontology; core owns algorithms" but the data-layer analogue at the core/plugin boundary was unstated. Plugins own kind vocabulary for language-specific concepts; the core validates shape (identifier grammar, reserved-kind namespace, rule-ID namespacing, emission-declaration contract) without baking in Python-specific assumptions. Shapes ADR-002's RPC surface, ADR-003's `{kind}` ID component, and ADR-006's clustering subgraph. | @@ -1259,14 +1259,14 @@ The terms below are the ones this system-design layer relies on most heavily: | Term | Definition | |---|---| | **Briefing** | Structured summary answering a fixed set of questions about an entity. | -| **Entity** | Typed node in Clarion's property graph. | +| **Entity** | Typed node in Loomweave's property graph. | | **Entity ID** | Stable identifier of the form `{plugin_id}:{kind}:{canonical_qualified_name}`. | | **Edge** | Typed relationship between entities such as `contains`, `calls`, or `imports`. | | **Finding** | Structured claim-with-evidence; may be a defect, fact, classification, metric, or suggestion. | | **Guidance fingerprint** | Hash of the guidance sheets applied to a query; part of the summary-cache key. | | **Plugin manifest** | YAML declaration of a plugin's kinds, edges, rules, and capabilities. | | **Scope lens** | Query/session filter that biases neighbour lookups toward a relationship family. | -| **Tier** | Wardline trust classification preserved verbatim by Clarion. | +| **Tier** | Wardline trust classification preserved verbatim by Loomweave. | | **Writer-actor** | Single task that owns SQLite writes; other tasks submit mutations through it. | | **Pre-ingest redaction** | Secret scan that runs before any file content is sent to the LLM provider. | | **Capability probe / compat report** | Startup check of sibling-tool availability that emits one compatibility finding. | @@ -1276,4 +1276,4 @@ See [detailed-design.md](./detailed-design.md) Appendix B for the full glossary. --- -**End of Clarion v1.0 system design.** +**End of Loomweave v1.0 system design.** diff --git a/docs/clarion/README.md b/docs/loomweave/README.md similarity index 78% rename from docs/clarion/README.md rename to docs/loomweave/README.md index 7f283476..fd6edbd8 100644 --- a/docs/clarion/README.md +++ b/docs/loomweave/README.md @@ -1,10 +1,10 @@ -# Clarion Docs +# Loomweave Docs -This folder holds Clarion product documentation. +This folder holds Loomweave product documentation. ## Read paths -- Starting Clarion v1.0: [1.0/README.md](./1.0/README.md) +- Starting Loomweave v1.0: [1.0/README.md](./1.0/README.md) - Architecture decisions: [adr/README.md](./adr/README.md) ## Structure diff --git a/docs/clarion/adr/ADR-001-rust-for-core.md b/docs/loomweave/adr/ADR-001-rust-for-core.md similarity index 82% rename from docs/clarion/adr/ADR-001-rust-for-core.md rename to docs/loomweave/adr/ADR-001-rust-for-core.md index a9d5a1c7..cc1cb845 100644 --- a/docs/clarion/adr/ADR-001-rust-for-core.md +++ b/docs/loomweave/adr/ADR-001-rust-for-core.md @@ -3,18 +3,18 @@ **Status**: Accepted **Date**: 2026-04-17 **Deciders**: qacona@gmail.com -**Context**: Clarion v0.1 core implementation language +**Context**: Loomweave v0.1 core implementation language ## Summary -Clarion's core will be implemented in Rust. The choice is anchored to requirements the design already assumes (single-binary distribution, long-lived SQLite workload, robust subprocess supervision) rather than to author preference alone. +Loomweave's core will be implemented in Rust. The choice is anchored to requirements the design already assumes (single-binary distribution, long-lived SQLite workload, robust subprocess supervision) rather than to author preference alone. ## Context -Clarion's core is responsible for: +Loomweave's core is responsible for: - single-binary distribution across developer machines (`NFR-OPS-04`: operator installs via `pipx` or a released binary with no runtime install) -- SQLite-backed storage that serves both batch `clarion analyze` and long-lived `clarion serve` simultaneously (`CON-SQLITE-01`, ADR-011 writer-actor) +- SQLite-backed storage that serves both batch `loomweave analyze` and long-lived `loomweave serve` simultaneously (`CON-SQLITE-01`, ADR-011 writer-actor) - subprocess supervision for language plugins over Content-Length framed JSON-RPC (`REQ-PLUGIN-01`..`REQ-PLUGIN-06`, ADR-002) - bounded-cost LLM orchestration with per-run cache behaviour and cancellation semantics (`NFR-COST-01`..`NFR-COST-03`) - local HTTP and MCP serving with a bounded trust posture (`NFR-SEC-*`, ADR-012 historically; ADR-014 for the registry-backend HTTP read API) @@ -23,7 +23,7 @@ The product posture is local-first and operationally lightweight (`CON-LOCAL-01` ## Decision -We will implement the Clarion core in Rust. +We will implement the Loomweave core in Rust. ## Alternatives Considered @@ -43,7 +43,7 @@ We will implement the Clarion core in Rust. - **SQLite ergonomics**: Go's canonical `database/sql` interface plus `mattn/go-sqlite3` works, but the common pattern around long-lived connections, per-statement caching, and the writer-actor-plus-reader-pool design assumed by ADR-011 is more idiomatic in `rusqlite` (direct control of connection-per-task, transaction lifetimes, and pragma tuning without `database/sql`'s abstractions getting in the way). - **Subprocess framing**: Content-Length framed JSON-RPC (ADR-002) is not a standard library primitive in Go; Rust's `tokio::io::AsyncBufRead` + `serde_json` makes framing and streaming framed reads lower-ceremony than Go's hand-rolled scanner pattern. - **LLM-orchestration backpressure**: per-run cost caps and prompt-cache segment management (`NFR-COST-*`) benefit from `tokio`'s structured concurrency and cancellation semantics; Go's context-cancellation model requires more manual discipline at each call site. -- **Garbage-collection pauses**: not disqualifying at Clarion's scale, but Rust's predictable allocation profile simplifies reasoning about `clarion serve` tail latency under MCP load. +- **Garbage-collection pauses**: not disqualifying at Loomweave's scale, but Rust's predictable allocation profile simplifies reasoning about `loomweave serve` tail latency under MCP load. **Why rejected**: Go would be a defensible choice — the first two cons are ergonomic, not blocking. The decision is driven by the concrete fit between `rusqlite` + `tokio` and the storage/concurrency/subprocess-framing workload this design actually specifies, not a judgment that Go could not work. The author directive aligns with the technical fit; it is not the sole basis for the decision. @@ -60,7 +60,7 @@ We will implement the Clarion core in Rust. **Cons**: - **Runtime dependency**: shipping requires either a bundled interpreter (contradicts `NFR-OPS-04` single-binary) or a user-installed interpreter (contradicts `CON-LOCAL-01` "works on developer's machine without system prep"). -- **Long-lived service profile**: `clarion serve` needs stable memory and latency characteristics under MCP load; GC-driven runtimes add noise to tail latency that is awkward to tune. +- **Long-lived service profile**: `loomweave serve` needs stable memory and latency characteristics under MCP load; GC-driven runtimes add noise to tail latency that is awkward to tune. - **Plugin supervision**: in-process Python plugins remove the subprocess boundary that ADR-002 uses as the Rust/Python compatibility seam — a plugin crash would take the core with it. **Why rejected**: Python/TypeScript reintroduce runtime-management overhead the product is explicitly trying to avoid (`NFR-OPS-*`, `CON-LOCAL-01`). In-process Python would also foreclose the language-agnostic plugin model ADR-002 establishes. @@ -90,5 +90,5 @@ We will implement the Clarion core in Rust. ## References -- [Clarion v0.1 system design](../v0.1/system-design.md) -- [Clarion v0.1 detailed design](../v0.1/detailed-design.md) +- [Loomweave v0.1 system design](../v0.1/system-design.md) +- [Loomweave v0.1 detailed design](../v0.1/detailed-design.md) diff --git a/docs/clarion/adr/ADR-002-plugin-transport-json-rpc.md b/docs/loomweave/adr/ADR-002-plugin-transport-json-rpc.md similarity index 84% rename from docs/clarion/adr/ADR-002-plugin-transport-json-rpc.md rename to docs/loomweave/adr/ADR-002-plugin-transport-json-rpc.md index 0ece6a13..11265bae 100644 --- a/docs/clarion/adr/ADR-002-plugin-transport-json-rpc.md +++ b/docs/loomweave/adr/ADR-002-plugin-transport-json-rpc.md @@ -7,11 +7,11 @@ ## Summary -Clarion plugins will run as subprocesses and speak JSON-RPC 2.0 over a Content-Length framed stream. This keeps plugin authorship language-specific while preserving resumable, binary-safe transport semantics. +Loomweave plugins will run as subprocesses and speak JSON-RPC 2.0 over a Content-Length framed stream. This keeps plugin authorship language-specific while preserving resumable, binary-safe transport semantics. ## Context -The Clarion core must communicate with language plugins that: +The Loomweave core must communicate with language plugins that: - run out of process - emit structured analysis results @@ -100,6 +100,6 @@ We will use subprocess-based JSON-RPC 2.0 with explicit Content-Length framing. ## References -- [Clarion v0.1 system design](../v0.1/system-design.md) -- [Clarion v0.1 detailed design](../v0.1/detailed-design.md) -- [Clarion v0.1 design review](../../implementation/v0.1-reviews/pre-restructure/design-review.md) +- [Loomweave v0.1 system design](../v0.1/system-design.md) +- [Loomweave v0.1 detailed design](../v0.1/detailed-design.md) +- [Loomweave v0.1 design review](../../implementation/v0.1-reviews/pre-restructure/design-review.md) diff --git a/docs/clarion/adr/ADR-003-entity-id-scheme.md b/docs/loomweave/adr/ADR-003-entity-id-scheme.md similarity index 83% rename from docs/clarion/adr/ADR-003-entity-id-scheme.md rename to docs/loomweave/adr/ADR-003-entity-id-scheme.md index 26692069..5023f6a6 100644 --- a/docs/clarion/adr/ADR-003-entity-id-scheme.md +++ b/docs/loomweave/adr/ADR-003-entity-id-scheme.md @@ -3,17 +3,17 @@ **Status**: Accepted **Date**: 2026-04-17 **Deciders**: qacona@gmail.com -**Context**: stable cross-tool identity for Clarion entities +**Context**: stable cross-tool identity for Loomweave entities ## Summary -Clarion entity IDs will use symbolic canonical qualified names rather than file-path-embedded identifiers. File paths remain properties, while rename tracking beyond the 80% case is deferred to a later `EntityAlias` mechanism. +Loomweave entity IDs will use symbolic canonical qualified names rather than file-path-embedded identifiers. File paths remain properties, while rename tracking beyond the 80% case is deferred to a later `EntityAlias` mechanism. ## Context -Clarion's entity IDs are referenced by: +Loomweave's entity IDs are referenced by: -- the Clarion catalog itself +- the Loomweave catalog itself - Filigree issues and findings - guidance sheets - Wardline-derived reconciliation surfaces @@ -83,7 +83,7 @@ For later versions: - file moves no longer rot most cross-tool references - canonical naming aligns better with language-native reasoning -- identity translation stays with Clarion rather than leaking into sibling tools +- identity translation stays with Loomweave rather than leaking into sibling tools ### Negative @@ -103,6 +103,6 @@ For later versions: ## References -- [Clarion v0.1 system design](../v0.1/system-design.md) -- [Clarion v0.1 detailed design](../v0.1/detailed-design.md) -- [Clarion v0.1 design review](../../implementation/v0.1-reviews/pre-restructure/design-review.md) +- [Loomweave v0.1 system design](../v0.1/system-design.md) +- [Loomweave v0.1 detailed design](../v0.1/detailed-design.md) +- [Loomweave v0.1 design review](../../implementation/v0.1-reviews/pre-restructure/design-review.md) diff --git a/docs/clarion/adr/ADR-004-finding-exchange-format.md b/docs/loomweave/adr/ADR-004-finding-exchange-format.md similarity index 58% rename from docs/clarion/adr/ADR-004-finding-exchange-format.md rename to docs/loomweave/adr/ADR-004-finding-exchange-format.md index e06d0081..41552f87 100644 --- a/docs/clarion/adr/ADR-004-finding-exchange-format.md +++ b/docs/loomweave/adr/ADR-004-finding-exchange-format.md @@ -3,17 +3,17 @@ **Status**: Accepted **Date**: 2026-04-17 **Deciders**: qacona@gmail.com -**Context**: how Clarion emits findings into Filigree +**Context**: how Loomweave emits findings into Filigree ## Summary -Clarion will emit findings to Filigree using Filigree's existing `POST /api/v1/scan-results` JSON schema. Extension fields will be preserved under `metadata.clarion.*`. SARIF remains an import and translation path, not the primary interop contract with Filigree. +Loomweave will emit findings to Filigree using Filigree's existing `POST /api/v1/scan-results` JSON schema. Extension fields will be preserved under `metadata.loomweave.*`. SARIF remains an import and translation path, not the primary interop contract with Filigree. ## Context The design review and integration reconnaissance both found that Filigree does not ingest SARIF directly. Its production intake is a flat JSON format with specific field names, severity vocabulary, and a `metadata` extension slot. -Clarion still needs: +Loomweave still needs: - lossless enough transport for richer fields - compatibility with Filigree as it exists today @@ -25,32 +25,32 @@ We will treat Filigree's native scan-results intake as the canonical v0.1 findin Specifically: -- Clarion posts Filigree-native JSON to `/api/v1/scan-results` -- extension fields live under `metadata.clarion.*` +- Loomweave posts Filigree-native JSON to `/api/v1/scan-results` +- extension fields live under `metadata.loomweave.*` - SARIF import remains a translator workflow rather than the direct Filigree contract ## Alternatives Considered -### Alternative 1: Make SARIF the direct Clarion-to-Filigree contract +### Alternative 1: Make SARIF the direct Loomweave-to-Filigree contract **Description**: emit SARIF directly and require Filigree to ingest or extend toward SARIF. **Pros**: - aligns with broader tool ecosystem -- avoids a Clarion-specific mapping at first glance +- avoids a Loomweave-specific mapping at first glance **Cons**: - does not match Filigree's production reality -- would require sibling-tool work before Clarion can rely on it +- would require sibling-tool work before Loomweave can rely on it - increases v0.1 coordination risk -**Why rejected**: it makes Clarion depend on functionality Filigree does not currently have. +**Why rejected**: it makes Loomweave depend on functionality Filigree does not currently have. ### Alternative 2: Invent a suite-specific "SARIF-lite" -**Description**: define a new intermediate schema for the Loom suite. +**Description**: define a new intermediate schema for the Weft suite. **Pros**: @@ -69,13 +69,13 @@ Specifically: ### Positive -- Clarion can integrate with Filigree as it exists today -- richer Clarion metadata survives under a namespaced extension slot +- Loomweave can integrate with Filigree as it exists today +- richer Loomweave metadata survives under a namespaced extension slot - SARIF translation remains available where it is genuinely needed ### Negative -- Clarion owns an explicit mapping layer +- Loomweave owns an explicit mapping layer - some internal semantics need round-trip preservation helpers ### Neutral @@ -85,12 +85,12 @@ Specifically: ## Related Decisions - Related to: [ADR-003](./ADR-003-entity-id-scheme.md) -- [ADR-014](./ADR-014-filigree-registry-backend.md) — `file_id` resolution in `registry_backend: clarion` mode produces the `file_id` field this ADR's wire format references. -- [ADR-015](./ADR-015-wardline-filigree-emission.md) — the SARIF-to-Filigree-native translator maps SARIF inputs into this ADR's format; `metadata._properties.*` namespacing is consistent with `metadata.clarion.*`. +- [ADR-014](./ADR-014-filigree-registry-backend.md) — `file_id` resolution in `registry_backend: loomweave` mode produces the `file_id` field this ADR's wire format references. +- [ADR-015](./ADR-015-wardline-filigree-emission.md) — the SARIF-to-Filigree-native translator maps SARIF inputs into this ADR's format; `metadata._properties.*` namespacing is consistent with `metadata.loomweave.*`. ## References -- [Clarion v0.1 system design](../v0.1/system-design.md) -- [Clarion v0.1 detailed design](../v0.1/detailed-design.md) -- [Clarion v0.1 design review](../../implementation/v0.1-reviews/pre-restructure/design-review.md) -- [Clarion v0.1 integration recon](../../implementation/v0.1-reviews/pre-restructure/integration-recon.md) +- [Loomweave v0.1 system design](../v0.1/system-design.md) +- [Loomweave v0.1 detailed design](../v0.1/detailed-design.md) +- [Loomweave v0.1 design review](../../implementation/v0.1-reviews/pre-restructure/design-review.md) +- [Loomweave v0.1 integration recon](../../implementation/v0.1-reviews/pre-restructure/integration-recon.md) diff --git a/docs/clarion/adr/ADR-005-clarion-dir-tracking.md b/docs/loomweave/adr/ADR-005-loomweave-dir-tracking.md similarity index 65% rename from docs/clarion/adr/ADR-005-clarion-dir-tracking.md rename to docs/loomweave/adr/ADR-005-loomweave-dir-tracking.md index fb813d21..bc7166ac 100644 --- a/docs/clarion/adr/ADR-005-clarion-dir-tracking.md +++ b/docs/loomweave/adr/ADR-005-loomweave-dir-tracking.md @@ -1,32 +1,32 @@ -# ADR-005: `.clarion/` Directory Git-Tracking Policy +# ADR-005: `.loomweave/` Directory Git-Tracking Policy **Status**: Accepted; amended by ADR-041 **Date**: 2026-04-18 **Deciders**: qacona@gmail.com -**Context**: `clarion install` must write a `.gitignore` inside `.clarion/` that +**Context**: `loomweave install` must write a `.gitignore` inside `.loomweave/` that separates committed analysis state from volatile per-run artefacts. Sprint 1 WP1 Task 5 is the authoring trigger; before this ADR, the rules were only proposed in `docs/implementation/sprint-1/wp1-scaffold.md §UQ-WP1-04`. ## Summary -`.clarion/clarion.db` and `.clarion/config.json` are committed. WAL sidecars, +`.loomweave/loomweave.db` and `.loomweave/config.json` are committed. WAL sidecars, the shadow-DB intermediate, `tmp/`, `logs/`, and per-run raw LLM request/response -logs (`runs/*/log.jsonl`) are `.gitignore`d. `clarion.yaml` lives at the project +logs (`runs/*/log.jsonl`) are `.gitignore`d. `loomweave.yaml` lives at the project root and is tracked under the user's existing repo-root `.gitignore`, not under -`.clarion/.gitignore` (it's a user-edited config, not analysis state). +`.loomweave/.gitignore` (it's a user-edited config, not analysis state). ## Context -`.clarion/` mixes artefact kinds that want different tracking posture: +`.loomweave/` mixes artefact kinds that want different tracking posture: - **Shared analysis state** (entities, edges, briefings, guidance) — diff-friendly - via `clarion db export --textual`; solo-developer and small-team cases benefit + via `loomweave db export --textual`; solo-developer and small-team cases benefit from having briefings versioned alongside the code they describe (`detailed-design.md §3 File layout`). - **Runtime write-ahead files** (`*-wal`, `*-shm`) — SQLite bookkeeping that is process-local and meaningless on a different machine. -- **Shadow DB** (`clarion.db.new`, `*.shadow.db`) — ADR-011's `--shadow-db` +- **Shadow DB** (`loomweave.db.new`, `*.shadow.db`) — ADR-011's `--shadow-db` intermediate; deleted on successful atomic rename, would leak as junk otherwise. - **Per-run LLM bodies** (`runs//log.jsonl`) — raw request/response @@ -34,14 +34,14 @@ root and is tracked under the user's existing repo-root `.gitignore`, not under but not appropriate to commit to a public repo. - **Scratch** (`tmp/`, `logs/`) — volatile by definition. -Without this ADR, `clarion install` has no normative place to look up the rules, +Without this ADR, `loomweave install` has no normative place to look up the rules, and every developer's install produces their own variant `.gitignore` by accident. ## Decision -`clarion install` writes `.clarion/.gitignore` with the following contents +`loomweave install` writes `.loomweave/.gitignore` with the following contents (verbatim — the literal file lives at -`crates/clarion-cli/src/install.rs` and ships as the v0.1 baseline): +`crates/loomweave-cli/src/install.rs` and ships as the v0.1 baseline): ``` *-wal @@ -57,16 +57,16 @@ runs/*/log.jsonl ### Tracked -- `.clarion/clarion.db` — the main analysis store. SQLite diffs poorly; the - `clarion db export --textual` + `clarion db merge-helper` pattern (detailed +- `.loomweave/loomweave.db` — the main analysis store. SQLite diffs poorly; the + `loomweave db export --textual` + `loomweave db merge-helper` pattern (detailed design §3 File layout) handles the team case. -- `.clarion/config.json` — small, human-readable internal state (schema +- `.loomweave/config.json` — small, human-readable internal state (schema version, last run IDs). -- `.clarion/.gitignore` itself — this file. -- `.clarion/runs//config.yaml` — the snapshot of `clarion.yaml` at run +- `.loomweave/.gitignore` itself — this file. +- `.loomweave/runs//config.yaml` — the snapshot of `loomweave.yaml` at run time. Material for provenance replay. -- `.clarion/runs//stats.json` — run statistics. -- `.clarion/runs//partial.json` — present only for partial runs; +- `.loomweave/runs//stats.json` — run statistics. +- `.loomweave/runs//partial.json` — present only for partial runs; material for `--resume`. ### Excluded @@ -76,18 +76,18 @@ runs/*/log.jsonl - `tmp/` and `logs/` (volatile scratch). - `runs/*/log.jsonl` (raw LLM bodies — audit-local, not commit-appropriate). -### Out of scope for `.clarion/.gitignore` +### Out of scope for `.loomweave/.gitignore` -- `clarion.yaml` (the user-edited config) lives at the *project root*, not - inside `.clarion/`. Its tracking is governed by the project's own repo-root +- `loomweave.yaml` (the user-edited config) lives at the *project root*, not + inside `.loomweave/`. Its tracking is governed by the project's own repo-root `.gitignore`, which is the user's concern. Default posture: tracked. ### Opt-out for users who don't want the DB committed -`clarion.yaml:storage.commit_db: false` (post-Sprint-1 knob; WP6 authors the -full `clarion.yaml` schema). When false, Clarion writes an additional -`.clarion/.gitignore` line excluding `clarion.db`, and emits -`clarion db sync push/pull` commands. Not implemented in Sprint 1; the knob +`loomweave.yaml:storage.commit_db: false` (post-Sprint-1 knob; WP6 authors the +full `loomweave.yaml` schema). When false, Loomweave writes an additional +`.loomweave/.gitignore` line excluding `loomweave.db`, and emits +`loomweave db sync push/pull` commands. Not implemented in Sprint 1; the knob is documented here so the future change has a home. ## Alternatives Considered @@ -104,7 +104,7 @@ committed is unbounded. ### Alternative 2: commit nothing -**Pros**: simplest — `.clarion/` becomes entirely machine-local. +**Pros**: simplest — `.loomweave/` becomes entirely machine-local. **Cons**: loses the "shared analysis state" benefit — briefings and guidance are derived outputs that are expensive to rebuild. Small teams especially @@ -118,7 +118,7 @@ analysis only opt out via `storage.commit_db: false`. **Pros**: keeps small-git-diff UX (LFS handles the binary file). -**Cons**: requires git-lfs installed on every developer machine; makes `clarion +**Cons**: requires git-lfs installed on every developer machine; makes `loomweave install` a multi-tool setup; adds failure modes (lfs server availability, large file policy). v0.1 target workflows are solo/small-team where the straight-commit path works; LFS is a v0.2+ knob. @@ -129,7 +129,7 @@ path works; LFS is a v0.2+ knob. ### Positive -- Every `clarion install` produces the same `.gitignore`. Ends per-developer +- Every `loomweave install` produces the same `.gitignore`. Ends per-developer drift on "what should be committed." - WAL sidecars cannot accidentally land in a commit. - Raw LLM bodies stay local to the developer that ran the analysis. @@ -139,10 +139,10 @@ path works; LFS is a v0.2+ knob. ### Negative - Committed SQLite DBs diff poorly by default. Mitigation: the - `clarion db export --textual` / merge-helper path (detailed-design §3) is + `loomweave db export --textual` / merge-helper path (detailed-design §3) is the documented escape hatch. -- Adding a new excluded pattern requires either a Clarion release or a - user-side `.clarion/.gitignore` edit. The post-v0.1 plan is to keep this +- Adding a new excluded pattern requires either a Loomweave release or a + user-side `.loomweave/.gitignore` edit. The post-v0.1 plan is to keep this file tool-owned; users adding their own ignores put them in the repo-root `.gitignore`, not here. @@ -156,7 +156,7 @@ path works; LFS is a v0.2+ knob. - [ADR-011](./ADR-011-writer-actor-concurrency.md) — names the shadow-DB intermediate; this ADR excludes it from git. - [ADR-014](./ADR-014-filigree-registry-backend.md) — cross-tool references - rely on `clarion.db` being available to readers (Filigree, Wardline); the + rely on `loomweave.db` being available to readers (Filigree, Wardline); the commit-by-default posture keeps those references resolvable across machines. ## References diff --git a/docs/clarion/adr/ADR-006-clustering-algorithm.md b/docs/loomweave/adr/ADR-006-clustering-algorithm.md similarity index 75% rename from docs/clarion/adr/ADR-006-clustering-algorithm.md rename to docs/loomweave/adr/ADR-006-clustering-algorithm.md index 2e4230a0..83cb85f7 100644 --- a/docs/clarion/adr/ADR-006-clustering-algorithm.md +++ b/docs/loomweave/adr/ADR-006-clustering-algorithm.md @@ -7,14 +7,14 @@ ## Summary -Phase 3 clustering runs **Leiden** over a directed, weighted subgraph of module-level `imports` + `calls` edges, with edge weights equal to reference counts. The algorithm is seeded (for determinism), filters out clusters smaller than `min_cluster_size` (default 3), and records `modularity_score` on each resulting `subsystem` entity. ADR-032 amends the original Louvain fallback into a deterministic **weighted-components** fallback, selectable via `clarion.yaml:analysis.clustering.algorithm: weighted_components` and used automatically only when Leiden emits zero or one community and the fallback emits more. Both algorithms produce subsystem entities under the core-reserved `subsystem` kind (ADR-022), identified as `core:subsystem:{cluster_hash}` (ADR-003). No hard modularity pass/fail threshold ships in v0.1 — the score is reported, not enforced. +Phase 3 clustering runs **Leiden** over a directed, weighted subgraph of module-level `imports` + `calls` edges, with edge weights equal to reference counts. The algorithm is seeded (for determinism), filters out clusters smaller than `min_cluster_size` (default 3), and records `modularity_score` on each resulting `subsystem` entity. ADR-032 amends the original Louvain fallback into a deterministic **weighted-components** fallback, selectable via `loomweave.yaml:analysis.clustering.algorithm: weighted_components` and used automatically only when Leiden emits zero or one community and the fallback emits more. Both algorithms produce subsystem entities under the core-reserved `subsystem` kind (ADR-022), identified as `core:subsystem:{cluster_hash}` (ADR-003). No hard modularity pass/fail threshold ships in v0.1 — the score is reported, not enforced. ## Context -Phase 3 is the clustering step in `clarion analyze` (system-design §6, `Phase3: Clustering (core, no LLM)`). It reads the module-level `imports` and `calls` edges emitted by plugins in Phase 1 and produces candidate `subsystem` entities that Phase 6 then passes to Opus for synthesised subsystem briefings. Two facts make algorithm choice load-bearing: +Phase 3 is the clustering step in `loomweave analyze` (system-design §6, `Phase3: Clustering (core, no LLM)`). It reads the module-level `imports` and `calls` edges emitted by plugins in Phase 1 and produces candidate `subsystem` entities that Phase 6 then passes to Opus for synthesised subsystem briefings. Two facts make algorithm choice load-bearing: 1. **Downstream cost.** Phase 6 makes one Opus call per subsystem (system-design `Writing Cadence` + detailed-design §4 profile presets). Clustering quality drives whether Phase 6 fires an Opus call per *meaningful* subsystem or wastes Opus budget on noise clusters. At elspeth scale (~1,100 modules, ~100k–200k entities), a 20-subsystem output versus a 60-subsystem output is a 3× Opus-cost delta. -2. **Query quality.** MCP tools (`enter_subsystem`, `find_similar`) and Phase-7 findings (`CLA-FACT-TIER-SUBSYSTEM-MIXING`) all depend on "this module belongs to subsystem X" being coherent. A cluster whose members aren't actually connected is worse than no cluster — queries return misleading results. +2. **Query quality.** MCP tools (`enter_subsystem`, `find_similar`) and Phase-7 findings (`LMWV-FACT-TIER-SUBSYSTEM-MIXING`) all depend on "this module belongs to subsystem X" being coherent. A cluster whose members aren't actually connected is worse than no cluster — queries return misleading results. The design-review noted "Leiden on imports+calls" as the intended direction but left the algorithm's edge definition, weighting, and fallback story unspecified. The scope-commitment memo promoted this to P0 because Phase 6's Opus spend depends directly on it. @@ -31,18 +31,18 @@ The trade-off: Leiden is less widely implemented than Louvain in the Rust ecosys - **Direction**: directed. `imports(A, B)` and `imports(B, A)` are distinct edges. Leiden is configured for directed graphs (directed modularity). - **Weight**: `reference_count` — the number of source-level import statements or call sites between two modules. Rationale: a single import is not the same signal as 47 calls; weighted modularity is the standard treatment. - **Filter**: self-loops removed; `python:unresolved:*` placeholder entities excluded. -- **Source of truth**: configurable via `clarion.yaml:analysis.clustering.edge_types` (default `[imports, calls]`) and `weight_by` (default `reference_count`). +- **Source of truth**: configurable via `loomweave.yaml:analysis.clustering.edge_types` (default `[imports, calls]`) and `weight_by` (default `reference_count`). ### Algorithm: Leiden (default) - **Implementation**: either vendored (~400 LoC) or a maintained crate resolved at implementation time. The design commits to the algorithm; the implementation source is a code-level detail recorded in the Cargo.lock at release. -- **Deterministic**: RNG seeded from `clarion.yaml:analysis.clustering.seed` (default `42`). Seed recorded in `runs//stats.json`. -- **Resolution parameter**: `γ = 1.0` default (standard modularity). Configurable via `clarion.yaml:analysis.clustering.resolution` if operators want finer or coarser communities. +- **Deterministic**: RNG seeded from `loomweave.yaml:analysis.clustering.seed` (default `42`). Seed recorded in `runs//stats.json`. +- **Resolution parameter**: `γ = 1.0` default (standard modularity). Configurable via `loomweave.yaml:analysis.clustering.resolution` if operators want finer or coarser communities. - **Iteration cap**: 100 passes (configurable). Most runs converge in <10. ### Fallback: Weighted Components -- **Trigger**: `clarion.yaml:analysis.clustering.algorithm: weighted_components` explicit selection, or runtime auto-fallback when `algorithm: leiden` returns zero or one community and weighted components returns more communities. +- **Trigger**: `loomweave.yaml:analysis.clustering.algorithm: weighted_components` explicit selection, or runtime auto-fallback when `algorithm: leiden` returns zero or one community and weighted components returns more communities. - **Implementation**: deterministic connected components over edges whose weight is at least the average positive edge weight. - **Behaviour delta**: weighted components is an explainable local grouping fallback, not modularity optimisation. `properties.algorithm` and `runs.stats.clustering.algorithm` record the algorithm actually used. @@ -62,7 +62,7 @@ Each cluster above `min_cluster_size` (default 3) becomes a `subsystem` entity ( ### Quality assessment -No hard modularity threshold passes/fails in v0.1. Modularity scores below `analysis.clustering.weak_modularity_threshold` (default `0.3`, set `0.0` to disable the finding) emit `CLA-FACT-CLUSTERING-WEAK-MODULARITY` (severity INFO). Operators see the signal but Phase 3 does not refuse to emit clusters. Block C1's cost-model spike will validate whether weak-modularity subsystems produce useful Opus output or wasted cost; a v0.2 decision may add a hard threshold with `--refuse-weak-modularity` semantics. +No hard modularity threshold passes/fails in v0.1. Modularity scores below `analysis.clustering.weak_modularity_threshold` (default `0.3`, set `0.0` to disable the finding) emit `LMWV-FACT-CLUSTERING-WEAK-MODULARITY` (severity INFO). Operators see the signal but Phase 3 does not refuse to emit clusters. Block C1's cost-model spike will validate whether weak-modularity subsystems produce useful Opus output or wasted cost; a v0.2 decision may add a hard threshold with `--refuse-weak-modularity` semantics. ## Alternatives Considered @@ -108,7 +108,7 @@ Take weakly-connected components of the imports+calls graph as subsystems. ### Alternative 5: Manifest-declared subsystems -Operators declare subsystems in `clarion.yaml`; clustering is skipped. +Operators declare subsystems in `loomweave.yaml`; clustering is skipped. **Pros**: deterministic, operator-controlled. @@ -120,7 +120,7 @@ Operators declare subsystems in `clarion.yaml`; clustering is skipped. ### Positive -- Leiden's connected-community guarantee makes `CLA-FACT-TIER-SUBSYSTEM-MIXING` and subsystem-navigation MCP tools semantically sound — a "subsystem" is always a coherent group. +- Leiden's connected-community guarantee makes `LMWV-FACT-TIER-SUBSYSTEM-MIXING` and subsystem-navigation MCP tools semantically sound — a "subsystem" is always a coherent group. - Phase 6's per-subsystem Opus call lands against meaningful clusters, not noise. Cost spend tracks semantically-relevant subsystems. - Determinism (seeded RNG) makes `--resume` and cross-run diffing of subsystem structure possible. Operators see "this module left subsystem X" as a real signal, not RNG noise. - Weighted-components fallback absorbs implementation risk without redesigning the decision. @@ -128,12 +128,12 @@ Operators declare subsystems in `clarion.yaml`; clustering is skipped. ### Negative - Leiden implementations in Rust are less mature than simpler local graph cuts. Vendoring ~400 LoC or depending on a possibly-young crate is real implementation risk. Mitigation: the weighted-components fallback exists specifically for this. -- Modularity is a quality *signal* not a hard threshold. Weak clusterings ship. Operators reading `CLA-FACT-CLUSTERING-WEAK-MODULARITY` may not know how to act on it. Mitigation: v0.2 validation work (and the C1 cost-model spike) clarifies whether a hard threshold is warranted. +- Modularity is a quality *signal* not a hard threshold. Weak clusterings ship. Operators reading `LMWV-FACT-CLUSTERING-WEAK-MODULARITY` may not know how to act on it. Mitigation: v0.2 validation work (and the C1 cost-model spike) clarifies whether a hard threshold is warranted. - Module-level only. Classes, functions, and decorators don't get their own clusters in v0.1. Operators wanting finer grains (sub-module clusters) get the "module is the unit" limitation — noted in release documentation. ### Neutral -- Algorithm selection is a config, not a code change. Switching from Leiden to weighted-components mid-project is a `clarion.yaml` edit + reindex. +- Algorithm selection is a config, not a code change. Switching from Leiden to weighted-components mid-project is a `loomweave.yaml` edit + reindex. - Resolution parameter (`γ`) is exposed for operators with unusual codebases; most never touch it. - Subsystem entity IDs are content-addressed over sorted member IDs; renaming a module changes the hash. This is correct — "the subsystem" changed composition, so a new identity is right. Filigree issues tagged to the old ID follow the ADR-003 EntityAlias story. @@ -147,7 +147,7 @@ Operators declare subsystems in `clarion.yaml`; clustering is skipped. ## References - [Traag, Waltman, van Eck 2019 — "From Louvain to Leiden: guaranteeing well-connected communities"](https://www.nature.com/articles/s41598-019-41695-z) — the paper defining Leiden's refinement step and the disconnected-community defect in Louvain. -- [Clarion v0.1 system design §2, §6 (Phase 3 Clustering)](../v0.1/system-design.md) — Phase 3 position in the pipeline; core-ownership of clustering. -- [Clarion v0.1 detailed design §4 (clarion.yaml clustering config)](../v0.1/detailed-design.md) (lines 937-941) — the config surface this ADR commits to. -- [Clarion v0.1 detailed design Appendix — petgraph dependency note](../v0.1/detailed-design.md) (line 1644) — implementation-level note on vendoring. -- [Clarion v0.1 requirements — REQ-CATALOG-06](../v0.1/requirements.md) — structural discovery requirement this ADR serves. +- [Loomweave v0.1 system design §2, §6 (Phase 3 Clustering)](../v0.1/system-design.md) — Phase 3 position in the pipeline; core-ownership of clustering. +- [Loomweave v0.1 detailed design §4 (loomweave.yaml clustering config)](../v0.1/detailed-design.md) (lines 937-941) — the config surface this ADR commits to. +- [Loomweave v0.1 detailed design Appendix — petgraph dependency note](../v0.1/detailed-design.md) (line 1644) — implementation-level note on vendoring. +- [Loomweave v0.1 requirements — REQ-CATALOG-06](../v0.1/requirements.md) — structural discovery requirement this ADR serves. diff --git a/docs/clarion/adr/ADR-007-summary-cache-key.md b/docs/loomweave/adr/ADR-007-summary-cache-key.md similarity index 77% rename from docs/clarion/adr/ADR-007-summary-cache-key.md rename to docs/loomweave/adr/ADR-007-summary-cache-key.md index 8b316caf..4982ec39 100644 --- a/docs/clarion/adr/ADR-007-summary-cache-key.md +++ b/docs/loomweave/adr/ADR-007-summary-cache-key.md @@ -7,7 +7,7 @@ ## Summary -The summary cache is keyed on **five components**: `(entity_id, content_hash, prompt_template_id, model_tier, guidance_fingerprint)`. Any component mismatch is a miss; all five must match for a hit. This makes **syntactic staleness impossible** — any code edit, template change, model tier rename, or guidance-sheet edit changes a component and forces re-summarisation. Three **semantic-staleness** paths the key alone doesn't catch (graph-neighbourhood drift, model-identity drift, guidance-worldview drift) are handled with targeted mechanisms: the cache row stores neighbourhood statistics and is flagged `stale_semantic: true` when they shift by >50%; model-identity drift is already caught because the `model_tier` component stores the concrete model ID (not the tier name); guidance-worldview drift is surfaced via `CLA-FACT-GUIDANCE-CHURN-STALE` findings combined with **churn-eager invalidation** — cache rows whose `guidance_fingerprint` includes a churn-stale sheet are invalidated immediately rather than waiting for TTL. A **TTL backstop** (180 days default) invalidates any row older than the bound on next query. +The summary cache is keyed on **five components**: `(entity_id, content_hash, prompt_template_id, model_tier, guidance_fingerprint)`. Any component mismatch is a miss; all five must match for a hit. This makes **syntactic staleness impossible** — any code edit, template change, model tier rename, or guidance-sheet edit changes a component and forces re-summarisation. Three **semantic-staleness** paths the key alone doesn't catch (graph-neighbourhood drift, model-identity drift, guidance-worldview drift) are handled with targeted mechanisms: the cache row stores neighbourhood statistics and is flagged `stale_semantic: true` when they shift by >50%; model-identity drift is already caught because the `model_tier` component stores the concrete model ID (not the tier name); guidance-worldview drift is surfaced via `LMWV-FACT-GUIDANCE-CHURN-STALE` findings combined with **churn-eager invalidation** — cache rows whose `guidance_fingerprint` includes a churn-stale sheet are invalidated immediately rather than waiting for TTL. A **TTL backstop** (180 days default) invalidates any row older than the bound on next query. ## Context @@ -43,7 +43,7 @@ All five combined are the `summary_cache` table's PRIMARY KEY (`detailed-design. ### TTL backstop -Rows older than `clarion.yaml:llm_policy.caching.max_age_days` (default **180 days**, configurable) are invalidated unconditionally on next query. The TTL catches semantic-staleness paths the key alone doesn't see, bounds worst-case stale-summary age, and forces refresh when a full Anthropic model generation has shipped. +Rows older than `loomweave.yaml:llm_policy.caching.max_age_days` (default **180 days**, configurable) are invalidated unconditionally on next query. The TTL catches semantic-staleness paths the key alone doesn't see, bounds worst-case stale-summary age, and forces refresh when a full Anthropic model generation has shipped. 180 days chosen as the intersection of: @@ -53,15 +53,15 @@ Rows older than `clarion.yaml:llm_policy.caching.max_age_days` (default **180 da ### Churn-eager invalidation -When `CLA-FACT-GUIDANCE-CHURN-STALE` fires (stale `critical: true` guidance sheet on a high-churn entity), every `summary_cache` row whose `guidance_fingerprint` includes that sheet is invalidated **eagerly**, not at TTL. The operator now sees the churn-stale finding *and* experiences cost pressure (the next few analyses cache-miss on affected entities), which creates an action-forcing loop. +When `LMWV-FACT-GUIDANCE-CHURN-STALE` fires (stale `critical: true` guidance sheet on a high-churn entity), every `summary_cache` row whose `guidance_fingerprint` includes that sheet is invalidated **eagerly**, not at TTL. The operator now sees the churn-stale finding *and* experiences cost pressure (the next few analyses cache-miss on affected entities), which creates an action-forcing loop. -Implementation: `CLA-FACT-GUIDANCE-CHURN-STALE` emission Phase-7 runs a `DELETE FROM summary_cache WHERE guidance_fingerprint LIKE '%{sheet_hash}%'` under the writer actor. Bounded by the number of affected rows; O(affected_rows) time. +Implementation: `LMWV-FACT-GUIDANCE-CHURN-STALE` emission Phase-7 runs a `DELETE FROM summary_cache WHERE guidance_fingerprint LIKE '%{sheet_hash}%'` under the writer actor. Bounded by the number of affected rows; O(affected_rows) time. ### Neighbourhood-drift flag -Each cache row additionally stores `caller_count` and `fan_out` at the time of summary generation. On each read, the consult path compares these against the current graph state. If either has shifted by more than `clarion.yaml:llm_policy.caching.neighborhood_drift_threshold` (default **50%**), the briefing is returned with `stale_semantic: true` in the response envelope. Agents consuming the briefing downweight `risks` and `relationships` claims accordingly. +Each cache row additionally stores `caller_count` and `fan_out` at the time of summary generation. On each read, the consult path compares these against the current graph state. If either has shifted by more than `loomweave.yaml:llm_policy.caching.neighborhood_drift_threshold` (default **50%**), the briefing is returned with `stale_semantic: true` in the response envelope. Agents consuming the briefing downweight `risks` and `relationships` claims accordingly. -This is a *flag*, not a miss. Forcing a miss on every graph-topology change would tank NFR-COST-02's hit rate. The flag lets agents reason about staleness; the next `clarion analyze` refreshes flagged entities. +This is a *flag*, not a miss. Forcing a miss on every graph-topology change would tank NFR-COST-02's hit rate. The flag lets agents reason about staleness; the next `loomweave analyze` refreshes flagged entities. ### Explicit non-decisions (v0.2 territory) @@ -69,7 +69,7 @@ Per Q1 scope commitment (`v0.1-scope-commitments.md:68`), summary-cache optimisa - Cross-session cache warm-up (pre-computing summaries for unvisited entities). - Semantic drift detection beyond the neighbourhood-stats flag (e.g., downstream-impact-analysis-based invalidation). -- Distributed / shared-team cache (useful only once `.clarion/clarion.db` is routinely git-committed and merged across team members — v0.2 concern). +- Distributed / shared-team cache (useful only once `.loomweave/loomweave.db` is routinely git-committed and merged across team members — v0.2 concern). - Per-agent cache affinity / multi-tenant scoping. v0.1 ships the SQLite-backed `summary_cache` table with the 5-part key, TTL backstop, churn-eager invalidation, and neighbourhood-drift flag. Everything else waits for v0.2. @@ -136,20 +136,20 @@ Store `"sonnet"` instead of `"claude-sonnet-4-6"`. - Syntactic staleness is impossible. Every change that *should* invalidate flips a component. - TTL backstop bounds worst-case semantic-staleness exposure. 180 days is the effective ceiling on any briefing's freshness. -- Churn-eager invalidation creates cost pressure on guidance neglect. Operators feel the friction of stale critical guidance in their Anthropic bill, not just in `CLA-FACT-*` findings. +- Churn-eager invalidation creates cost pressure on guidance neglect. Operators feel the friction of stale critical guidance in their Anthropic bill, not just in `LMWV-FACT-*` findings. - Neighbourhood-drift flag gives agents actionable staleness information without tanking hit rate. - Cache-miss reasoning is explainable. A 5-tuple mismatch can be inspected: "which component changed?" → actionable diagnosis. ### Negative - 5-component composite primary key has nontrivial index overhead. Lookup is a 5-way PK scan; reasonable performance on SQLite but not free. Mitigation: the PK is the natural access pattern (no secondary indexes on the cache needed). -- At ~200k rows × JSON summaries, the `summary_cache` table is the largest single table in `clarion.db` (500 MB – 2 GB range). Operators on constrained disks need to know. Mitigation: `clarion cache prune --older-than ` CLI exists for manual reduction. +- At ~200k rows × JSON summaries, the `summary_cache` table is the largest single table in `loomweave.db` (500 MB – 2 GB range). Operators on constrained disks need to know. Mitigation: `loomweave cache prune --older-than ` CLI exists for manual reduction. - NFR-COST-02's 95% hit-rate assumption is design-time. Block C1 is the validation; this ADR ships the design the spike measures. If Block C1 shows hit rate materially below 95%, the decision surface is whether to raise the target (accept lower hit rate) or adjust the key / TTL. - Neighbourhood-drift flag relies on agents correctly downweighting `stale_semantic: true` briefings. If agent implementations ignore the flag, correctness degrades silently. Mitigation: the response-envelope convention is documented; MCP tool responses carry the flag prominently. ### Neutral -- The cache is SQLite-backed, same store as entities. Clarion's git-commit posture (ADR-005) keeps the cache available across developer machines; team members pulling the committed DB benefit from each other's summaries. +- The cache is SQLite-backed, same store as entities. Loomweave's git-commit posture (ADR-005) keeps the cache available across developer machines; team members pulling the committed DB benefit from each other's summaries. - Churn-eager invalidation lands through the writer actor (ADR-011). Large invalidations (thousands of rows at once) are per-N-files transactions just like analyse writes. - `model_tier` storing the concrete model ID is not a surprise — it's the data that was already being stored; the ADR is just making "why that's correct" explicit. @@ -162,8 +162,8 @@ Store `"sonnet"` instead of `"claude-sonnet-4-6"`. ## References -- [Clarion v0.1 detailed design §4 (Summary cache key design)](../v0.1/detailed-design.md) (lines 965-983) — the target this ADR formalises. -- [Clarion v0.1 detailed design §3 schema — `summary_cache` table](../v0.1/detailed-design.md) (lines 679-691) — storage shape. -- [Clarion v0.1 requirements — NFR-COST-01, NFR-COST-02, NFR-PERF-01](../v0.1/requirements.md) — cost/performance envelopes this cache design serves. -- [Clarion v0.1 scope commitments — Q1](../../implementation/v0.1-scope-plans/v0.1-scope-commitments.md) (line 68) — v0.2 deferrals: cache optimisations beyond the simple shape. -- [Clarion v0.1 scope commitments — Validation](../../implementation/v0.1-scope-plans/v0.1-scope-commitments.md) (lines 237-247) — empirical validation plan for NFR-COST-01/02 and the 95% hit-rate assumption this ADR depends on. +- [Loomweave v0.1 detailed design §4 (Summary cache key design)](../v0.1/detailed-design.md) (lines 965-983) — the target this ADR formalises. +- [Loomweave v0.1 detailed design §3 schema — `summary_cache` table](../v0.1/detailed-design.md) (lines 679-691) — storage shape. +- [Loomweave v0.1 requirements — NFR-COST-01, NFR-COST-02, NFR-PERF-01](../v0.1/requirements.md) — cost/performance envelopes this cache design serves. +- [Loomweave v0.1 scope commitments — Q1](../../implementation/v0.1-scope-plans/v0.1-scope-commitments.md) (line 68) — v0.2 deferrals: cache optimisations beyond the simple shape. +- [Loomweave v0.1 scope commitments — Validation](../../implementation/v0.1-scope-plans/v0.1-scope-commitments.md) (lines 237-247) — empirical validation plan for NFR-COST-01/02 and the 95% hit-rate assumption this ADR depends on. diff --git a/docs/clarion/adr/ADR-011-writer-actor-concurrency.md b/docs/loomweave/adr/ADR-011-writer-actor-concurrency.md similarity index 59% rename from docs/clarion/adr/ADR-011-writer-actor-concurrency.md rename to docs/loomweave/adr/ADR-011-writer-actor-concurrency.md index 2d132935..19071c47 100644 --- a/docs/clarion/adr/ADR-011-writer-actor-concurrency.md +++ b/docs/loomweave/adr/ADR-011-writer-actor-concurrency.md @@ -3,24 +3,24 @@ **Status**: Accepted; amended by ADR-041 **Date**: 2026-04-18 **Deciders**: qacona@gmail.com -**Context**: SQLite concurrency model for `clarion analyze` + `clarion serve` against a shared `.clarion/clarion.db`; design-review `§2.2` flagged the original single-transaction posture as CRITICAL +**Context**: SQLite concurrency model for `loomweave analyze` + `loomweave serve` against a shared `.loomweave/loomweave.db`; design-review `§2.2` flagged the original single-transaction posture as CRITICAL ## Summary -Clarion uses **one writer-actor per process** — a single `tokio::task` owning the sole write `rusqlite::Connection`, fed by a bounded `mpsc::Sender` with backpressure. `clarion analyze` commits every **N files** (default 50, configurable) rather than the full run in one transaction. Reader tasks (MCP tool calls, HTTP API handlers, plugin processes) take read-only connections from a `deadpool-sqlite` pool (default max 16). A `clarion analyze --shadow-db` flag writes to `.clarion/clarion.db.new` and atomic-renames on completion for operators who need zero-stale-read snapshots during long runs. This shape assumes WAL mode plus per-batch transactions handles realistic analyze+serve contention without lock starvation — an assumption **not empirically validated at v0.1 scale** and called out here as a known gap for v0.2 verification. +Loomweave uses **one writer-actor per process** — a single `tokio::task` owning the sole write `rusqlite::Connection`, fed by a bounded `mpsc::Sender` with backpressure. `loomweave analyze` commits every **N files** (default 50, configurable) rather than the full run in one transaction. Reader tasks (MCP tool calls, HTTP API handlers, plugin processes) take read-only connections from a `deadpool-sqlite` pool (default max 16). A `loomweave analyze --shadow-db` flag writes to `.loomweave/loomweave.db.new` and atomic-renames on completion for operators who need zero-stale-read snapshots during long runs. This shape assumes WAL mode plus per-batch transactions handles realistic analyze+serve contention without lock starvation — an assumption **not empirically validated at v0.1 scale** and called out here as a known gap for v0.2 verification. ## Context The original design review (`design-review.md §2.2`, flagged CRITICAL) found the SQLite concurrency claims incoherent: -> §4 claims WAL mode "supports concurrent reads during writes" and says `clarion analyze` "holds the writer lock for the duration of the batch." The example run in §6 shows a 38-minute batch. These are incompatible: WAL grows unboundedly during a long writer; checkpointing cannot complete while readers pinned to the pre-analyze snapshot hold the old page. +> §4 claims WAL mode "supports concurrent reads during writes" and says `loomweave analyze` "holds the writer lock for the duration of the batch." The example run in §6 shows a 38-minute batch. These are incompatible: WAL grows unboundedly during a long writer; checkpointing cannot complete while readers pinned to the pre-analyze snapshot hold the old page. The flagged options were (a) writer-actor + per-N-files transactions and (b) shadow-DB + atomic swap. The detailed-design §3 Concurrency section (`detailed-design.md:758-769`) adopted both: writer-actor is the default, shadow-DB is an opt-in flag. This ADR formalises that adoption and names the unvalidated-assumption asterisk. The concurrency story matters because: -- `clarion analyze` at elspeth scale produces ~100k–200k code entities, ~500k–1M edges, ~200k summary-cache rows (`detailed-design.md:773-778`). Transactions must be short enough that WAL growth stays bounded. -- `clarion serve` must keep answering reads while `clarion analyze` is ingesting (operator might run a long analyze in one terminal and `clarion consult` in another). Writer-actor preserves this because WAL lets readers advance past committed transactions without blocking. +- `loomweave analyze` at elspeth scale produces ~100k–200k code entities, ~500k–1M edges, ~200k summary-cache rows (`detailed-design.md:773-778`). Transactions must be short enough that WAL growth stays bounded. +- `loomweave serve` must keep answering reads while `loomweave analyze` is ingesting (operator might run a long analyze in one terminal and `loomweave consult` in another). Writer-actor preserves this because WAL lets readers advance past committed transactions without blocking. - Consult-mode agents write summary-cache entries during queries; those writes are tiny and sparse but must share the writer actor's commit discipline. - `--resume` semantics depend on transaction granularity. If the run crashes mid-batch, committed transactions persist; the resume path restarts at the first uncommitted file. @@ -28,11 +28,11 @@ The concurrency story matters because: ### Writer-actor model -One `tokio::task` per process owns the sole write `rusqlite::Connection`. All mutations route through an `mpsc::Sender` with a bounded channel (default 256 operations; backpressure when full). There is no in-process write contention because there is exactly one writer; there is no cross-process writer contention because `clarion analyze` and `clarion serve` each instantiate their own writer-actor — but both processes writing to the same `clarion.db` simultaneously produces stale-snapshot reads for `serve` (addressed via `--shadow-db`, below). +One `tokio::task` per process owns the sole write `rusqlite::Connection`. All mutations route through an `mpsc::Sender` with a bounded channel (default 256 operations; backpressure when full). There is no in-process write contention because there is exactly one writer; there is no cross-process writer contention because `loomweave analyze` and `loomweave serve` each instantiate their own writer-actor — but both processes writing to the same `loomweave.db` simultaneously produces stale-snapshot reads for `serve` (addressed via `--shadow-db`, below). ### Per-N-files transactions -`clarion analyze` commits every **N files** (default 50) rather than the full run in one transaction. The constant is configurable via `clarion.yaml:storage.tx_batch_size` (floor 10, no declared ceiling but operators changing this should know WAL growth tracks linearly). +`loomweave analyze` commits every **N files** (default 50) rather than the full run in one transaction. The constant is configurable via `loomweave.yaml:storage.tx_batch_size` (floor 10, no declared ceiling but operators changing this should know WAL growth tracks linearly). Justification: @@ -43,10 +43,10 @@ Justification: ### Reader pool -Read-only connections come from `deadpool-sqlite` with `max_size` 16 by default, configurable via `clarion.yaml:storage.reader_pool_max`. Readers include: +Read-only connections come from `deadpool-sqlite` with `max_size` 16 by default, configurable via `loomweave.yaml:storage.reader_pool_max`. Readers include: - MCP tool call handlers (consult mode) -- HTTP API handlers (`clarion serve`) +- HTTP API handlers (`loomweave serve`) - Plugin subprocesses reading prior-run state (rare) - The markdown renderer @@ -63,27 +63,27 @@ PRAGMA busy_timeout = 5000; # ms PRAGMA wal_autocheckpoint = 1000; # pages ``` -`synchronous = NORMAL` trades slightly weaker crash-durability (the last transaction in WAL may be lost on a power loss) for substantially faster commits. Acceptable for local-first workloads; operators on shared-infrastructure hosts with UPS can raise to `FULL` via `clarion.yaml:storage.synchronous`. +`synchronous = NORMAL` trades slightly weaker crash-durability (the last transaction in WAL may be lost on a power loss) for substantially faster commits. Acceptable for local-first workloads; operators on shared-infrastructure hosts with UPS can raise to `FULL` via `loomweave.yaml:storage.synchronous`. -### Shadow-DB opt-in (`clarion analyze --shadow-db`) +### Shadow-DB opt-in (`loomweave analyze --shadow-db`) -`clarion analyze --shadow-db` writes to `.clarion/clarion.db.new` (WAL files beside it). On completion, atomic-renames over `clarion.db`. While analyze runs, `clarion serve` reads the pre-run snapshot with zero staleness. +`loomweave analyze --shadow-db` writes to `.loomweave/loomweave.db.new` (WAL files beside it). On completion, atomic-renames over `loomweave.db`. While analyze runs, `loomweave serve` reads the pre-run snapshot with zero staleness. Trigger conditions for operators: - Long analyze runs (minutes+) where consult-mode users need fresh reads from the pre-run state. -- Multi-user workstations where multiple agents hit `clarion serve` during analyze. -- CI pipelines where the post-analyze `clarion db verify` must read the fresh DB without locking the running `serve`. +- Multi-user workstations where multiple agents hit `loomweave serve` during analyze. +- CI pipelines where the post-analyze `loomweave db verify` must read the fresh DB without locking the running `serve`. Trade-offs: -- Doubles disk space during analyze (both `clarion.db` and `clarion.db.new` exist). +- Doubles disk space during analyze (both `loomweave.db` and `loomweave.db.new` exist). - Atomic-rename semantics work on Linux/macOS; on Windows, `MoveFileExW(MOVEFILE_REPLACE_EXISTING)` with retries. WAL files handled alongside. -- `--resume` must be aware: shadow-DB resume reopens `.clarion/clarion.db.new`, not the live store. The `partial.json` run file records shadow-mode so resume picks the right DB. +- `--resume` must be aware: shadow-DB resume reopens `.loomweave/loomweave.db.new`, not the live store. The `partial.json` run file records shadow-mode so resume picks the right DB. ### Operational posture -Running `clarion analyze` and `clarion serve` against the same `clarion.db` simultaneously is supported but produces stale-snapshot reads in `serve` until `analyze` completes and checkpoint runs. `serve` emits `CLA-INFRA-STALE-SNAPSHOT` once per detected staleness window. `--shadow-db` is the workaround for operators unwilling to accept that. +Running `loomweave analyze` and `loomweave serve` against the same `loomweave.db` simultaneously is supported but produces stale-snapshot reads in `serve` until `analyze` completes and checkpoint runs. `serve` emits `LMWV-INFRA-STALE-SNAPSHOT` once per detected staleness window. `--shadow-db` is the workaround for operators unwilling to accept that. ### Unvalidated assumption (named here) @@ -99,7 +99,7 @@ The scope-commitment memo's Validation section observes analyze-phase timing; a ### Alternative 1: Full-batch single transaction -Commit the entire `clarion analyze` run in one transaction. +Commit the entire `loomweave analyze` run in one transaction. **Pros**: atomic view of the whole run; `--resume` is either "run it all again" or "accept the committed state" with no intermediate cases. @@ -109,7 +109,7 @@ Commit the entire `clarion analyze` run in one transaction. ### Alternative 2: Shadow-DB as the default -Every `clarion analyze` writes to `clarion.db.new` and atomic-renames; no write contention with `serve` ever. +Every `loomweave analyze` writes to `loomweave.db.new` and atomic-renames; no write contention with `serve` ever. **Pros**: zero-stale-read is the default; simpler contention model (analyze never locks the live DB). @@ -129,13 +129,13 @@ Multiple writer tasks, each owning a write connection, coordinate at the SQLite ### Alternative 4: Separate store process (analyze writes, serve reads via RPC) -`clarion analyze` runs as one process; `clarion serve` as another; a third "store" process owns the DB and serves both over RPC. +`loomweave analyze` runs as one process; `loomweave serve` as another; a third "store" process owns the DB and serves both over RPC. **Pros**: physical isolation; no shared-file-descriptor concerns. -**Cons**: introduces a Clarion-database daemon — violates single-binary posture (ADR-001, CON-LOCAL-01). Adds process supervision, protocol version management, IPC encoding/decoding to the hot path. The problems shadow-DB solves (zero-stale-read during long analyze) are bigger when RPC adds its own latency. +**Cons**: introduces a Loomweave-database daemon — violates single-binary posture (ADR-001, CON-LOCAL-01). Adds process supervision, protocol version management, IPC encoding/decoding to the hot path. The problems shadow-DB solves (zero-stale-read during long analyze) are bigger when RPC adds its own latency. -**Why rejected**: categorical single-binary violation. Loom §6 ("no central store or database") reads harder at the process level. +**Why rejected**: categorical single-binary violation. Weft §6 ("no central store or database") reads harder at the process level. ### Alternative 5: Alternative storage engine (DuckDB, Kuzu, custom) @@ -148,7 +148,7 @@ Already rejected in ADR-001 for the storage-engine selection. Re-cited here beca ### Positive - WAL growth is bounded by N (per-batch commits let checkpointing catch up). -- `clarion analyze` and `clarion serve` coexist against the same DB without catastrophic lock contention — stale snapshots are the failure mode, not "database is locked" exceptions. +- `loomweave analyze` and `loomweave serve` coexist against the same DB without catastrophic lock contention — stale snapshots are the failure mode, not "database is locked" exceptions. - `--resume` semantics are meaningful: crashed-mid-run analyze resumes at the first uncommitted file, not from scratch. - Shadow-DB provides a clean escape hatch for operators with concurrency-critical workflows. Opt-in keeps the default lightweight. - Design-review `§2.2` CRITICAL flag retires: the concurrency model is coherent under the stated topology. @@ -162,19 +162,19 @@ Already rejected in ADR-001 for the storage-engine selection. Re-cited here beca ### Neutral - `synchronous = NORMAL` is a durability-vs-speed trade-off. Acceptable default for local-first; escape via config. -- Writer-actor is a `tokio::task`, not an OS thread. The same Tokio runtime hosts the rest of Clarion's async I/O — no thread-pool tuning surface. +- Writer-actor is a `tokio::task`, not an OS thread. The same Tokio runtime hosts the rest of Loomweave's async I/O — no thread-pool tuning surface. - Reader pool max 16 is sufficient for one consult-agent + one Wardline-equivalent state puller (`requirements.md:699`). Raising it is free modulo memory. ## Related Decisions - [ADR-001](./ADR-001-rust-for-core.md) — Rust + `rusqlite` + `tokio` is the framework this ADR lives inside. `deadpool-sqlite` is named in ADR-001's ecosystem argument. -- [ADR-005](./ADR-005-clarion-dir-tracking.md) — `.clarion/` git-committable default. `clarion.db.new` (shadow-DB intermediate) must be `.gitignore`d; ADR-005 picks the exact ignore rules. +- [ADR-005](./ADR-005-loomweave-dir-tracking.md) — `.loomweave/` git-committable default. `loomweave.db.new` (shadow-DB intermediate) must be `.gitignore`d; ADR-005 picks the exact ignore rules. - [ADR-021](./ADR-021-plugin-authority-hybrid.md) — the per-run entity-count cap (Layer 2d) interacts with transaction granularity: the cap triggers a final flush and abort, which lands cleanly because per-N-files keeps the write path predictable. ## References -- [Clarion v0.1 design review §2.2](../../implementation/v0.1-reviews/pre-restructure/design-review.md) (lines 56-66) — original CRITICAL flag; writer-actor and shadow-DB options. -- [Clarion v0.1 detailed design §3 (Concurrency)](../v0.1/detailed-design.md) (lines 758-769) — the implementation detail this ADR formalises. -- [Clarion v0.1 requirements §NFR-RELIABILITY-02](../v0.1/requirements.md) (line 857) — WAL + writer-actor + checkpoint discipline as crash-safety requirement. -- [Clarion v0.1 system design §4 Storage](../v0.1/system-design.md) — SQLite rationale; WAL mode claim. -- [Clarion v0.1 scope commitments — ADR sprint + validation](../../implementation/v0.1-scope-plans/v0.1-scope-commitments.md) (lines 181-195, 249-251) — P0 promotion from P1 and the explicit follow-up validation task. +- [Loomweave v0.1 design review §2.2](../../implementation/v0.1-reviews/pre-restructure/design-review.md) (lines 56-66) — original CRITICAL flag; writer-actor and shadow-DB options. +- [Loomweave v0.1 detailed design §3 (Concurrency)](../v0.1/detailed-design.md) (lines 758-769) — the implementation detail this ADR formalises. +- [Loomweave v0.1 requirements §NFR-RELIABILITY-02](../v0.1/requirements.md) (line 857) — WAL + writer-actor + checkpoint discipline as crash-safety requirement. +- [Loomweave v0.1 system design §4 Storage](../v0.1/system-design.md) — SQLite rationale; WAL mode claim. +- [Loomweave v0.1 scope commitments — ADR sprint + validation](../../implementation/v0.1-scope-plans/v0.1-scope-commitments.md) (lines 181-195, 249-251) — P0 promotion from P1 and the explicit follow-up validation task. diff --git a/docs/clarion/adr/ADR-012-http-auth-default.md b/docs/loomweave/adr/ADR-012-http-auth-default.md similarity index 65% rename from docs/clarion/adr/ADR-012-http-auth-default.md rename to docs/loomweave/adr/ADR-012-http-auth-default.md index bf719a00..b6fe4651 100644 --- a/docs/clarion/adr/ADR-012-http-auth-default.md +++ b/docs/loomweave/adr/ADR-012-http-auth-default.md @@ -3,12 +3,12 @@ **Status**: Superseded for the ADR-014 registry-backend HTTP read API **Date**: 2026-04-18 **Deciders**: qacona@gmail.com -**Context**: `clarion serve` exposes the read API to sibling tools; v0.1 panel threat-model scored the original `auth: none` default at risk 9 (T-02) +**Context**: `loomweave serve` exposes the read API to sibling tools; v0.1 panel threat-model scored the original `auth: none` default at risk 9 (T-02) ## Summary **2026-05-19 supersession note**: ADR-014 now owns the security posture for the -Clarion HTTP read API used by Filigree's `registry_backend: clarion` mode. That +Loomweave HTTP read API used by Filigree's `registry_backend: loomweave` mode. That surface is unauthenticated and loopback-only by default, refuses non-loopback binds unless `serve.http.allow_non_loopback: true`, and relies on an operator-managed authenticated reverse proxy or equivalent access-control layer @@ -16,7 +16,7 @@ for non-loopback exposure. The UDS/token design below remains historical context for the earlier broad v0.1 HTTP API proposal; it is not the implementation contract for the ADR-014 federation read surface. -`clarion serve` defaults to **Unix domain socket** at `.clarion/socket` (mode 0600, owner = current UID). Filesystem permissions are the auth; no network bind, no Bearer tokens, no CAP_NET_BIND_SERVICE surface. When a UDS is unavailable (Windows, SSH-port-forwarded remote access, cross-UID-namespace containers), `serve.auth: token` falls back to TCP on `127.0.0.1:8765` + a Bearer token auto-minted at `.clarion/auth.token` (mode 0600). `serve.auth: none` remains configurable for operators who explicitly accept the unauthenticated-loopback posture, but now emits `CLA-INFRA-HTTP-AUTH-DISABLED` (severity ERROR) per serve startup and shows a persistent banner in logs. The default flip closes T-02 and structurally reduces T-05 (DNS rebinding) by eliminating the TCP bind in the primary path. +`loomweave serve` defaults to **Unix domain socket** at `.loomweave/socket` (mode 0600, owner = current UID). Filesystem permissions are the auth; no network bind, no Bearer tokens, no CAP_NET_BIND_SERVICE surface. When a UDS is unavailable (Windows, SSH-port-forwarded remote access, cross-UID-namespace containers), `serve.auth: token` falls back to TCP on `127.0.0.1:8765` + a Bearer token auto-minted at `.loomweave/auth.token` (mode 0600). `serve.auth: none` remains configurable for operators who explicitly accept the unauthenticated-loopback posture, but now emits `LMWV-INFRA-HTTP-AUTH-DISABLED` (severity ERROR) per serve startup and shows a persistent banner in logs. The default flip closes T-02 and structurally reduces T-05 (DNS rebinding) by eliminating the TCP bind in the primary path. ## Context @@ -26,7 +26,7 @@ The original v0.1 design (system-design §9 "Token auth") defaulted `serve.auth` The panel's "three non-negotiable v0.1 controls" (`09-threat-model.md` §12 recommendation 1) named the specific fix: -> Flip the HTTP API default to "authenticated or not listening." Either bind to a mode-0600 Unix-domain socket by default, or auto-mint a token on first `clarion serve`. `auth: none` on loopback is defended in §10 by naming "loopback is not a boundary"; the design should then not make `none` the default. +> Flip the HTTP API default to "authenticated or not listening." Either bind to a mode-0600 Unix-domain socket by default, or auto-mint a token on first `loomweave serve`. `auth: none` on loopback is defended in §10 by naming "loopback is not a boundary"; the design should then not make `none` the default. The scope-commitment memo adopted the flip (`v0.1-scope-commitments.md:199`, action item 5). This ADR records the specific choice — UDS over auto-minted token as the primary — and names the fallback triggers. @@ -36,46 +36,46 @@ Two candidates existed. UDS (a) has the additional property of removing the TCP ### Primary — Unix domain socket (`serve.auth: uds`, the default) -`clarion serve` binds `/.clarion/socket` with filesystem mode 0600 and owner = the UID running `clarion serve`. The process owns the socket for its lifetime; on graceful shutdown the socket is unlinked. +`loomweave serve` binds `/.loomweave/socket` with filesystem mode 0600 and owner = the UID running `loomweave serve`. The process owns the socket for its lifetime; on graceful shutdown the socket is unlinked. - **Transport**: HTTP/1.1 over UDS. Server-side `axum` + `tokio::net::UnixListener` + `hyper::server`. No `Authorization` header required; no Bearer check on the HTTP layer. -- **Client side**: sibling tools (Wardline, local agents) connect via UDS using `hyper-unix-connector` or equivalent. Endpoint URL convention: the CLI and config accept `unix:///absolute/path/to/.clarion/socket`; the local fully-qualified path is used in sibling config rather than a pseudo-URL. +- **Client side**: sibling tools (Wardline, local agents) connect via UDS using `hyper-unix-connector` or equivalent. Endpoint URL convention: the CLI and config accept `unix:///absolute/path/to/.loomweave/socket`; the local fully-qualified path is used in sibling config rather than a pseudo-URL. - **Auth by filesystem**: mode 0600 + owner-match means only the owning UID can connect. Shared-Docker and shared-dev-host scenarios (T-02's original concern) are closed because a different UID cannot open the socket at all. - **CAP_NET_BIND_SERVICE equivalent**: there is no TCP listener. DNS rebinding (T-05) is structurally inapplicable. `Host:` / `Origin:` checks are unnecessary. -- **Discovery**: `/.clarion/socket` is the canonical path; `clarion serve` documents it at startup. Sibling tools read `/.clarion/config.json`'s `serve.socket_path` for explicit discovery. +- **Discovery**: `/.loomweave/socket` is the canonical path; `loomweave serve` documents it at startup. Sibling tools read `/.loomweave/config.json`'s `serve.socket_path` for explicit discovery. ### Fallback — TCP + Bearer token (`serve.auth: token`) -When UDS is unavailable or the operator explicitly opts in, Clarion falls back to TCP on `127.0.0.1:8765` with an auto-minted Bearer token. +When UDS is unavailable or the operator explicitly opts in, Loomweave falls back to TCP on `127.0.0.1:8765` with an auto-minted Bearer token. -- **Trigger (auto)**: Windows builds default to `serve.auth: token` since Windows UDS support is recent enough (Windows 10 1803+) that reliability varies. Clarion's Windows default is `token`. -- **Trigger (explicit)**: operators who need cross-UID-namespace access (e.g., local sibling running in a container, SSH port-forward exposing the serve endpoint to a remote workstation) set `serve.auth: token` in `clarion.yaml`. -- **Token auto-mint on first serve**: `.clarion/auth.token` written with mode 0600. Format: `clrn_` prefix + 32 bytes URL-safe base64 = 43 chars. No `clarion serve auth init` required to get started — first invocation auto-mints. -- **OS keychain promotion**: `clarion serve auth promote-to-keychain` moves the token from the file to the OS keychain (macOS Keychain / Linux libsecret / Windows Credential Manager) via the `keyring` crate. Promotes away from the filesystem hazard; emits `CLA-INFRA-TOKEN-STORAGE-DEGRADED` if the keychain is unavailable (falls back to file). +- **Trigger (auto)**: Windows builds default to `serve.auth: token` since Windows UDS support is recent enough (Windows 10 1803+) that reliability varies. Loomweave's Windows default is `token`. +- **Trigger (explicit)**: operators who need cross-UID-namespace access (e.g., local sibling running in a container, SSH port-forward exposing the serve endpoint to a remote workstation) set `serve.auth: token` in `loomweave.yaml`. +- **Token auto-mint on first serve**: `.loomweave/auth.token` written with mode 0600. Format: `clrn_` prefix + 32 bytes URL-safe base64 = 43 chars. No `loomweave serve auth init` required to get started — first invocation auto-mints. +- **OS keychain promotion**: `loomweave serve auth promote-to-keychain` moves the token from the file to the OS keychain (macOS Keychain / Linux libsecret / Windows Credential Manager) via the `keyring` crate. Promotes away from the filesystem hazard; emits `LMWV-INFRA-TOKEN-STORAGE-DEGRADED` if the keychain is unavailable (falls back to file). - **Wire**: `Authorization: Bearer clrn_<43chars>`. Constant-time server comparison. -- **Rotation**: `clarion serve auth rotate` with 24-hour grace window (both tokens accepted during the window). +- **Rotation**: `loomweave serve auth rotate` with 24-hour grace window (both tokens accepted during the window). ### Explicit-none (`serve.auth: none`) -The operator can still disable auth. This is the escape hatch for air-gapped CI with external ingress control, for local debugging with trusted process landscape, or for operators who have a strong reason Clarion can't infer. It is loud rather than silent: +The operator can still disable auth. This is the escape hatch for air-gapped CI with external ingress control, for local debugging with trusted process landscape, or for operators who have a strong reason Loomweave can't infer. It is loud rather than silent: -- `CLA-INFRA-HTTP-AUTH-DISABLED` (severity ERROR) emitted once per `clarion serve` startup with `serve.auth: none`. Propagated through the finding pipeline to Filigree; surfaces on the normal audit dashboard. -- Persistent banner at every serve-startup log message: `WARNING: clarion serve is running with NO authentication. Any local process can read the store.` -- The flag is named `--i-accept-no-auth` if operators want to enable it via CLI rather than `clarion.yaml` — the name is deliberately verbose. +- `LMWV-INFRA-HTTP-AUTH-DISABLED` (severity ERROR) emitted once per `loomweave serve` startup with `serve.auth: none`. Propagated through the finding pipeline to Filigree; surfaces on the normal audit dashboard. +- Persistent banner at every serve-startup log message: `WARNING: loomweave serve is running with NO authentication. Any local process can read the store.` +- The flag is named `--i-accept-no-auth` if operators want to enable it via CLI rather than `loomweave.yaml` — the name is deliberately verbose. ### Wardline CI integration -`clarion check-auth --from wardline` remains the pre-flight check for CI. Its behaviour by mode: +`loomweave check-auth --from wardline` remains the pre-flight check for CI. Its behaviour by mode: - **UDS mode**: verifies the socket exists, is owned by the current UID, has mode 0600, and accepts a connection. Exit 0 = ready. -- **Token mode**: verifies `CLARION_TOKEN` env var or `.clarion/auth.token` is readable and authenticates against the running serve. Exit 0 = ready. -- **None mode**: exits 0 with warning (`CLA-INFRA-HTTP-AUTH-DISABLED` emitted at serve-side; check-auth confirms no auth is required). +- **Token mode**: verifies `LOOMWEAVE_TOKEN` env var or `.loomweave/auth.token` is readable and authenticates against the running serve. Exit 0 = ready. +- **None mode**: exits 0 with warning (`LMWV-INFRA-HTTP-AUTH-DISABLED` emitted at serve-side; check-auth confirms no auth is required). ### Cross-platform matrix | Platform | Primary default | Fallback | |---|---|---| -| Linux / macOS | UDS at `.clarion/socket` mode 0600 | TCP + token (explicit) | +| Linux / macOS | UDS at `.loomweave/socket` mode 0600 | TCP + token (explicit) | | Windows 10 1803+ | TCP + token | (no further fallback; `none` is explicit opt-in) | | WSL (Linux on Windows) | UDS | TCP + token (if WSL-Windows client access needed) | | Remote-over-SSH (`ssh -L 8765:localhost:8765`) | TCP + token (explicit) | — | @@ -113,7 +113,7 @@ Bind HTTPS on 127.0.0.1 with a self-signed cert; operators trust the cert once. ### Alternative 4: Kernel-enforced namespace isolation (pid/net namespace) -Run `clarion serve` in a private network namespace reachable only from cooperating processes. +Run `loomweave serve` in a private network namespace reachable only from cooperating processes. **Pros**: strongest isolation. @@ -140,30 +140,30 @@ v0.1 tokens carry scope claims — read-only catalog, read-only findings, submit - T-06 (MCP `auto_emit` bypass, risk 6) reduced: a local attacker would need socket access before the consent gate matters. - The explicit-none path is loud. Operators bypassing auth see it in findings, logs, and compat reports. The bypass is not silent. - Windows operators get the token path by default; no platform-specific UX degradation. -- CI integration via `clarion check-auth --from wardline` works uniformly across modes. +- CI integration via `loomweave check-auth --from wardline` works uniformly across modes. ### Negative - Platform asymmetry: UDS on Unix, TCP+token on Windows. Documentation must cover both; integration tests must cover both. Mitigation: the asymmetry is deliberate and named; Windows operators get the token path consistently. - UDS support in HTTP client libraries varies. Wardline's HTTP client in v0.2 needs to support `hyper-unix-connector` or equivalent. Mitigation: the token fallback exists specifically for clients that can't do UDS. An early Wardline implementation can start on TCP+token and move to UDS later. -- Socket cleanup on crash: if `clarion serve` SIGKILL-dies, the `.clarion/socket` file persists. Next start needs to unlink stale sockets. Mitigation: startup logic unlinks existing sockets after verifying no process is listening. +- Socket cleanup on crash: if `loomweave serve` SIGKILL-dies, the `.loomweave/socket` file persists. Next start needs to unlink stale sockets. Mitigation: startup logic unlinks existing sockets after verifying no process is listening. - Cross-SSH remote access requires the token mode. Operators SSHing from a laptop to a dev VM cannot use UDS directly (unless they forward the socket via `ssh -o StreamLocalBindUnlink=yes -L /path/to/local.socket:/path/to/remote.socket`). Mitigation: documentation points at this path; `serve.auth: token` is the easier answer for most teams. ### Neutral -- `.clarion/socket` must be gitignored (it's a runtime artifact, not shared state). ADR-005 picks the exact ignore rules. -- `.clarion/auth.token` is also gitignored. Committing it is a leak of the token; ADR-005 covers this. +- `.loomweave/socket` must be gitignored (it's a runtime artifact, not shared state). ADR-005 picks the exact ignore rules. +- `.loomweave/auth.token` is also gitignored. Committing it is a leak of the token; ADR-005 covers this. - The token format (`clrn_` + 32 bytes base64) stays as documented in detailed-design §7 — this ADR changes the default posture and the auto-mint trigger, not the token format. ## Related Decisions -- [ADR-005](./ADR-005-clarion-dir-tracking.md) — `.clarion/` git-committable by default, but `.clarion/socket` and `.clarion/auth.token` are runtime artifacts that must be excluded. ADR-005 picks the `.gitignore` rules that protect against both. +- [ADR-005](./ADR-005-loomweave-dir-tracking.md) — `.loomweave/` git-committable by default, but `.loomweave/socket` and `.loomweave/auth.token` are runtime artifacts that must be excluded. ADR-005 picks the `.gitignore` rules that protect against both. - [ADR-021](./ADR-021-plugin-authority-hybrid.md) — closes the plugin-side attack surface (T-01, T-08, T-11, T-12); this ADR closes the HTTP-side attack surface (T-02, T-05). Together they are the panel's "three non-negotiable v0.1 controls" (third being ADR-013, secret scanner). ## References -- [Clarion v0.1 panel threat model T-02](../../implementation/v0.1-reviews/panel-2026-04-17/09-threat-model.md) (line 233) — original risk scoring. +- [Loomweave v0.1 panel threat model T-02](../../implementation/v0.1-reviews/panel-2026-04-17/09-threat-model.md) (line 233) — original risk scoring. - [Panel threat model §12 recommendation 1](../../implementation/v0.1-reviews/panel-2026-04-17/09-threat-model.md) (line 298) — the specific "authenticated or not listening" prescription. -- [Clarion v0.1 system design §10 "Loopback is not a security boundary"](../v0.1/system-design.md) — the paragraph this ADR flips; updated in the same commit. +- [Loomweave v0.1 system design §10 "Loopback is not a security boundary"](../v0.1/system-design.md) — the paragraph this ADR flips; updated in the same commit. - [ADR-014](./ADR-014-filigree-registry-backend.md) — supersedes this ADR for the active registry-backend HTTP read API trust model. -- [Clarion v0.1 scope commitments — action 5](../../implementation/v0.1-scope-plans/v0.1-scope-commitments.md) (line 199) — the commitment mandate. +- [Loomweave v0.1 scope commitments — action 5](../../implementation/v0.1-scope-plans/v0.1-scope-commitments.md) (line 199) — the commitment mandate. diff --git a/docs/clarion/adr/ADR-013-pre-ingest-secret-scanner.md b/docs/loomweave/adr/ADR-013-pre-ingest-secret-scanner.md similarity index 75% rename from docs/clarion/adr/ADR-013-pre-ingest-secret-scanner.md rename to docs/loomweave/adr/ADR-013-pre-ingest-secret-scanner.md index 1fffaf0e..18d90a53 100644 --- a/docs/clarion/adr/ADR-013-pre-ingest-secret-scanner.md +++ b/docs/loomweave/adr/ADR-013-pre-ingest-secret-scanner.md @@ -7,13 +7,13 @@ ## Summary -Before any file content reaches the LLM provider (Phases 4, 5, 6), Clarion runs a **core-owned** secret scanner over the file buffer. Detections on any file block LLM dispatch for that file specifically: structural extraction (Phase 1) still runs, entities still land in the store, but briefings marked `briefing_blocked: secret_present` are not dispatched to Anthropic. A `CLA-SEC-SECRET-DETECTED` finding (severity ERROR) is emitted per detection. Operators mark known false positives in a committable `.clarion/secrets-baseline.yaml` (detect-secrets baseline format). The `--allow-unredacted-secrets` override requires explicit TTY confirmation or an explicit `--confirm-allow-unredacted-secrets=yes-i-understand` non-TTY flag, and records `CLA-SEC-UNREDACTED-SECRETS-ALLOWED` per affected file. The scanner implementation is a **Rust-native port of the detect-secrets rule set** — chosen over Python-embed / subprocess-invocation to preserve single-binary distribution (NFR-OPS-04). +Before any file content reaches the LLM provider (Phases 4, 5, 6), Loomweave runs a **core-owned** secret scanner over the file buffer. Detections on any file block LLM dispatch for that file specifically: structural extraction (Phase 1) still runs, entities still land in the store, but briefings marked `briefing_blocked: secret_present` are not dispatched to Anthropic. A `LMWV-SEC-SECRET-DETECTED` finding (severity ERROR) is emitted per detection. Operators mark known false positives in a committable `.loomweave/secrets-baseline.yaml` (detect-secrets baseline format). The `--allow-unredacted-secrets` override requires explicit TTY confirmation or an explicit `--confirm-allow-unredacted-secrets=yes-i-understand` non-TTY flag, and records `LMWV-SEC-UNREDACTED-SECRETS-ALLOWED` per affected file. The scanner implementation is a **Rust-native port of the detect-secrets rule set** — chosen over Python-embed / subprocess-invocation to preserve single-binary distribution (NFR-OPS-04). ## Context The 2026-04-17 panel threat model (`09-threat-model.md` §7, T-10) scored "secret scanner false negatives leak source + secrets to Anthropic" at risk 6. The design review (`design-review.md:88-91`) escalated the v0.1 case to CRITICAL: -> The design sends file content to Anthropic for summarization (§5, §6) with no pre-ingest secret scanner, no redaction pass, no per-file allow/deny beyond include/exclude globs. Any `.env`, test fixture, or committed API key enters Anthropic's API and persists in the summary cache and `runs//log.jsonl` (which the design keeps "for audit"). The first real user running `clarion analyze` on a repo with a committed `.env` leaks to Anthropic silently. +> The design sends file content to Anthropic for summarization (§5, §6) with no pre-ingest secret scanner, no redaction pass, no per-file allow/deny beyond include/exclude globs. Any `.env`, test fixture, or committed API key enters Anthropic's API and persists in the summary cache and `runs//log.jsonl` (which the design keeps "for audit"). The first real user running `loomweave analyze` on a repo with a committed `.env` leaks to Anthropic silently. The mitigation the review prescribed — pre-ingest secret scanner with LLM-dispatch block — is a v0.1 non-negotiable. The scope-commitment memo confirmed P0 (`v0.1-scope-commitments.md:188`). @@ -30,31 +30,31 @@ Open questions this ADR settles: ### Implementation — Rust-native port of detect-secrets rule set -The scanner is a Rust module in the Clarion core. It implements the rule set of `detect-secrets` (v1.x baseline compatibility) natively: +The scanner is a Rust module in the Loomweave core. It implements the rule set of `detect-secrets` (v1.x baseline compatibility) natively: - **High-entropy strings**: base64 (entropy ≥ 4.5 over ≥20 chars), hex (entropy ≥ 3.0 over ≥40 chars). - **Named credential patterns**: AWS access keys (`AKIA[0-9A-Z]{16}`, `ASIA[0-9A-Z]{16}`), AWS secret-key adjacency, GitHub PATs (`ghp_[a-zA-Z0-9]{36}`, `github_pat_[a-zA-Z0-9_]{82}`), GitHub OAuth tokens (`gho_`, `ghu_`, `ghs_`, `ghr_`), Anthropic API keys (`sk-ant-[a-zA-Z0-9_-]{90,}`), OpenAI keys (`sk-[a-zA-Z0-9]{48}`), Stripe keys (`sk_live_`, `pk_live_`, `rk_live_`), Slack tokens, JWT tokens (`eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}`). - **Private key headers**: `-----BEGIN (RSA|EC|DSA|OPENSSH|PGP) PRIVATE KEY-----`, `-----BEGIN ENCRYPTED PRIVATE KEY-----`. - **Contextual credentials**: name-based patterns (`password`, `passwd`, `secret`, `token`, `api_key`, `api-key` followed by `=`, `:`, `:=`, or equivalent assignment + a quoted string). -The rule set matches `detect-secrets` v1.x so that operators familiar with that tool (and its baseline file) migrate without surprise. The implementation lives in `clarion_scanner` crate inside the Clarion workspace; ships as part of the core binary; zero runtime dependency on Python or external `detect-secrets` install. +The rule set matches `detect-secrets` v1.x so that operators familiar with that tool (and its baseline file) migrate without surprise. The implementation lives in `loomweave_scanner` crate inside the Loomweave workspace; ships as part of the core binary; zero runtime dependency on Python or external `detect-secrets` install. ### Block granularity — file-level; structural extraction preserved When the scanner flags file `F`: -- `F`'s Phase-1 structural extraction runs normally. The plugin parses `F`, emits entities, edges, and structural findings (`CLA-PY-STRUCTURE-*`, `CLA-FACT-*`). These land in the store. +- `F`'s Phase-1 structural extraction runs normally. The plugin parses `F`, emits entities, edges, and structural findings (`LMWV-PY-STRUCTURE-*`, `LMWV-FACT-*`). These land in the store. - `F`'s entities carry `briefing_blocked: secret_present` in their `properties` dict. Phase 4–6 dispatch skips them; no LLM call, no summary-cache write. -- `CLA-SEC-SECRET-DETECTED` findings are emitted — one per (rule, file, line) tuple. Severity ERROR. +- `LMWV-SEC-SECRET-DETECTED` findings are emitted — one per (rule, file, line) tuple. Severity ERROR. - Consult-mode queries on blocked entities return the entity with no `summary` field and an explicit `briefing_blocked` signal so agents know the absence is policy, not pipeline failure. Why file-level, not entity-level: the scanner runs over file-buffers (byte streams), not entity slices. Secret detection operating on pre-parse bytes catches secrets outside the parseable structure (comments, top-of-file docstrings, binary-ish strings that still embed a secret). Entity-level would miss file-header secrets. Why not refuse to parse: structural information is needed for the catalog even when briefings can't be written. Operators fixing a committed `.env` should get the full Phase-1 picture of what the codebase looks like — they just don't get summaries for the affected files until they fix the secret. -### Baseline — `.clarion/secrets-baseline.yaml` +### Baseline — `.loomweave/secrets-baseline.yaml` -False positives are marked in `.clarion/secrets-baseline.yaml`, using the exact format `detect-secrets`' `baseline` command produces: +False positives are marked in `.loomweave/secrets-baseline.yaml`, using the exact format `detect-secrets`' `baseline` command produces: ```yaml version: "1.0" @@ -67,8 +67,8 @@ results: justification: "Test fixture — Stripe sandbox key, documented public" ``` -- Committable by default. Reviewing a `.clarion/secrets-baseline.yaml` diff in PRs is an intentional audit surface: "operator marked this string as not-a-secret" is a review-worthy signal. -- `justification` field is required (schema-validated at load time). A baseline entry without justification is rejected with `CLA-INFRA-SECRET-BASELINE-NO-JUSTIFICATION`. +- Committable by default. Reviewing a `.loomweave/secrets-baseline.yaml` diff in PRs is an intentional audit surface: "operator marked this string as not-a-secret" is a review-worthy signal. +- `justification` field is required (schema-validated at load time). A baseline entry without justification is rejected with `LMWV-INFRA-SECRET-BASELINE-NO-JUSTIFICATION`. - `hashed_secret` stored over the literal bytes; the actual string never lives in the baseline. ### Override — `--allow-unredacted-secrets` @@ -76,9 +76,9 @@ results: The override exists for specific legitimate cases: analysing a repo that genuinely contains committed test-only credentials against a sandbox, or urgent debugging where the operator accepts the leak. It is explicit and audited: - **TTY sessions**: `--allow-unredacted-secrets` prompts interactively with the list of detected secrets; operator types `yes-i-understand` to proceed. -- **Non-TTY sessions (CI)**: requires both `--allow-unredacted-secrets` AND `--confirm-allow-unredacted-secrets=yes-i-understand`. A CI pipeline accidentally adding `--allow-unredacted-secrets` alone fails with `CLA-INFRA-SECRET-OVERRIDE-UNCONFIRMED` rather than bypassing silently. -- **Audit trail**: each affected file emits `CLA-SEC-UNREDACTED-SECRETS-ALLOWED` (severity ERROR). `runs//stats.json` records `{override_used: true, files_affected: [...]}`. -- **Filigree surfacing**: the override finding reaches Filigree via the normal scan-results path. Security-focused operators running `filigree list --rule-id=CLA-SEC-UNREDACTED-SECRETS-ALLOWED --since 30d` see every override in the audit window. +- **Non-TTY sessions (CI)**: requires both `--allow-unredacted-secrets` AND `--confirm-allow-unredacted-secrets=yes-i-understand`. A CI pipeline accidentally adding `--allow-unredacted-secrets` alone fails with `LMWV-INFRA-SECRET-OVERRIDE-UNCONFIRMED` rather than bypassing silently. +- **Audit trail**: each affected file emits `LMWV-SEC-UNREDACTED-SECRETS-ALLOWED` (severity ERROR). `runs//stats.json` records `{override_used: true, files_affected: [...]}`. +- **Filigree surfacing**: the override finding reaches Filigree via the normal scan-results path. Security-focused operators running `filigree list --rule-id=LMWV-SEC-UNREDACTED-SECRETS-ALLOWED --since 30d` see every override in the audit window. ### Coverage v0.1 — committed list @@ -152,7 +152,7 @@ Rely on operator discipline — "don't commit `.env` files"; add documentation. **Pros**: zero engineering cost. -**Cons**: design review called this CRITICAL. The first real user running `clarion analyze` on a repo with a committed `.env` leaks to Anthropic silently. Marketing copy describes Clarion as a security-analysis tool; shipping without the scanner is a credibility failure before v0.1 launches. +**Cons**: design review called this CRITICAL. The first real user running `loomweave analyze` on a repo with a committed `.env` leaks to Anthropic silently. Marketing copy describes Loomweave as a security-analysis tool; shipping without the scanner is a credibility failure before v0.1 launches. **Why rejected**: not an option for a security-tool release. @@ -170,27 +170,27 @@ Rely on operator discipline — "don't commit `.env` files"; add documentation. - Pattern-based scanning has false negatives. Novel secret shapes (custom internal API keys without a named pattern, stegano-encoded secrets, secrets in non-text formats) slip through. Mitigation: high-entropy detection catches many; operators running against truly high-risk repos should prefer `--no-llm` mode or air-gapped alternatives. - Rust-native port is engineering cost. Core team owns maintenance; rule additions are Rust code changes. Mitigation: v0.2 adds Wardline-sourced custom rules so the core rule set doesn't need to absorb every project's unique patterns. -- False-positive pressure on operators. High-entropy detection triggers on UUIDs, base64-encoded checksums, test-fixture tokens. Operators maintain `.clarion/secrets-baseline.yaml` to suppress; this is manual work. Mitigation: baseline-format compatibility with `detect-secrets` means `detect-secrets scan --baseline` from operator habits transfers directly. -- Override path exists and is used sometimes. `CLA-SEC-UNREDACTED-SECRETS-ALLOWED` must be monitored; absence of monitoring turns the audit trail into theatre. +- False-positive pressure on operators. High-entropy detection triggers on UUIDs, base64-encoded checksums, test-fixture tokens. Operators maintain `.loomweave/secrets-baseline.yaml` to suppress; this is manual work. Mitigation: baseline-format compatibility with `detect-secrets` means `detect-secrets scan --baseline` from operator habits transfers directly. +- Override path exists and is used sometimes. `LMWV-SEC-UNREDACTED-SECRETS-ALLOWED` must be monitored; absence of monitoring turns the audit trail into theatre. ### Neutral - The scanner's file-buffer input comes through core-side file I/O, upstream of plugin `file_list` returns. ADR-021's path-jail filters the file list before the scanner sees it. -- Entities in blocked files land without briefings; the summary cache (ADR-007) has no row for them until the secret is fixed. Next `clarion analyze` post-fix cache-misses and computes normally. +- Entities in blocked files land without briefings; the summary cache (ADR-007) has no row for them until the secret is fixed. Next `loomweave analyze` post-fix cache-misses and computes normally. - Baseline file committed alongside source means teammates pulling a repo inherit each other's false-positive markings. This is correct — security-review consensus is expressed in the baseline diff. ## Related Decisions -- [ADR-004](./ADR-004-finding-exchange-format.md) — `CLA-SEC-SECRET-DETECTED`, `CLA-SEC-UNREDACTED-SECRETS-ALLOWED`, `CLA-INFRA-SECRET-*` findings use the Filigree-native exchange format this ADR defines. +- [ADR-004](./ADR-004-finding-exchange-format.md) — `LMWV-SEC-SECRET-DETECTED`, `LMWV-SEC-UNREDACTED-SECRETS-ALLOWED`, `LMWV-INFRA-SECRET-*` findings use the Filigree-native exchange format this ADR defines. - [ADR-007](./ADR-007-summary-cache-key.md) — `briefing_blocked: secret_present` entities produce no cache rows; the cache correctness argument relies on this ADR's block behaviour being deterministic. -- [ADR-017](./ADR-017-severity-and-dedup.md) — `CLA-SEC-*` is one of the namespaced rule-ID prefixes; this ADR is its primary producer. +- [ADR-017](./ADR-017-severity-and-dedup.md) — `LMWV-SEC-*` is one of the namespaced rule-ID prefixes; this ADR is its primary producer. - [ADR-021](./ADR-021-plugin-authority-hybrid.md) — path-jail (Layer 2a) filters the file list this scanner operates on. The scanner does not need its own path validation. - [ADR-022](./ADR-022-core-plugin-ontology.md) — classifies secret detection as a core-owned algorithm (not plugin-owned ontology). This ADR is the canonical instance of the "algorithm stays in the core" rule. ## References -- [Clarion v0.1 design review §3 (Secret exfiltration)](../../implementation/v0.1-reviews/pre-restructure/design-review.md) (lines 88-91) — the CRITICAL flag this ADR retires. -- [Clarion v0.1 panel threat model T-10](../../implementation/v0.1-reviews/panel-2026-04-17/09-threat-model.md) (line 241) — risk scoring and residual-risk framing. -- [Clarion v0.1 system design §10 (Pre-ingest redaction)](../v0.1/system-design.md) (lines 1044-1056) — the behaviour this ADR formalises. -- [Clarion v0.1 requirements — NFR-SEC-01, NFR-SEC-05](../v0.1/requirements.md) — requirement floor. +- [Loomweave v0.1 design review §3 (Secret exfiltration)](../../implementation/v0.1-reviews/pre-restructure/design-review.md) (lines 88-91) — the CRITICAL flag this ADR retires. +- [Loomweave v0.1 panel threat model T-10](../../implementation/v0.1-reviews/panel-2026-04-17/09-threat-model.md) (line 241) — risk scoring and residual-risk framing. +- [Loomweave v0.1 system design §10 (Pre-ingest redaction)](../v0.1/system-design.md) (lines 1044-1056) — the behaviour this ADR formalises. +- [Loomweave v0.1 requirements — NFR-SEC-01, NFR-SEC-05](../v0.1/requirements.md) — requirement floor. - [detect-secrets baseline format](https://github.com/Yelp/detect-secrets/blob/master/README.md#baseline-file) — the format this ADR's baseline layout matches. diff --git a/docs/clarion/adr/ADR-014-filigree-registry-backend.md b/docs/loomweave/adr/ADR-014-filigree-registry-backend.md similarity index 50% rename from docs/clarion/adr/ADR-014-filigree-registry-backend.md rename to docs/loomweave/adr/ADR-014-filigree-registry-backend.md index 03e1b4cd..1009b840 100644 --- a/docs/clarion/adr/ADR-014-filigree-registry-backend.md +++ b/docs/loomweave/adr/ADR-014-filigree-registry-backend.md @@ -3,11 +3,11 @@ **Status**: Accepted; partially extended by [ADR-034](./ADR-034-federation-http-read-api-hardening.md) (Security Posture and Error Envelope sections only — registry-backend protocol decision remains in force) **Date**: 2026-04-18 **Deciders**: qacona@gmail.com -**Context**: Clarion v0.1 integration boundary with Filigree's file registry; joint deliverable (Clarion + Filigree, same author) +**Context**: Loomweave v0.1 integration boundary with Filigree's file registry; joint deliverable (Loomweave + Filigree, same author) ## Summary -Filigree gains a pluggable `RegistryProtocol` with two modes, selected via a `registry_backend` configuration flag: `local` (Filigree's current native registry — the default) and `clarion` (Filigree delegates file-identity operations to Clarion's HTTP read API). Clarion v0.1 ships expecting `registry_backend: clarion` and degrades to shadow-registry mode when the flag is absent. Because the same author maintains both products, Filigree's implementation lands alongside Clarion's v0.1 release rather than as a cross-team prerequisite. +Filigree gains a pluggable `RegistryProtocol` with two modes, selected via a `registry_backend` configuration flag: `local` (Filigree's current native registry — the default) and `loomweave` (Filigree delegates file-identity operations to Loomweave's HTTP read API). Loomweave v0.1 ships expecting `registry_backend: loomweave` and degrades to shadow-registry mode when the flag is absent. Because the same author maintains both products, Filigree's implementation lands alongside Loomweave's v0.1 release rather than as a cross-team prerequisite. ## Context @@ -17,9 +17,9 @@ Filigree today owns the file registry unconditionally. `file_records(id TEXT PRI 2. `create_observation(file_path=…)` calls `register_file` to bind the observation (`db_observations.py:135-147`). 3. `trigger_scan` / `trigger_scan_batch` call `tracker.register_file(...)` to populate `scan_runs.file_ids` (`mcp_tools/scanners.py:422, :586`). -Each auto-create path produces a Filigree-native `file_records.id` (UUID-derived: `f"{prefix}-f-{uuid.uuid4().hex[:10]}"`). Clarion's entity-identity scheme uses symbolic canonical names (ADR-003). The two schemes currently diverge: anything Clarion POSTs creates a shadow Filigree file row, and cross-tool "same file" queries have no answer. +Each auto-create path produces a Filigree-native `file_records.id` (UUID-derived: `f"{prefix}-f-{uuid.uuid4().hex[:10]}"`). Loomweave's entity-identity scheme uses symbolic canonical names (ADR-003). The two schemes currently diverge: anything Loomweave POSTs creates a shadow Filigree file row, and cross-tool "same file" queries have no answer. -Clarion v0.1 claims to own structural truth about the codebase (`loom.md` §2). That claim is inconsistent with Filigree silently minting file identities on every POST. A protocol boundary is needed. +Loomweave v0.1 claims to own structural truth about the codebase (`weft.md` §2). That claim is inconsistent with Filigree silently minting file identities on every POST. A protocol boundary is needed. `registry_backend` and `FILIGREE_FILE_REGISTRY_DISPLACED` do not exist in Filigree today — verified by `grep` across `/home/john/filigree` on 2026-04-17 (see `reviews/pre-restructure/integration-recon.md` §2.1). Both are net-new additions. @@ -27,19 +27,19 @@ Clarion v0.1 claims to own structural truth about the codebase (`loom.md` §2). Filigree introduces a `RegistryProtocol` trait with two implementations. -**Mode `local` (default)**: Filigree's current behaviour. The three auto-create paths populate `file_records` using UUID-derived IDs. Filigree remains fully usable standalone — no Clarion dependency, no degradation. +**Mode `local` (default)**: Filigree's current behaviour. The three auto-create paths populate `file_records` using UUID-derived IDs. Filigree remains fully usable standalone — no Loomweave dependency, no degradation. -**Mode `clarion`**: Filigree delegates `file_id` resolution to Clarion's HTTP read API. The three auto-create paths call `RegistryProtocol::resolve_file(path, language) -> file_id` which, under `clarion` mode, issues an HTTP GET to Clarion's read API. The returned `file_id` is Clarion's symbolic file-kind entity ID (`core:file:{qualified_name}`). The `file_records` row is created in Filigree with that ID, preserving the existing foreign-key structure. +**Mode `loomweave`**: Filigree delegates `file_id` resolution to Loomweave's HTTP read API. The three auto-create paths call `RegistryProtocol::resolve_file(path, language) -> file_id` which, under `loomweave` mode, issues an HTTP GET to Loomweave's read API. The returned `file_id` is Loomweave's symbolic file-kind entity ID (`core:file:{qualified_name}`). The `file_records` row is created in Filigree with that ID, preserving the existing foreign-key structure. -**Flag surfacing**: `registry_backend` appears in `GET /api/files/_schema.config_flags`. Clarion's capability probe reads it at every `clarion analyze` start. Present + value `clarion` → proceed with delegation. Absent or value `local` → Clarion enters shadow-registry mode and emits `CLA-INFRA-FILIGREE-SHADOW-REGISTRY` per batch. +**Flag surfacing**: `registry_backend` appears in `GET /api/files/_schema.config_flags`. Loomweave's capability probe reads it at every `loomweave analyze` start. Present + value `loomweave` → proceed with delegation. Absent or value `local` → Loomweave enters shadow-registry mode and emits `LMWV-INFRA-FILIGREE-SHADOW-REGISTRY` per batch. -**Error code**: `FILIGREE_FILE_REGISTRY_DISPLACED` is returned by Filigree to any caller that tries to directly mutate `file_records` (e.g., `register_file` MCP tool) while `registry_backend: clarion` is active. The write path is Clarion's; Filigree's direct file-registration MCP tool becomes a read-only query in `clarion` mode. +**Error code**: `FILIGREE_FILE_REGISTRY_DISPLACED` is returned by Filigree to any caller that tries to directly mutate `file_records` (e.g., `register_file` MCP tool) while `registry_backend: loomweave` is active. The write path is Loomweave's; Filigree's direct file-registration MCP tool becomes a read-only query in `loomweave` mode. -**Startup failure mode**: if Filigree starts with `registry_backend: clarion` but Clarion's read API is unreachable, Filigree refuses writes (returns `503 Service Unavailable` from the three auto-create paths) rather than silently degrading to `local`. An explicit `--allow-local-fallback` flag exists for single-operator recovery scenarios; the default is fail-closed. +**Startup failure mode**: if Filigree starts with `registry_backend: loomweave` but Loomweave's read API is unreachable, Filigree refuses writes (returns `503 Service Unavailable` from the three auto-create paths) rather than silently degrading to `local`. An explicit `--allow-local-fallback` flag exists for single-operator recovery scenarios; the default is fail-closed. ### Capability Probe Semantics -Clarion's read API exposes `GET /api/v1/_capabilities` for Filigree's +Loomweave's read API exposes `GET /api/v1/_capabilities` for Filigree's registry-backend probe. The response includes: ```json @@ -53,23 +53,23 @@ registry-backend probe. The response includes: `api_version` is the wire-contract version for the HTTP read API. It increments only when the HTTP read API changes incompatibly for existing Filigree clients. -It is not Clarion product semver and must not be compared as a release version. +It is not Loomweave product semver and must not be compared as a release version. -`instance_id` identifies the Clarion project instance serving the API. Filigree +`instance_id` identifies the Loomweave project instance serving the API. Filigree uses it to detect that the same endpoint has been rebound to a different -Clarion project instance. +Loomweave project instance. ### File Identity Semantics -Clarion resolves only existing file-kind rows. When no `kind = 'file'` entity +Loomweave resolves only existing file-kind rows. When no `kind = 'file'` entity row exists for the requested path, the API fails closed with `404 NOT_FOUND`. -Clarion must not synthesize a `core:file:{content_hash}@{canonical_path}` +Loomweave must not synthesize a `core:file:{content_hash}@{canonical_path}` identity. That pattern violates ADR-003's entity-ID grammar and creates shadow IDs that will not match future file-discovery rows. ### Batch Resolution Amendment -Clarion also exposes `POST /api/v1/files:resolve` for callers that need to +Loomweave also exposes `POST /api/v1/files:resolve` for callers that need to resolve many file paths without N HTTP round trips. The request body is `{"paths": [{"path": "...", "language": "..."}, ...]}` with a fixed 1000-path envelope cap plus the normal HTTP body limit. The response preserves @@ -94,18 +94,18 @@ This endpoint is a transport optimization, not a replacement for the single - no trailing `/` - `/` as the separator on every platform -The path is relative to the Clarion project root so file identity responses +The path is relative to the Loomweave project root so file identity responses survive project relocation. It is the path Filigree should store as human and drift context, not an absolute filesystem path. ### Instance Fingerprint -Clarion persists a stable per-project UUID at `.clarion/instance_id`. The first +Loomweave persists a stable per-project UUID at `.loomweave/instance_id`. The first creation writes the file with mode `0600` on Unix. `GET /api/v1/_capabilities` surfaces the UUID as `instance_id`. -Deleting `.clarion/` may create a new instance ID. That is acceptable because it -represents a new Clarion project instance and should be detectable by Filigree. +Deleting `.loomweave/` may create a new instance ID. That is acceptable because it +represents a new Loomweave project instance and should be detectable by Filigree. ### Error Envelope @@ -113,7 +113,7 @@ Non-2xx read API responses use a closed JSON envelope: ```json { - "error": "path does not resolve to a Clarion file entity", + "error": "path does not resolve to a Loomweave file entity", "code": "NOT_FOUND" } ``` @@ -133,55 +133,55 @@ Clients must switch on `code`, not on human-readable `error` text. The HTTP read API is loopback-only by default and may remain unauthenticated for local sidecar workflows. Authenticated deployments configure -`serve.http.identity_token_env`; Clarion refuses to start if that env var is +`serve.http.identity_token_env`; Loomweave refuses to start if that env var is missing, and protected read routes require -`X-Loom-Component: clarion:`. +`X-Weft-Component: loomweave:`. The HMAC is lowercase hex HMAC-SHA256 over `METHOD`, `PATH_AND_QUERY`, and the -SHA-256 request-body hash separated by newlines. Clarion refuses non-loopback +SHA-256 request-body hash separated by newlines. Loomweave refuses non-loopback HTTP binds unless `serve.http.allow_non_loopback: true` is set, and a non-loopback bind must have either the HMAC identity secret or the legacy bearer token resolved at startup. ## Alternatives Considered -### Alternative 1: Clarion-native registry without a flag — hard displacement +### Alternative 1: Loomweave-native registry without a flag — hard displacement -Filigree always delegates to Clarion. No `registry_backend` flag; no `local` mode. +Filigree always delegates to Loomweave. No `registry_backend` flag; no `local` mode. **Pros**: single code path in Filigree; no dual-mode testing surface. -**Cons**: violates Loom federation (`loom.md` §4 composition law). Filigree becomes semantically dependent on Clarion running — "removing Clarion changes the meaning of Filigree's own data" (§5 failure test). `loom.md` §5's explicit Filigree example ("Filigree creates and closes tickets exactly the same way whether Clarion is installed or not") fails. Also makes Filigree's existing deployments (including `filigree` itself, which uses Filigree for its own issue tracking) require Clarion, which is absurd for a product that ships standalone today. +**Cons**: violates Weft federation (`weft.md` §4 composition law). Filigree becomes semantically dependent on Loomweave running — "removing Loomweave changes the meaning of Filigree's own data" (§5 failure test). `weft.md` §5's explicit Filigree example ("Filigree creates and closes tickets exactly the same way whether Loomweave is installed or not") fails. Also makes Filigree's existing deployments (including `filigree` itself, which uses Filigree for its own issue tracking) require Loomweave, which is absurd for a product that ships standalone today. **Why rejected**: pairwise composability is a hard rule, not an aspiration. -### Alternative 2: Schema-level surgery — replace `file_records(id)` with a foreign key into Clarion +### Alternative 2: Schema-level surgery — replace `file_records(id)` with a foreign key into Loomweave -Eliminate `file_records` and reference Clarion's entity catalog directly via an external database handle or JSONB column. +Eliminate `file_records` and reference Loomweave's entity catalog directly via an external database handle or JSONB column. **Pros**: single source of truth at the storage layer; no RPC round-trip on every operation. -**Cons**: fundamentally couples Filigree's database to Clarion's. Violates `loom.md` §6 ("A central store or database... No shared SQLite/Postgres sits under the suite"). Every Filigree operator would need a local Clarion database even in pure-Filigree deployments. Migration cost is high and irreversible. +**Cons**: fundamentally couples Filigree's database to Loomweave's. Violates `weft.md` §6 ("A central store or database... No shared SQLite/Postgres sits under the suite"). Every Filigree operator would need a local Loomweave database even in pure-Filigree deployments. Migration cost is high and irreversible. -**Why rejected**: the whole point of the Loom architecture is that each product owns its storage. Schema surgery is a stealth-monolith pattern. +**Why rejected**: the whole point of the Weft architecture is that each product owns its storage. Schema surgery is a stealth-monolith pattern. -### Alternative 3: Event-driven sync — Clarion pushes entity state to Filigree +### Alternative 3: Event-driven sync — Loomweave pushes entity state to Filigree -Clarion maintains its catalog and publishes file-identity events to Filigree via a webhook or event bus. Filigree reconciles its `file_records` asynchronously. +Loomweave maintains its catalog and publishes file-identity events to Filigree via a webhook or event bus. Filigree reconciles its `file_records` asynchronously. **Pros**: keeps Filigree's storage independent; allows eventual consistency. -**Cons**: introduces an event-delivery mechanism that does not exist today. Filigree writes (from non-Clarion sources, e.g., manual scans, other Loom siblings) would have no immediate Clarion ID available and would need deferred reconciliation. Error-recovery semantics are complex (what if the event bus is down?). Also, Loom already prohibits shared infrastructure (`loom.md` §6) — an event bus qualifies. +**Cons**: introduces an event-delivery mechanism that does not exist today. Filigree writes (from non-Loomweave sources, e.g., manual scans, other Weft siblings) would have no immediate Loomweave ID available and would need deferred reconciliation. Error-recovery semantics are complex (what if the event bus is down?). Also, Weft already prohibits shared infrastructure (`weft.md` §6) — an event bus qualifies. **Why rejected**: too much mechanism for a problem that has a simpler synchronous answer. ### Alternative 4: Leave it as shadow-registry permanently — no displacement -Accept that Filigree mints its own file IDs forever; Clarion reconciles post-hoc via path + hash. +Accept that Filigree mints its own file IDs forever; Loomweave reconciles post-hoc via path + hash. **Pros**: zero Filigree-side work; preserves total independence. -**Cons**: the "Clarion owns structural truth" claim in `loom.md` §2 becomes a lie — Filigree owns the authoritative file ID for everything it stores, and Clarion's catalog is the shadow. Cross-tool "same file" queries have no deterministic answer when file paths change. Issues referencing Filigree file IDs cannot round-trip to Clarion entity IDs without a fragile path-based join. +**Cons**: the "Loomweave owns structural truth" claim in `weft.md` §2 becomes a lie — Filigree owns the authoritative file ID for everything it stores, and Loomweave's catalog is the shadow. Cross-tool "same file" queries have no deterministic answer when file paths change. Issues referencing Filigree file IDs cannot round-trip to Loomweave entity IDs without a fragile path-based join. **Why rejected**: turns a v0.1 deferral into a permanent identity-model concession; the cost compounds across every future cross-tool query. @@ -190,38 +190,38 @@ Accept that Filigree mints its own file IDs forever; Clarion reconciles post-hoc ### Positive - Preserves Filigree's standalone usability (`registry_backend: local` is the default). -- Makes "Clarion owns structural truth" honest: in `registry_backend: clarion` mode, Filigree's file IDs *are* Clarion's entity IDs, not a shadow mapping. +- Makes "Loomweave owns structural truth" honest: in `registry_backend: loomweave` mode, Filigree's file IDs *are* Loomweave's entity IDs, not a shadow mapping. - Creates a clean contract surface for v0.2+ alternative registry backends (e.g., `registry_backend: git-objects`). -- `FILIGREE_FILE_REGISTRY_DISPLACED` surfaces the coupling explicitly; operators running `clarion` mode know why direct file-registration MCP calls fail. -- Capability probe (`GET /api/files/_schema.config_flags`) means Clarion discovers the mode at runtime rather than requiring synchronised deployment. +- `FILIGREE_FILE_REGISTRY_DISPLACED` surfaces the coupling explicitly; operators running `loomweave` mode know why direct file-registration MCP calls fail. +- Capability probe (`GET /api/files/_schema.config_flags`) means Loomweave discovers the mode at runtime rather than requiring synchronised deployment. ### Negative - Two Filigree code paths per auto-create operation (local vs delegated). Testing surface doubles for file-registry operations. -- `registry_backend: clarion` mode introduces a synchronous RPC hop on every Filigree write that touches `file_records`. Latency impact: one local HTTP round-trip to Clarion (typically <5ms on loopback). Acceptable for developer-machine workloads; would need re-evaluation for high-throughput server deployments. -- Fail-closed startup (Filigree refuses writes if Clarion is unreachable under `clarion` mode) means operators must start Clarion before Filigree — or use the explicit `--allow-local-fallback` recovery flag. -- Shadow-registry mode (for operators who never want to run Clarion) remains available, but Clarion now has *two* v0.1 shapes it needs to test (`clarion` mode and shadow mode). ADR-020's degraded-mode policy covers the testing burden. +- `registry_backend: loomweave` mode introduces a synchronous RPC hop on every Filigree write that touches `file_records`. Latency impact: one local HTTP round-trip to Loomweave (typically <5ms on loopback). Acceptable for developer-machine workloads; would need re-evaluation for high-throughput server deployments. +- Fail-closed startup (Filigree refuses writes if Loomweave is unreachable under `loomweave` mode) means operators must start Loomweave before Filigree — or use the explicit `--allow-local-fallback` recovery flag. +- Shadow-registry mode (for operators who never want to run Loomweave) remains available, but Loomweave now has *two* v0.1 shapes it needs to test (`loomweave` mode and shadow mode). ADR-020's degraded-mode policy covers the testing burden. ### Neutral -- Clarion's existing HTTP read API is the contract; no new endpoints are introduced for Filigree's consumption — `resolve_file(path, language)` is just a read through the existing file-entity query surface. +- Loomweave's existing HTTP read API is the contract; no new endpoints are introduced for Filigree's consumption — `resolve_file(path, language)` is just a read through the existing file-entity query surface. - The `FILIGREE_FILE_REGISTRY_DISPLACED` error code lives in Filigree's error-code registry alongside existing codes; it does not become a cross-product shared enum. ## Related Decisions -- [ADR-003](./ADR-003-entity-id-scheme.md) — symbolic entity IDs are what `clarion` mode uses as `file_records.id` values. +- [ADR-003](./ADR-003-entity-id-scheme.md) — symbolic entity IDs are what `loomweave` mode uses as `file_records.id` values. - [ADR-004](./ADR-004-finding-exchange-format.md) — findings intake uses the same `file_id` that `resolve_file` returns. - ADR-008 (superseded) — earlier framing of this decision as a "feature flag"; the recon revealed it is an interface, not a flag. - [ADR-012](./ADR-012-http-auth-default.md) — superseded for this registry-backend HTTP read surface; ADR-014 owns the unauthenticated loopback-only trust model and non-loopback guard. -- [ADR-016](./ADR-016-observation-transport.md) — the `create_observation(file_path=…)` auto-create path is one of the three delegated operations; under `registry_backend: clarion` mode the `file_id` resolution in that path uses this ADR's protocol regardless of whether ADR-016's transport is MCP-spawn (v0.1) or HTTP (v0.2). -- [ADR-017](./ADR-017-severity-and-dedup.md) — `mark_unseen=true` dedup relies on stable file IDs, which `clarion` mode provides and shadow mode does not. -- [ADR-018](./ADR-018-identity-reconciliation.md) — the qualname ↔ EntityId translation layer is adjacent; `clarion` mode's `file_id` resolution is one slice of the broader identity-reconciliation surface. +- [ADR-016](./ADR-016-observation-transport.md) — the `create_observation(file_path=…)` auto-create path is one of the three delegated operations; under `registry_backend: loomweave` mode the `file_id` resolution in that path uses this ADR's protocol regardless of whether ADR-016's transport is MCP-spawn (v0.1) or HTTP (v0.2). +- [ADR-017](./ADR-017-severity-and-dedup.md) — `mark_unseen=true` dedup relies on stable file IDs, which `loomweave` mode provides and shadow mode does not. +- [ADR-018](./ADR-018-identity-reconciliation.md) — the qualname ↔ EntityId translation layer is adjacent; `loomweave` mode's `file_id` resolution is one slice of the broader identity-reconciliation surface. - ADR-020 (pending; see the [ADR index backlog](./README.md)) — shadow-registry mode is one of the enumerated degraded modes. - [ADR-022](./ADR-022-core-plugin-ontology.md) — file-kind entities are the narrowest ontology surface the plugin-vs-core boundary governs; ADR-022 states the `file` kind's core ownership as a first-class decision, and this ADR is the downstream consumer that depends on it. ## References -- [Clarion v0.1 system design §9](../v0.1/system-design.md) — integration posture; capability probe; degraded modes. +- [Loomweave v0.1 system design §9](../v0.1/system-design.md) — integration posture; capability probe; degraded modes. - [Integration reconnaissance §2.1](../../implementation/v0.1-reviews/pre-restructure/integration-recon.md) — `file_records` schema; four NOT-NULL foreign keys; three auto-create paths; verified absence of `registry_backend` and error code. -- [Loom doctrine §4, §5, §6](../../suite/loom.md) — pairwise composability; enrichment failure test; no-shared-store rule. -- [Clarion v0.1 scope commitments](../../implementation/v0.1-scope-plans/v0.1-scope-commitments.md) — Q2 commits `registry_backend` to v0.1 as within-scope Filigree work. +- [Weft doctrine §4, §5, §6](../../suite/weft.md) — pairwise composability; enrichment failure test; no-shared-store rule. +- [Loomweave v0.1 scope commitments](../../implementation/v0.1-scope-plans/v0.1-scope-commitments.md) — Q2 commits `registry_backend` to v0.1 as within-scope Filigree work. diff --git a/docs/clarion/adr/ADR-015-wardline-filigree-emission.md b/docs/loomweave/adr/ADR-015-wardline-filigree-emission.md similarity index 53% rename from docs/clarion/adr/ADR-015-wardline-filigree-emission.md rename to docs/loomweave/adr/ADR-015-wardline-filigree-emission.md index 730c3b1d..5b773777 100644 --- a/docs/clarion/adr/ADR-015-wardline-filigree-emission.md +++ b/docs/loomweave/adr/ADR-015-wardline-filigree-emission.md @@ -1,27 +1,27 @@ -# ADR-015: Wardline→Filigree Emission Ownership — Clarion-Side Translator (v0.1), Native Wardline Emitter (v0.2) +# ADR-015: Wardline→Filigree Emission Ownership — Loomweave-Side Translator (v0.1), Native Wardline Emitter (v0.2) -**Status**: Accepted (Revision 2, 2026-05-29 — native Wardline emitter promoted to the production path; Clarion off the transport path. See [Revision 2](#revision-2-2026-05-29--wardline-ships-native-clarion-off-the-transport-path).) +**Status**: Accepted (Revision 2, 2026-05-29 — native Wardline emitter promoted to the production path; Loomweave off the transport path. See [Revision 2](#revision-2-2026-05-29--wardline-ships-native-loomweave-off-the-transport-path).) **Date**: 2026-04-18 **Deciders**: qacona@gmail.com **Context**: Wardline has no HTTP client today; getting its findings into Filigree needs an owner for v0.1 and a retirement plan for v0.2 ## Summary -In v0.1, Wardline's findings reach Filigree via Clarion's `clarion sarif import --scan-source wardline` CLI — a translator that reads Wardline's on-disk SARIF output, translates to Filigree-native intake (ADR-004), and POSTs to `/api/v1/scan-results`. In v0.2, Wardline gains a native `POST /api/v1/scan-results` emitter, which eliminates the Clarion-in-the-middle path for the (Wardline, Filigree) pair. The translator itself stays forever — it serves Semgrep, CodeQL, Trivy, and every other SARIF-emitting tool — but the translator's role as "v0.1 Wardline bridge" retires when the native emitter lands. +In v0.1, Wardline's findings reach Filigree via Loomweave's `loomweave sarif import --scan-source wardline` CLI — a translator that reads Wardline's on-disk SARIF output, translates to Filigree-native intake (ADR-004), and POSTs to `/api/v1/scan-results`. In v0.2, Wardline gains a native `POST /api/v1/scan-results` emitter, which eliminates the Loomweave-in-the-middle path for the (Wardline, Filigree) pair. The translator itself stays forever — it serves Semgrep, CodeQL, Trivy, and every other SARIF-emitting tool — but the translator's role as "v0.1 Wardline bridge" retires when the native emitter lands. -This arrangement is named as an explicit **pipeline-coupling asterisk** in `loom.md` §5 (asterisk 1). It violates pairwise composability between Wardline and Filigree until v0.2 and ships that way only with a written retirement condition. **Revision trigger**: if the Block C2 Wardline-native-emitter spike shows the native emitter is ≤1 day of work, this ADR is revised in place to promote the native emitter into v0.1 and retire the loom.md §5 asterisk. +This arrangement is named as an explicit **pipeline-coupling asterisk** in `weft.md` §5 (asterisk 1). It violates pairwise composability between Wardline and Filigree until v0.2 and ships that way only with a written retirement condition. **Revision trigger**: if the Block C2 Wardline-native-emitter spike shows the native emitter is ≤1 day of work, this ADR is revised in place to promote the native emitter into v0.1 and retire the weft.md §5 asterisk. ## Context Wardline today has no HTTP client (`reviews/pre-restructure/integration-recon.md:339`). `wardline/pyproject.toml:22` declares `dependencies = []` and the CI pipeline uploads SARIF to GitHub Security, not Filigree. For Wardline's findings to reach Filigree at all, something has to read Wardline's SARIF output and POST it. -The `loom.md` §5 failure test names pipeline coupling as a federation violation: "if a pair of sibling products (X, Z) cannot exchange data except through a third sibling (Y)." The (Wardline, Filigree) pair cannot compose directly in v0.1 — they compose only when Clarion is present and someone runs `clarion sarif import`. That is the triangle the panel flagged in its doctrine synthesis (`11-doctrine-panel-synthesis.md` paragraph on the SARIF triangle) and the scope-commitments memo confirmed as an open consequence (`v0.1-scope-commitments.md:72`). +The `weft.md` §5 failure test names pipeline coupling as a federation violation: "if a pair of sibling products (X, Z) cannot exchange data except through a third sibling (Y)." The (Wardline, Filigree) pair cannot compose directly in v0.1 — they compose only when Loomweave is present and someone runs `loomweave sarif import`. That is the triangle the panel flagged in its doctrine synthesis (`11-doctrine-panel-synthesis.md` paragraph on the SARIF triangle) and the scope-commitments memo confirmed as an open consequence (`v0.1-scope-commitments.md:72`). Three decisions exist inside the emission question: -1. **Who owns the v0.1 path?** Clarion (Option A) or Wardline (Option B)? +1. **Who owns the v0.1 path?** Loomweave (Option A) or Wardline (Option B)? 2. **When does Wardline gain a native path?** v0.2 default, or a v0.1 stretch-goal contingent on a cheap implementation? -3. **What happens to the Clarion translator when Wardline gains native emission?** Does it retire entirely, or stay for other SARIF emitters? +3. **What happens to the Loomweave translator when Wardline gains native emission?** Does it retire entirely, or stay for other SARIF emitters? The default committed answers in `../../implementation/v0.1-scope-plans/v0.1-scope-commitments.md:190` are: Option A for v0.1, Option B for v0.2 (default deferral, optional promotion pending spike), translator stays permanently for non-Wardline SARIF sources. @@ -29,9 +29,9 @@ This ADR formalises those answers and names the revision trigger. ## Decision -### v0.1 position — Clarion-side SARIF translator +### v0.1 position — Loomweave-side SARIF translator -Clarion ships a CLI at `clarion sarif import [--scan-source ]`. The v0.1 Wardline path is `clarion sarif import wardline.sarif.baseline.json --scan-source wardline`. The translator: +Loomweave ships a CLI at `loomweave sarif import [--scan-source ]`. The v0.1 Wardline path is `loomweave sarif import wardline.sarif.baseline.json --scan-source wardline`. The translator: - Reads Wardline's SARIF output from disk. - Translates to Filigree-native `POST /api/v1/scan-results` format (ADR-004). @@ -40,13 +40,13 @@ Clarion ships a CLI at `clarion sarif import [--scan-source ] The translator is a general-purpose feature, not a Wardline-specific bridge. `--scan-source` defaults to the SARIF driver name, lowercased — `semgrep`, `codeql`, `trivy`, and any future SARIF emitter use the same translator with different `scan_source` values. -Operators run the translator as part of their CI pipeline (Wardline → SARIF on disk → `clarion sarif import` → Filigree) or on-demand after local Wardline runs. Wardline findings reach Filigree *only* when someone runs the translator — this is the "pipeline coupling" shape. +Operators run the translator as part of their CI pipeline (Wardline → SARIF on disk → `loomweave sarif import` → Filigree) or on-demand after local Wardline runs. Wardline findings reach Filigree *only* when someone runs the translator — this is the "pipeline coupling" shape. ### v0.2 retirement — native Wardline emitter Wardline gains an HTTP client (`httpx` or `requests`, to be decided Wardline-side) and emits findings directly to `POST /api/v1/scan-results` from its scanner path. When this lands: -- The `loom.md` §5 asterisk 1 retires. The briefing's "Wardline-sourced findings" data-flow row changes from v0.2-deferred to current. +- The `weft.md` §5 asterisk 1 retires. The briefing's "Wardline-sourced findings" data-flow row changes from v0.2-deferred to current. - The translator's role as "v0.1 Wardline bridge" retires. The translator itself remains for non-Wardline SARIF sources; Wardline no longer flows through it. - `--scan-source wardline` remains usable for historical SARIF baselines (e.g., re-ingesting an old Wardline scan) but stops being the production path. @@ -61,11 +61,11 @@ Wardline-side commitments for v0.2: The default commitment assumes Wardline's native emitter is significant enough to defer to v0.2. The Block C2 spike (one day, optional) investigates whether adding `httpx` + a single emit call is ≤1 day of work end-to-end. If the spike shows it is: - This ADR is revised in place (new dated revision, per `system-design.md:1206` writing-cadence rule). Position flips to Option B in v0.1. -- The `loom.md` §5 asterisk 1 is deleted. +- The `weft.md` §5 asterisk 1 is deleted. - The briefing's "Wardline-sourced findings" data-flow row updates to current. - The translator's framing loses its "v0.1 Wardline bridge" role from day one; it ships as a general-purpose SARIF ingest path. -If the spike is not run, or shows the refactor is >1 day, this ADR stands as-is and the retirement condition lives in `loom.md` §5. +If the spike is not run, or shows the refactor is >1 day, this ADR stands as-is and the retirement condition lives in `weft.md` §5. ### Spike result (research-level, 2026-04-18) @@ -79,75 +79,75 @@ A research pass (code inspection only, no implementation) against `/home/john/wa **Verdict (research-level)**: borderline at the promotion threshold (<1 day). A determined half-day is plausible for the minimum-viable emitter; an unhurried day is more realistic; an integration-test-thorough pass could go to 1.5 days. This is the kind of boundary where "cheap enough" is a judgement call, not a measurement. -**Decision (2026-04-18)**: ADR-015 stands as-is — **v0.1 = Clarion SARIF translator, v0.2 = Wardline native emitter**. Rationale: the research-level estimate is bounded but the v0.1 sprint is already loaded with 10 ADRs + auth flip + secret scanner implementation; adding 1 day of Wardline-side refactor plus its own cross-tool integration tests is a scope delta the user may choose to take on, but defaulting to the v0.2 commitment preserves the documented scope-commitment shape. The spike result is recorded here so a later decision to flip can cite concrete evidence without re-running the research. +**Decision (2026-04-18)**: ADR-015 stands as-is — **v0.1 = Loomweave SARIF translator, v0.2 = Wardline native emitter**. Rationale: the research-level estimate is bounded but the v0.1 sprint is already loaded with 10 ADRs + auth flip + secret scanner implementation; adding 1 day of Wardline-side refactor plus its own cross-tool integration tests is a scope delta the user may choose to take on, but defaulting to the v0.2 commitment preserves the documented scope-commitment shape. The spike result is recorded here so a later decision to flip can cite concrete evidence without re-running the research. -Operators or maintainers reviewing this decision later: if the Wardline-native emitter is implemented as part of Clarion v0.1 work, revise this ADR with a dated "Revision 2" section, delete the `loom.md` §5 asterisk 1, and update the briefing's "Wardline-sourced findings" data-flow row. +Operators or maintainers reviewing this decision later: if the Wardline-native emitter is implemented as part of Loomweave v0.1 work, revise this ADR with a dated "Revision 2" section, delete the `weft.md` §5 asterisk 1, and update the briefing's "Wardline-sourced findings" data-flow row. -## Revision 2 (2026-05-29) — Wardline ships native; Clarion off the transport path +## Revision 2 (2026-05-29) — Wardline ships native; Loomweave off the transport path -**Trigger.** The 2026-05-29 Wardline ↔ Loom integration brief states that the generic Wardline rebuild intends to ship the **native Filigree emitter directly** (Wardline-side), emitting findings to `POST /api/v1/scan-results` from its own scanner path. This is the ~1-day emitter the 2026-04-18 spike already costed — now taken on Wardline-side rather than as Clarion v0.1 work. This revision records the flip the original decision and its closing note anticipated. +**Trigger.** The 2026-05-29 Wardline ↔ Weft integration brief states that the generic Wardline rebuild intends to ship the **native Filigree emitter directly** (Wardline-side), emitting findings to `POST /api/v1/scan-results` from its own scanner path. This is the ~1-day emitter the 2026-04-18 spike already costed — now taken on Wardline-side rather than as Loomweave v0.1 work. This revision records the flip the original decision and its closing note anticipated. -**Position flip.** Option B (native Wardline emitter) becomes the production path for the (Wardline, Filigree) pair. **Clarion is no longer on the transport path for that pair.** The decision in §"v0.1 position — Clarion-side SARIF translator" is superseded for Wardline specifically; it stands unchanged for every other SARIF source. +**Position flip.** Option B (native Wardline emitter) becomes the production path for the (Wardline, Filigree) pair. **Loomweave is no longer on the transport path for that pair.** The decision in §"v0.1 position — Loomweave-side SARIF translator" is superseded for Wardline specifically; it stands unchanged for every other SARIF source. **What changes:** -- **Clarion's role becomes pure enrichment.** Clarion reconciles Wardline findings to `EntityId`s via the qualname carried on the finding (`metadata.wardline.qualname`), per [ADR-018](./ADR-018-identity-reconciliation.md) and its 2026-05-29 amendment. It is no longer a bridge — it adds entity context to findings that reach Filigree on their own. -- **The SARIF translator stays, general-purpose.** `clarion sarif import --scan-source ` continues to serve Semgrep, CodeQL, Trivy, and any other SARIF emitter. `--scan-source wardline` remains usable for re-ingesting historical Wardline SARIF baselines, but is no longer the production Wardline path. -- **Severity mapping moves off Clarion for Wardline.** The Wardline 4-level → Filigree 5-level mapping (integration brief §5: `CRITICAL→critical`, `ERROR→high`, `WARN→medium`, `INFO→low`, `NONE→info`) is now a direct Wardline→Filigree concern applied by the native emitter. [ADR-017](./ADR-017-severity-and-dedup.md)'s severity/rule-ID rules continue to apply to the translator for *other* SARIF sources; their role as the Wardline translation step retires with the transport path. +- **Loomweave's role becomes pure enrichment.** Loomweave reconciles Wardline findings to `EntityId`s via the qualname carried on the finding (`metadata.wardline.qualname`), per [ADR-018](./ADR-018-identity-reconciliation.md) and its 2026-05-29 amendment. It is no longer a bridge — it adds entity context to findings that reach Filigree on their own. +- **The SARIF translator stays, general-purpose.** `loomweave sarif import --scan-source ` continues to serve Semgrep, CodeQL, Trivy, and any other SARIF emitter. `--scan-source wardline` remains usable for re-ingesting historical Wardline SARIF baselines, but is no longer the production Wardline path. +- **Severity mapping moves off Loomweave for Wardline.** The Wardline 4-level → Filigree 5-level mapping (integration brief §5: `CRITICAL→critical`, `ERROR→high`, `WARN→medium`, `INFO→low`, `NONE→info`) is now a direct Wardline→Filigree concern applied by the native emitter. [ADR-017](./ADR-017-severity-and-dedup.md)'s severity/rule-ID rules continue to apply to the translator for *other* SARIF sources; their role as the Wardline translation step retires with the transport path. -**Asterisk-1 retirement — on ship, not on agreement.** `loom.md` §5 asterisk 1 (pipeline coupling of the (Wardline, Filigree) pair through Clarion) **is not deleted by this revision.** Wardline's generic build is in flight and the native emitter is not yet shipped or verified in production. Per `loom.md` §5 ("Asterisks are acceptable only with a written retirement condition"), deleting the asterisk on a promise is itself the failure mode the section guards against. This revision instead **rewrites the asterisk's retirement mechanism**: the native emitter is now Wardline-side (this brief), not the previously-planned Clarion-side v0.2 work. The asterisk stays live, tracked under `release:1.1`, and retires when the emitter ships and Wardline→Filigree composition is verified without Clarion present. At that point: delete asterisk 1 from `loom.md` §5 and update the briefing's "Wardline-sourced findings" data-flow row. +**Asterisk-1 retirement — on ship, not on agreement.** `weft.md` §5 asterisk 1 (pipeline coupling of the (Wardline, Filigree) pair through Loomweave) **is not deleted by this revision.** Wardline's generic build is in flight and the native emitter is not yet shipped or verified in production. Per `weft.md` §5 ("Asterisks are acceptable only with a written retirement condition"), deleting the asterisk on a promise is itself the failure mode the section guards against. This revision instead **rewrites the asterisk's retirement mechanism**: the native emitter is now Wardline-side (this brief), not the previously-planned Loomweave-side v0.2 work. The asterisk stays live, tracked under `release:1.1`, and retires when the emitter ships and Wardline→Filigree composition is verified without Loomweave present. At that point: delete asterisk 1 from `weft.md` §5 and update the briefing's "Wardline-sourced findings" data-flow row. -**Open contract back to Wardline (does not block this decision).** Reconciliation correctness now depends on Wardline composing the dotted `module.qualified_name` with rules byte-identical to Clarion's `module_dotted_name()` — see the [ADR-018](./ADR-018-identity-reconciliation.md) 2026-05-29 amendment for the contract and the failure mode (silent degradation to `resolution_confidence: none` on nested-class and closure entities). This is an enrichment-quality concern, not a transport-path concern; the (Wardline, Filigree) pair composes regardless. +**Open contract back to Wardline (does not block this decision).** Reconciliation correctness now depends on Wardline composing the dotted `module.qualified_name` with rules byte-identical to Loomweave's `module_dotted_name()` — see the [ADR-018](./ADR-018-identity-reconciliation.md) 2026-05-29 amendment for the contract and the failure mode (silent degradation to `resolution_confidence: none` on nested-class and closure entities). This is an enrichment-quality concern, not a transport-path concern; the (Wardline, Filigree) pair composes regardless. ## Alternatives Considered -### Alternative 1: Make SARIF the direct Clarion/Wardline → Filigree contract (Filigree grows SARIF ingest) +### Alternative 1: Make SARIF the direct Loomweave/Wardline → Filigree contract (Filigree grows SARIF ingest) -Filigree adds a `POST /api/v1/sarif` endpoint. Every emitting tool POSTs SARIF directly; no Clarion translator needed. +Filigree adds a `POST /api/v1/sarif` endpoint. Every emitting tool POSTs SARIF directly; no Loomweave translator needed. **Pros**: aligned with the broader SARIF tool ecosystem; eliminates translator ownership questions entirely. -**Cons**: Filigree's production intake is deliberately Filigree-native flat JSON (ADR-004). Growing a second ingest path doubles the schema surface Filigree must validate. Does not actually collapse the triangle — Wardline still has no HTTP client, so something still has to ship the file. Relocates Clarion's translator to Filigree's side, which makes Filigree the new centralisation site for SARIF interpretation. +**Cons**: Filigree's production intake is deliberately Filigree-native flat JSON (ADR-004). Growing a second ingest path doubles the schema surface Filigree must validate. Does not actually collapse the triangle — Wardline still has no HTTP client, so something still has to ship the file. Relocates Loomweave's translator to Filigree's side, which makes Filigree the new centralisation site for SARIF interpretation. -**Why rejected**: ADR-004 already decided Filigree-native is the canonical Clarion→Filigree contract; SARIF is a translator input/output path. Reopening that at the Wardline boundary re-introduces the "invent a suite schema" option ADR-004 rejected. +**Why rejected**: ADR-004 already decided Filigree-native is the canonical Loomweave→Filigree contract; SARIF is a translator input/output path. Reopening that at the Wardline boundary re-introduces the "invent a suite schema" option ADR-004 rejected. ### Alternative 2: Wardline native emitter in v0.1 (default-position promotion without spike) Commit Option B for v0.1 unconditionally. Wardline adds an HTTP client in the v0.1 release cycle. -**Pros**: collapses the (Wardline, Filigree) triangle immediately; `loom.md` §5 asterisk 1 never exists. +**Pros**: collapses the (Wardline, Filigree) triangle immediately; `weft.md` §5 asterisk 1 never exists. -**Cons**: Wardline's `dependencies = []` posture is deliberate, and changing it needs consideration that Wardline's own design owes rather than a Clarion ADR imposing it. The refactor adds retry/auth/error-handling plumbing to Wardline's scanner path, which currently ends at SARIF-on-disk. For a v0.1 timeline that already carries 10 P0 ADRs, a surprise day-2+ of Wardline-side plumbing work is real timeline risk. +**Cons**: Wardline's `dependencies = []` posture is deliberate, and changing it needs consideration that Wardline's own design owes rather than a Loomweave ADR imposing it. The refactor adds retry/auth/error-handling plumbing to Wardline's scanner path, which currently ends at SARIF-on-disk. For a v0.1 timeline that already carries 10 P0 ADRs, a surprise day-2+ of Wardline-side plumbing work is real timeline risk. **Why rejected as default**: the scope-commitments memo explicitly defers this to v0.2 unless the C2 spike shows it is cheap. The trigger condition preserves the upside (if it *is* cheap, v0.1 collapses the triangle) without committing to it sight-unseen. -### Alternative 3: Permanent Clarion translator ownership of Wardline findings +### Alternative 3: Permanent Loomweave translator ownership of Wardline findings -Option A for v0.1 and forever. Wardline never grows an HTTP client; Clarion always owns the Wardline→Filigree path. +Option A for v0.1 and forever. Wardline never grows an HTTP client; Loomweave always owns the Wardline→Filigree path. -**Pros**: no Wardline changes ever; Clarion's translator is a single owner. +**Pros**: no Wardline changes ever; Loomweave's translator is a single owner. -**Cons**: the pipeline-coupling asterisk never retires. `(Wardline, Filigree)` can never compose without Clarion running, which permanently violates `loom.md` §5 for that pair. A three-product suite where one pair cannot compose pairwise without a third is exactly the shape Loom's failure test is supposed to prevent. +**Cons**: the pipeline-coupling asterisk never retires. `(Wardline, Filigree)` can never compose without Loomweave running, which permanently violates `weft.md` §5 for that pair. A three-product suite where one pair cannot compose pairwise without a third is exactly the shape Weft's failure test is supposed to prevent. -**Why rejected**: an asterisk without a retirement condition is not an asterisk — it is the stealth-monolith pattern. `loom.md` §5 itself says so ("Asterisks are acceptable only with a written retirement condition"). +**Why rejected**: an asterisk without a retirement condition is not an asterisk — it is the stealth-monolith pattern. `weft.md` §5 itself says so ("Asterisks are acceptable only with a written retirement condition"). -### Alternative 4: Custom streaming format — Wardline emits JSON Lines to stdout; Clarion tails +### Alternative 4: Custom streaming format — Wardline emits JSON Lines to stdout; Loomweave tails -Wardline writes findings to stdout as JSON Lines; Clarion tails the process and POSTs. No SARIF, no file on disk. +Wardline writes findings to stdout as JSON Lines; Loomweave tails the process and POSTs. No SARIF, no file on disk. **Pros**: avoids a disk round-trip; avoids SARIF format overhead. -**Cons**: invents a format that is neither SARIF (no future-proof ecosystem benefit) nor Filigree-native (still needs translation). Adds a Wardline-Clarion-specific format surface. Wardline's existing SARIF-on-disk output is useful independently (GitHub Security upload); a stdout-only path regresses that utility. +**Cons**: invents a format that is neither SARIF (no future-proof ecosystem benefit) nor Filigree-native (still needs translation). Adds a Wardline-Loomweave-specific format surface. Wardline's existing SARIF-on-disk output is useful independently (GitHub Security upload); a stdout-only path regresses that utility. **Why rejected**: gains nothing over SARIF-on-disk + translator; adds format-maintenance cost. -### Alternative 5: Shared internal library consumed by both Wardline and Clarion +### Alternative 5: Shared internal library consumed by both Wardline and Loomweave -A `loom-findings` package that both products import; the library owns emission to Filigree. +A `weft-findings` package that both products import; the library owns emission to Filigree. **Pros**: no translator anywhere; findings always reach Filigree regardless of which tool produces them. -**Cons**: violates `loom.md` §6 ("no shared runtime, no shared configuration layer"). A shared library that both products import is a dependency-level stealth monolith — removing the library breaks both products simultaneously. Wardline's `dependencies = []` posture collapses the moment it imports `loom-findings`. +**Cons**: violates `weft.md` §6 ("no shared runtime, no shared configuration layer"). A shared library that both products import is a dependency-level stealth monolith — removing the library breaks both products simultaneously. Wardline's `dependencies = []` posture collapses the moment it imports `weft-findings`. **Why rejected**: categorical federation violation. @@ -155,15 +155,15 @@ A `loom-findings` package that both products import; the library owns emission t ### Positive -- Clarion ships v0.1 without requiring a Wardline-side refactor on Clarion's timeline. The translator exists today; the Wardline SARIF output exists today; the path works. -- The translator architecture is general, not Wardline-specific. Semgrep, CodeQL, Trivy, and future SARIF tools all use the same `clarion sarif import --scan-source ` pattern. -- The `loom.md` §5 asterisk 1 has a named retirement condition (native Wardline emitter in v0.2). The asterisk lives in the doctrine doc, not only in Clarion's design, so it is visible suite-wide. +- Loomweave ships v0.1 without requiring a Wardline-side refactor on Loomweave's timeline. The translator exists today; the Wardline SARIF output exists today; the path works. +- The translator architecture is general, not Wardline-specific. Semgrep, CodeQL, Trivy, and future SARIF tools all use the same `loomweave sarif import --scan-source ` pattern. +- The `weft.md` §5 asterisk 1 has a named retirement condition (native Wardline emitter in v0.2). The asterisk lives in the doctrine doc, not only in Loomweave's design, so it is visible suite-wide. - The revision trigger (Block C2 spike) is cheap to evaluate — one day of investigation, with a clear go/no-go threshold. If the native emitter is easy, v0.1 gets the triangle-collapse benefit without the timeline cost of committing to it first. ### Negative -- In v0.1, Wardline findings reach Filigree only when someone runs `clarion sarif import`. Operators who expect Wardline→Filigree to be automatic (CI pipeline, scheduled scan) must add the translator invocation explicitly. The failure mode — Wardline findings silently absent from Filigree — is plausible and needs documentation. Mitigation: the SUITE-COMPAT-REPORT finding (system-design §11) names whether `clarion sarif import` has run recently against each known SARIF source. -- The pipeline-coupling asterisk ships with v0.1. For a product whose marketing register includes "federation", this is a visible gap. Mitigation: it is named as an asterisk, not hidden — `loom.md` §5 carries the retirement condition, and the briefing's data-flow row is labelled v0.2. +- In v0.1, Wardline findings reach Filigree only when someone runs `loomweave sarif import`. Operators who expect Wardline→Filigree to be automatic (CI pipeline, scheduled scan) must add the translator invocation explicitly. The failure mode — Wardline findings silently absent from Filigree — is plausible and needs documentation. Mitigation: the SUITE-COMPAT-REPORT finding (system-design §11) names whether `loomweave sarif import` has run recently against each known SARIF source. +- The pipeline-coupling asterisk ships with v0.1. For a product whose marketing register includes "federation", this is a visible gap. Mitigation: it is named as an asterisk, not hidden — `weft.md` §5 carries the retirement condition, and the briefing's data-flow row is labelled v0.2. - Wardline-side v0.2 work (HTTP client refactor) is within-scope because the user owns Wardline, but it is real engineering time. Mitigation: the C2 spike is the mechanism that clarifies the scope before committing v0.2. ### Neutral @@ -175,14 +175,14 @@ A `loom-findings` package that both products import; the library owns emission t ## Related Decisions - [ADR-004](./ADR-004-finding-exchange-format.md) — Filigree-native intake is the target schema the translator maps to. SARIF is the input; Filigree-native is the output. -- [ADR-017](./ADR-017-severity-and-dedup.md) — severity mapping (SARIF `error`/`warning`/`note` → Clarion `ERROR`/`WARN`/`INFO` → Filigree `high`/`medium`/`info`) and rule-ID round-trip rules apply to this translator. +- [ADR-017](./ADR-017-severity-and-dedup.md) — severity mapping (SARIF `error`/`warning`/`note` → Loomweave `ERROR`/`WARN`/`INFO` → Filigree `high`/`medium`/`info`) and rule-ID round-trip rules apply to this translator. - ADR-019 (pending) — SARIF property-bag preservation (`metadata._properties.*` pass-through) applies here. Wardline's 44 extension keys land under `metadata.wardline_properties.*`. - [ADR-018](./ADR-018-identity-reconciliation.md) — SARIF ingest is translation entry point 3 (via `location.logicalLocations` or `partialFingerprints`). The identity-reconciliation rules named there apply to this translator's `entity_id` resolution. ## References -- [Loom doctrine §5 (v0.1 asterisks, asterisk 1)](../../suite/loom.md) — pipeline-coupling asterisk for (Wardline, Filigree) with this ADR as the retirement condition. -- [Clarion v0.1 detailed design §9 (Wardline prerequisites) item 3](../v0.1/detailed-design.md) (line 1361) — Option A / Option B enumeration. -- [Clarion v0.1 scope commitments](../../implementation/v0.1-scope-plans/v0.1-scope-commitments.md) (lines 72, 190, 202) — default deferral; spike promotion condition. -- [Clarion v0.1 integration reconnaissance §4.3](../../implementation/v0.1-reviews/pre-restructure/integration-recon.md) (line 339) — "Wardline has zero HTTP client code"; empirical basis for the v0.2 deferral default. +- [Weft doctrine §5 (v0.1 asterisks, asterisk 1)](../../suite/weft.md) — pipeline-coupling asterisk for (Wardline, Filigree) with this ADR as the retirement condition. +- [Loomweave v0.1 detailed design §9 (Wardline prerequisites) item 3](../v0.1/detailed-design.md) (line 1361) — Option A / Option B enumeration. +- [Loomweave v0.1 scope commitments](../../implementation/v0.1-scope-plans/v0.1-scope-commitments.md) (lines 72, 190, 202) — default deferral; spike promotion condition. +- [Loomweave v0.1 integration reconnaissance §4.3](../../implementation/v0.1-reviews/pre-restructure/integration-recon.md) (line 339) — "Wardline has zero HTTP client code"; empirical basis for the v0.2 deferral default. - [Panel doctrine synthesis](../../implementation/v0.1-reviews/panel-2026-04-17/11-doctrine-panel-synthesis.md) — SARIF triangle framing; asterisk requirement. diff --git a/docs/loomweave/adr/ADR-016-observation-transport.md b/docs/loomweave/adr/ADR-016-observation-transport.md new file mode 100644 index 00000000..171ef548 --- /dev/null +++ b/docs/loomweave/adr/ADR-016-observation-transport.md @@ -0,0 +1,128 @@ +# ADR-016: Observation Transport — MCP-Spawn (v0.1), Filigree HTTP Endpoint (v0.2) + +**Status**: Accepted +**Date**: 2026-04-18 +**Deciders**: qacona@gmail.com +**Context**: Filigree's observation API is MCP-only today; v0.1 scope commitment deferred the HTTP endpoint to v0.2 + +## Summary + +Loomweave emits observations (LLM-proposed guidance, unknown-vocabulary candidates, `knowledge_basis` signals) to Filigree. In v0.1, Loomweave spawns a `filigree mcp` subprocess and uses Filigree's existing MCP `create_observation` tool as the transport. In v0.2, Filigree adds `POST /api/v1/observations`; Loomweave's observation emit path migrates to HTTP and the subprocess-spawn path retires. The Q1 scope commitment (`v0.1-scope-commitments.md`) explicitly defers the HTTP endpoint to v0.2 — not because it is technically difficult, but because Filigree-side work is already loaded with `registry_backend` surgery (ADR-014) on the v0.1 timeline. MCP-spawn works against the existing Filigree MCP server without Filigree-side change. + +## Context + +Filigree's observation API exists as an MCP tool (`mcp_tools/observations.py` — `create_observation`, `list_observations`, `promote_observation`). There is no HTTP endpoint. The original Loomweave design (system-design §9) framed `POST /api/v1/observations` as the preferred transport with MCP-spawn as a fallback when Filigree hadn't shipped the endpoint yet. The 2026-04-17 panel's scope-commitment review re-litigated which Filigree-side work belongs in v0.1 and which belongs in v0.2. + +The Q1 decision (`v0.1-scope-commitments.md`) committed: + +> **v0.1 scope**: Minimal-core + `registry_backend`. Deferred to v0.2: Wardline→Filigree SARIF bridge, **observation HTTP transport**, summary cache beyond in-memory, HTTP write API. + +That commitment reverses the original "preferred / fallback" framing. In v0.1, the HTTP endpoint does not exist; the capability probe has nothing to detect; MCP-spawn is the v0.1 path, not a fallback. This ADR records that commitment and names the v0.2 retirement trigger. + +Loomweave is a Rust binary. Spawning `filigree mcp` as a subprocess and speaking MCP over stdio is a known shape (it's exactly the plugin transport, ADR-002). The engineering cost is real but bounded: Rust MCP client crate + process supervision + stdio framing. The alternative — adding `POST /api/v1/observations` to Filigree — is also real engineering (new Flask/FastAPI route, MCP-to-HTTP parity, schema validation, auth plumbing) and competes with `registry_backend` work on the Filigree v0.1 timeline. + +## Decision + +### v0.1 transport — `filigree mcp` subprocess + +Loomweave's `loomweave analyze` and `loomweave serve` both spawn a `filigree mcp` subprocess and use it for observation emission. + +- **Lifecycle**: one subprocess per `loomweave analyze` invocation, spawned at Phase 0 alongside the capability probe. Kept alive for the duration of the run; terminated at the final phase boundary. `loomweave serve` spawns its own subprocess at startup, kept alive for the lifetime of the serve process. +- **Transport**: stdio JSON-RPC 2.0 with Content-Length framing (same framing as plugin transport, ADR-002). Loomweave reuses the framing implementation. +- **MCP tools called**: `create_observation(entity_id, text, source, file_path?, line?)` and `promote_observation(obs_id, issue_template?)` (the latter only from `loomweave serve`'s MCP tool surface). +- **Supervision**: process crashes emit `LMWV-INFRA-FILIGREE-MCP-CRASHED`; crash-loop circuit breaker (>3 crashes in 60s, same threshold as plugins) disables observation emission for the run. Observations emitted after disable are written to `runs//deferred_observations.jsonl` for manual replay. +- **Filigree binary discovery**: `loomweave.yaml:integrations.filigree.binary_path` (default: `filigree` on `PATH`). Not found → `LMWV-INFRA-FILIGREE-BINARY-MISSING`; falls back to `--no-filigree` mode (observations written only to `deferred_observations.jsonl`). + +### v0.2 retirement — `POST /api/v1/observations` + +When Filigree ships the HTTP endpoint: + +- Capability probe (system-design §11 step 3) detects it via `HEAD /api/v1/observations` returning 200. +- Loomweave's observation emit path switches to HTTP. The `filigree mcp` subprocess is no longer spawned for observation emission. +- The deferred-observations replay mechanism remains for `--no-filigree` runs. +- `loomweave serve`'s MCP tools (`emit_observation`, `promote_observation`) continue to exist on Loomweave's side — they are consult-agent-facing, not Filigree-facing. They translate internally to the new HTTP emit path. +- The `LMWV-INFRA-FILIGREE-OBS-VIA-MCP` finding (previously listed in the compat-report fallback table) is removed from v0.2 — MCP-via-subprocess is no longer the expected v0.1 degradation. + +### Retirement trigger + +Filigree publishes `POST /api/v1/observations` with a schema parallel to the MCP `create_observation` tool. Loomweave's capability probe `HEAD /api/v1/observations` returning 200 is the specific switch-over signal. Filigree's CHANGELOG is the authoritative announcement; Loomweave's compat-report signals presence. + +### Consult-tool emit path (unchanged by transport flip) + +Loomweave's own MCP tool `emit_observation(id, text)` (exposed on `loomweave serve`'s MCP surface for consult-mode agents) is transport-independent. In v0.1 it writes to the `filigree mcp` subprocess; in v0.2 it writes via HTTP. The MCP tool's contract to consult agents doesn't change. + +## Alternatives Considered + +### Alternative 1: Filigree HTTP endpoint in v0.1 + +Add `POST /api/v1/observations` to Filigree's v0.1 release; Loomweave emits via HTTP from day one. + +**Pros**: simpler Loomweave-side — one transport instead of two; no subprocess management; no Filigree binary path dependency. Matches the original design's "preferred" framing. + +**Cons**: Filigree-side engineering competes with `registry_backend` schema surgery (ADR-014) on the v0.1 timeline. The HTTP endpoint is not technically difficult but is not free — route handler, schema validation, MCP-to-HTTP tool parity, auth, tests. Q1 scope commitment explicitly deferred this to v0.2 so that v0.1 Filigree-side work could focus on the one surgery (`registry_backend`) that cannot be worked around. + +**Why rejected**: Q1 commitment locks the v0.1 scope. The MCP-spawn workaround is functional and bounded; Loomweave ships on time, Filigree-side work focuses on `registry_backend`, and v0.2 adds the HTTP endpoint cleanly. + +### Alternative 2: TCP MCP transport instead of subprocess spawn + +Loomweave connects to a running `filigree serve` over TCP (Filigree's MCP-over-TCP mode) rather than spawning a subprocess. + +**Pros**: no per-run subprocess spawn cost; works with teams running a central Filigree instance; no `filigree` binary path dependency on each dev host. + +**Cons**: Filigree's MCP-over-TCP is less mature than stdio; adds TLS/auth/retry plumbing to the transport layer that stdio avoids. Loomweave's v0.1 is explicitly local-first (CON-LOCAL-01) — spawning a subprocess matches that posture better than dialing a network service. The scope-commitment memo's "Q1 minimal-core" posture doesn't include managing a persistent Filigree daemon. + +**Why rejected**: TCP transport's surface area doesn't pay off at v0.1 scale. Subprocess spawn is the lower-cost option consistent with the local-first posture. + +### Alternative 3: Coerce observations into the finding pipeline (ADR-004 path) + +Emit observations as a class of finding (`kind: suggestion`, `scan_source: loomweave-obs`) through `POST /api/v1/scan-results`. No separate transport. + +**Pros**: reuses the already-committed finding transport (ADR-004); zero new transport surface. + +**Cons**: observations have a different lifecycle than findings. Filigree observations auto-expire after 14 days (`mcp_tools/observations.py` lifetime policy); findings persist until explicitly closed. Forcing observations through the finding pipeline misrepresents their semantic: operators would see "observations" under the findings UI with no clean signal that they're ephemeral. Filigree's `promote_observation` tool (promotes an observation to an issue) has no finding-path analogue; that workflow breaks. + +**Why rejected**: observations and findings are semantically distinct; the transport shouldn't collapse the distinction. + +### Alternative 4: Defer observations entirely to v0.2 + +v0.1 does not emit observations. Observation generators (LLM-proposed guidance, vocabulary candidates) queue signals locally; v0.2 adds both the HTTP endpoint and the emit path. + +**Pros**: zero transport code in v0.1; no subprocess, no Filigree-binary dependency. + +**Cons**: observations are a load-bearing feedback channel in v0.1. `propose_guidance` (ADR-009) produces observations, not sheets — the guidance-promotion gate *requires* observations to exist for an operator to review. `LMWV-FACT-VOCABULARY-CANDIDATE` and similar signals are observation-shaped. Deferring the transport means deferring the feature. + +**Why rejected**: the v0.1 feature set includes observation *emission*; deferring *transport* would defer the feature by proxy. + +## Consequences + +### Positive + +- v0.1 ships without requiring Filigree-side HTTP endpoint work. Filigree's v0.1 engineering focus is `registry_backend` (ADR-014); observations ride on existing MCP infrastructure. +- The spawn approach works today — `filigree mcp` + `create_observation` already exist. Loomweave's side is the new code; Filigree's side is zero change. +- Retirement path is explicit: v0.2 HTTP endpoint lands, capability probe detects, Loomweave's emit path switches. No ambiguity about when or how. +- Consult-tool contract (`emit_observation` MCP tool on `loomweave serve`) is transport-independent. v0.1 → v0.2 migration is invisible to consult agents. + +### Negative + +- Subprocess management in Loomweave — spawn, stdio I/O, process supervision, crash-loop handling, termination at shutdown. All real code paths that ADR-002's plugin supervision already has; Loomweave reuses that implementation but it's still surface to test. +- `filigree` binary must be on `PATH` (or explicitly configured) for observation emission. Fresh-install developer workstations need the Filigree CLI installed for Loomweave to emit observations; otherwise fallback to `deferred_observations.jsonl`. Mitigation: the SUITE-COMPAT-REPORT finding (system-design §11) names the binary-missing case explicitly. +- One extra subprocess per `loomweave analyze` run (+ one persistent for `loomweave serve`). A few MB RSS each. Tolerable at v0.1 scale; not free. +- `deferred_observations.jsonl` replay is an explicit operator step (`loomweave observations replay`); observations generated during a degraded run are not self-healing. Mitigation: the deferred-observations file is a first-class part of the `runs//` structure, not a hidden error log. + +### Neutral + +- The stale system-design §9 and §11 passages framing HTTP as "preferred" and MCP-spawn as "fallback" are updated alongside this ADR's acceptance — the capability-probe fallback table row for "`/api/v1/observations` absent" becomes a v0.2 feature-presence check, not a degradation indicator. +- The `LMWV-INFRA-FILIGREE-OBS-VIA-MCP` finding listed in the original design is re-scoped: it was framed as a degradation marker; under this ADR it marks the *expected* v0.1 state and retires alongside the transport in v0.2. + +## Related Decisions + +- [ADR-002](./ADR-002-plugin-transport-json-rpc.md) — the subprocess + stdio + Content-Length framing shape is shared with plugin transport. Loomweave reuses ADR-002's implementation for the `filigree mcp` subprocess. +- [ADR-014](./ADR-014-filigree-registry-backend.md) — the scope-commitment counterweight. `registry_backend` is the one Filigree-side surgery v0.1 commits to; deferring the observation HTTP endpoint is what makes that focus possible. +- ADR-009 (pending) — the structured-briefing / propose-guidance decision produces observations via the transport defined here. + +## References + +- [Loomweave v0.1 scope commitments — Q1](../../implementation/v0.1-scope-plans/v0.1-scope-commitments.md) — observation HTTP transport explicitly deferred to v0.2. +- [Loomweave v0.1 system design §9 (Observation transport)](../v0.1/system-design.md) (lines 916-921) — the passage reversed by this ADR; updated in the same commit. +- [Loomweave v0.1 system design §11 (Capability negotiation)](../v0.1/system-design.md) (lines 1122-1141) — observation HTTP presence moves from v0.1 fallback trigger to v0.2 feature-flag detection. +- [Loomweave v0.1 detailed design §9.1 Filigree prerequisites](../v0.1/detailed-design.md) (lines 1333-1351) — `POST /api/v1/observations` moved from "Required for v0.1 ship" to "Nice-to-have (v0.2+)". diff --git a/docs/loomweave/adr/ADR-017-severity-and-dedup.md b/docs/loomweave/adr/ADR-017-severity-and-dedup.md new file mode 100644 index 00000000..2d3bb1d7 --- /dev/null +++ b/docs/loomweave/adr/ADR-017-severity-and-dedup.md @@ -0,0 +1,166 @@ +# ADR-017: Severity Mapping, Rule-ID Round-Trip, and Dedup Policy + +**Status**: Accepted +**Date**: 2026-04-18 +**Deciders**: qacona@gmail.com +**Context**: Loomweave's internal finding vocabulary does not match Filigree's wire vocabulary, and Filigree's dedup key does not match Loomweave's entity-centric mental model. Both need a committed mapping. + +## Summary + +Loomweave's internal severity vocabulary is `{INFO, WARN, ERROR, CRITICAL}` for defects plus `NONE` for facts; Filigree's wire vocabulary is `{critical, high, medium, low, info}`. The mapping is one-to-one in the forward direction; round-trip fidelity is preserved by copying the internal value into `metadata.loomweave.internal_severity` so read-back can recover it. Rule IDs are namespaced by emitter (`LMWV-PY-*`, `LMWV-INFRA-*`, `LMWV-FACT-*`, `LMWV-SEC-*`, plus Wardline's own namespaces) and round-trip byte-for-byte — Filigree does not enforce the namespace, but ADR-022's grammar check does at the Loomweave-plugin boundary. v0.1 dedup relies on Filigree's existing key (`file_id`, `scan_source`, `rule_id`, `coalesce(line_start, -1)`) plus `mark_unseen=true` on ingest; entity moves within a file transition old-position findings to `unseen_in_latest` rather than overwriting them. Server-side per-entity dedup is deferred to v0.2 (NG-21). + +## Context + +Integration reconnaissance (`integration-recon.md` §2.3, §2.4) verified three concrete mismatches between Loomweave's design and Filigree's production intake: + +1. **Severity vocabulary.** Loomweave's internal enum (`INFO`, `WARN`, `ERROR`, `CRITICAL`, `NONE`) is a defect-plus-fact split; Filigree's intake takes `{critical, high, medium, low, info}` lowercase and coerces unknowns to `"info"` with a `warnings[]` response (`db_schema.py:133-148`). A one-way emit loses information; a round-trip loses the `NONE`-is-a-fact distinction entirely. + +2. **Rule-ID namespacing.** Filigree stores `rule_id` as a free-text string (`db_schema.py:154-155`) — no enum enforcement. Loomweave emits several rule-ID prefix conventions (`LMWV-PY-*` for Python-plugin structural findings, `LMWV-INFRA-*` for pipeline failures, `LMWV-FACT-*` for factual findings, `LMWV-SEC-*` for security findings); Wardline emits its own (`PY-WL-*`, `SUP-*`, `SCN-*`, `TOOL-ERROR`). Without a committed mapping and a grammar rule (ADR-022), prefixes drift. The panel's self-sufficiency review (`04-self-sufficiency.md` Issue 7) found exactly this: `requirements.md` REQ-ANALYZE-06 uses `LMWV-PY-PARSE-ERROR` but `detailed-design.md` §10 uses `LMWV-INFRA-PARSE-ERROR` for the same finding. + +3. **Dedup key.** Filigree's server-side dedup key is `(file_id, scan_source, rule_id, coalesce(line_start, -1))` (`db_schema.py:156-157`). Loomweave's mental model is entity-centric — the same rule on the same entity is a single finding, even if the entity moves. The dedup key does not carry `entity_id`, so two `loomweave analyze` runs that see the same entity at different line numbers produce two findings rather than one. + +Each mismatch has an operational consequence: lost severity round-trip (operators can't recover Loomweave's internal gradation from Filigree's storage), inconsistent rule IDs (triage dashboards have to know both spellings of the same error), and dedup blow-up on every entity move. + +## Decision + +### Severity mapping (emit) + +Loomweave's emit path uses this one-to-one mapping: + +| Loomweave internal | Filigree wire | Notes | +|---|---|---| +| `CRITICAL` | `critical` | Direct | +| `ERROR` | `high` | Direct | +| `WARN` | `medium` | Direct | +| `INFO` | `info` | Direct | +| `NONE` (facts) | `info` | With `metadata.loomweave.kind = "fact"` to distinguish from `INFO` defects | + +Every emitted finding also sets `metadata.loomweave.internal_severity` to the pre-mapping value (one of the five Loomweave internal values). This is the round-trip anchor. + +### Severity read-back (consume) + +Loomweave's consume path (reading its own findings back from Filigree): + +1. If `metadata.loomweave.internal_severity` is present → use that value. Lossless round-trip. +2. Else if `severity` is present → reverse-map the wire value to Loomweave's internal vocabulary using the table above (reversed). `critical`→`CRITICAL`, `high`→`ERROR`, `medium`→`WARN`, `low`→`INFO` (Filigree-produced only; Loomweave itself never emits `low`), `info`→`INFO` if `metadata.loomweave.kind != "fact"`, else `NONE`. +3. Else → default `INFO`, emit `LMWV-INFRA-FINDING-SEVERITY-MISSING` on the consume path. + +The read-back path handles Wardline-sourced findings (no `metadata.loomweave.internal_severity`) and Filigree-authored findings (no Loomweave metadata) without conflating them with Loomweave-authored ones. + +### SARIF-level mapping (translator input) + +The `loomweave sarif import` translator (ADR-015) maps SARIF `result.level` into Loomweave internal severity before the emit table above applies: + +| SARIF level | Loomweave internal | Filigree wire (via emit table) | +|---|---|---| +| `error` | `ERROR` | `high` | +| `warning` | `WARN` | `medium` | +| `note` | `INFO` | `info` | +| `none` | `INFO` | `info` (with `LMWV-INFRA-SARIF-LEVEL-NONE` translator warning — SARIF `none` is rare and usually indicates a tool-side bug) | + +### Rule-ID namespacing + +Loomweave rule IDs are namespaced by emitter and purpose: + +- `LMWV-PY-*` — Python-plugin structural and rule findings (`LMWV-PY-STRUCTURE-001`, `LMWV-PY-UNRESOLVED-IMPORT`, etc.). Emittable only by the Python plugin. **Includes parse errors**: `LMWV-PY-PARSE-ERROR` is the canonical ID for a Python source file the plugin cannot parse. +- `LMWV-FACT-*` — factual observations, emittable by any plugin or the core (`LMWV-FACT-TODO`, `LMWV-FACT-ENTRYPOINT`, `LMWV-FACT-CONDITIONAL-IMPORT`). +- `LMWV-INFRA-*` — pipeline and infrastructure failures; core-only. Reserved namespace per ADR-022. +- `LMWV-SEC-*` — security-domain findings; emitted by the core's security-gate subsystem (`LMWV-SEC-SECRET-DETECTED`, `LMWV-SEC-UNREDACTED-SECRETS-ALLOWED`). + +Wardline namespaces (passed through verbatim): `PY-WL-*`, `SUP-*`, `SCN-*`, `TOOL-ERROR`. Future-tool namespaces reserved: `COV-*` (coverage), `SEC-*` (standalone security scanner). ADR-022 enforces the grammar at the Loomweave plugin boundary; Filigree performs no enforcement. + +**Issue 7 fix**: `LMWV-PY-PARSE-ERROR` is correct; `detailed-design.md` §10's use of `LMWV-INFRA-PARSE-ERROR` is the bug. A parse error is Python-plugin-emitted (the plugin tries to parse and fails); it is not a pipeline failure. The namespace rule is "emitter + purpose." This ADR's authoring is the occasion to fix that drift; the detailed-design §10 error surfaces table updates alongside ADR-017's acceptance. + +### Rule-ID round-trip + +Filigree stores `rule_id` byte-for-byte. Loomweave's consult tools re-namespace on display: findings with `scan_source="wardline"` surface as "Wardline/PY-WL-001-GOVERNED-DEFAULT"; findings with `scan_source="loomweave"` surface unprefixed. This is a rendering convention, not a storage convention. + +### Dedup policy + +Filigree's server-side dedup key is `(file_id, scan_source, rule_id, coalesce(line_start, -1))`. Loomweave's emit path: + +- **Every batch**: POST with `mark_unseen=true`. On a rule + file + line tuple that has not appeared in this run but appeared in a prior run, Filigree transitions the prior finding to `unseen_in_latest`. New findings at different line numbers insert as new rows. +- **Resume path** (`loomweave analyze --resume`): POST with `mark_unseen=false`. Re-using the same `run_id` on resume must not trigger "no longer seen" transitions for findings the current run has not yet re-visited. +- **Prune policy**: `loomweave analyze --prune-unseen` removes `unseen_in_latest` findings older than 30 days (configurable via `loomweave.yaml:findings.prune_unseen_days`). Operators running this in CI keep the findings store bounded without losing recent history. + +**Entity move within a file**: producing `unseen_in_latest` at the old line and a fresh finding at the new line is the v0.1 behaviour. It is a known coarseness — operators see the same finding twice briefly (old position marked `unseen_in_latest`, new position active) until `--prune-unseen` runs. The v0.2 improvement (NG-21) is server-side per-entity dedup: an optional `entity_id` extension field on Filigree's dedup key, so `(file_id, scan_source, rule_id, entity_id)` tracks the logical finding across position changes. + +### Rule-ID synthesis (explicit non-option) + +Loomweave does **not** synthesise rule IDs with entity IDs (e.g., `LMWV-PY-STRUCTURE-001__python:class:foo`). That would defeat Filigree's cross-run triage — every rule-on-every-entity becomes unique, so "acknowledge all STRUCTURE-001 findings" no longer works as a triage operation. + +## Alternatives Considered + +### Alternative 1: Rule-ID synthesis with entity IDs + +Concatenate entity IDs into rule IDs to sidestep Filigree's dedup key mismatch entirely. + +**Pros**: eliminates the entity-move dedup problem at the ID level; no `mark_unseen=true` workaround needed. + +**Cons**: destroys the triage utility of rule IDs. Filigree's UI groups by rule ID to let operators acknowledge a whole class of findings at once; synthesised IDs make every finding unique, so the group-acknowledge operation stops working. Every Filigree query that filters by `rule_id` breaks. + +**Why rejected**: the cost (losing triage) is worse than the problem (transient `unseen_in_latest` findings that prune after 30 days). + +### Alternative 2: Client-side dedup in Loomweave + +Loomweave reads back its own findings before POSTing, deduplicates client-side, and only emits deltas. + +**Pros**: no Filigree-side changes; Loomweave controls the dedup semantics fully. + +**Cons**: introduces read-back coupling on the emit path — Loomweave now depends on Filigree being reachable *and* complete before it can emit. A new `loomweave analyze` run cannot start emitting until the prior run's findings are all readable. Shifts cross-run identity tracking to Loomweave's side, which has no database of Filigree-stored findings; every dedup operation requires a HTTP read. Net worse than the current workaround. + +**Why rejected**: creates a synchronous dependency on Filigree state during emit. + +### Alternative 3: Push Filigree-side per-entity dedup in v0.1 + +Add `entity_id` as an optional fifth column in Filigree's dedup key in v0.1. + +**Pros**: cleanest outcome — dedup matches the entity-centric mental model. No `unseen_in_latest` noise on entity moves. + +**Cons**: Filigree-side schema migration. Existing `file_records` + findings need the migration, existing dedup-query paths need updating, and the `(file_id, scan_source, rule_id, coalesce(line_start, -1))` index must either be supplemented or replaced. Within-scope (same author owns Filigree) but real v0.1 engineering time on top of the registry-backend work (ADR-014). + +**Why deferred**: the v0.1 `mark_unseen=true` workaround is bounded (`--prune-unseen` controls unbounded growth) and well-understood. NG-21 names the v0.2 improvement; deferral is a scope-commitment decision, not a design compromise. + +### Alternative 4: Drop Loomweave's internal severity; use Filigree's wire vocabulary directly + +Loomweave's internal records store `{critical, high, medium, low, info}` directly. No mapping needed. + +**Pros**: zero mapping surface; round-trip is trivial (no metadata dance). + +**Cons**: loses the `CRITICAL`/`ERROR` distinction in Loomweave's own queries. `CRITICAL` means "production-impacting" in Loomweave's register; `ERROR` means "design-smell or bug." Filigree's `critical`/`high` mapping preserves a *wire* distinction but the values are operator-facing triage labels, not Loomweave's internal semantics. Collapsing them means Loomweave's internal query "what critical findings exist?" now returns triage-labelled `critical` findings, not Loomweave-semantics `CRITICAL` findings. + +**Why rejected**: the internal vocabulary is load-bearing for Loomweave's own reasoning; the mapping surface is small. + +## Consequences + +### Positive + +- Round-trip fidelity for Loomweave-emitted findings. An operator who re-reads Loomweave's findings from Filigree recovers the internal severity exactly. +- Rule-ID namespacing is now enforceable at the Loomweave boundary (ADR-022 grammar check) and round-trips cleanly to Filigree (byte-for-byte). The `LMWV-INFRA-PARSE-ERROR` vs `LMWV-PY-PARSE-ERROR` drift noted in the panel's self-sufficiency review has a committed resolution. +- Dedup policy is explicit and bounded. `mark_unseen=true` + 30-day prune gives operators a predictable findings-store size. The v0.2 NG-21 upgrade path is named. +- SARIF translator severity is locked; operators running `loomweave sarif import wardline.sarif.baseline.json` see Wardline's `error`/`warning`/`note` land at the correct Filigree wire values. + +### Negative + +- Entity moves within a file produce transient duplicates (old-position `unseen_in_latest`, new-position active) until pruning. Operators reading Filigree UI during an active `loomweave analyze` see both. Mitigation: the two-value window is expected and documented; `--prune-unseen` run in CI keeps the window bounded. +- The severity mapping is asymmetric by necessity (`NONE`/`INFO` both map to `info` on the wire) — recovering `NONE` on read-back relies on `metadata.loomweave.kind` presence. If another emitter writes `info` without that metadata field, Loomweave's read-back treats it as `INFO`. This is the correct default (unknown info-severity findings are defects by default) but operators writing custom emitters into Filigree should know the convention. +- Rule-ID namespace enforcement is at the Loomweave boundary, not at Filigree. A Wardline bug that emits `LMWV-PY-*` IDs (shouldn't happen, but could under maintenance error) lands in Filigree without complaint. Mitigation: ADR-022 + cross-tool fixture tests (`detailed-design.md` §9.3) catch this class of error at release time. + +### Neutral + +- `scan_run_id` lifecycle (create at Phase 0, complete at last batch, don't complete on resume) is already specified in detailed-design §7. This ADR doesn't re-decide it but cites it; the `mark_unseen=true` policy sits inside that lifecycle. +- SARIF property-bag preservation (ADR-019) is complementary: the severity mapping here and the property-bag mapping there together define the full SARIF → Filigree translation for the ADR-015 translator. + +## Related Decisions + +- [ADR-004](./ADR-004-finding-exchange-format.md) — Filigree-native intake is the target format; this ADR fills in the severity and rule-ID mapping that the format needs. +- [ADR-015](./ADR-015-wardline-filigree-emission.md) — the SARIF translator applies the severity mapping defined here. Rule-ID round-trip for Wardline's `PY-WL-*` / `SUP-*` / `SCN-*` namespaces happens through this ADR's byte-for-byte policy. +- ADR-019 (pending) — SARIF property-bag preservation complements this ADR. Where ADR-019 governs `metadata._properties.*` for extension keys, this ADR governs severity + rule-ID values. +- [ADR-022](./ADR-022-core-plugin-ontology.md) — grammar enforcement on rule-ID prefixes is implemented at manifest acceptance and RPC time; this ADR's namespace choices populate the allowed set. + +## References + +- [Loomweave v0.1 detailed design §7](../v0.1/detailed-design.md) (lines 1163-1200) — the canonical mapping tables; this ADR formalises what's already there and fixes Issue 7. +- [Loomweave v0.1 integration reconnaissance §2.3, §2.4](../../implementation/v0.1-reviews/pre-restructure/integration-recon.md) — empirical evidence for severity vocabulary and dedup key on Filigree's side (`db_schema.py` line numbers). +- [Panel self-sufficiency review — Issue 7](../../implementation/v0.1-reviews/panel-2026-04-17/04-self-sufficiency.md) (lines 132-134) — rule-ID namespace inconsistency this ADR resolves. +- [Loomweave v0.1 requirements — REQ-FINDING-02, NG-21](../v0.1/requirements.md) — rule-ID namespace rule; v0.2 server-side per-entity dedup deferral. diff --git a/docs/loomweave/adr/ADR-018-identity-reconciliation.md b/docs/loomweave/adr/ADR-018-identity-reconciliation.md new file mode 100644 index 00000000..5745b768 --- /dev/null +++ b/docs/loomweave/adr/ADR-018-identity-reconciliation.md @@ -0,0 +1,204 @@ +# ADR-018: Identity Reconciliation — Loomweave Translates; Wardline Owns Its Qualnames + +**Status**: Accepted (Revision 3, 2026-06-05 — direct REGISTRY import asterisk retired; Python plugin reads Wardline's NG-25 descriptor without importing Wardline.) +**Date**: 2026-04-18 +**Deciders**: qacona@gmail.com +**Context**: three independent identity schemes exist across Loomweave, Wardline, and Wardline's exception register; one-way translation is the federation-compatible answer + +## Summary + +Loomweave maintains the translation layer between its own `EntityId` scheme (ADR-003) and Wardline's qualnames / exception-register locations. Wardline is authoritative for its own qualnames and does **not** adopt Loomweave's ID format. The Python plugin imports `wardline.core.registry.REGISTRY` directly at startup with a `REGISTRY_VERSION` pin; skew is handled with three graduated responses (exact / additive / major-bump) rather than hard failure. The HTTP read API exposes an `entities/resolve?scheme=…&value=…` oracle so siblings ask in their own scheme instead of embedding Loomweave's ID format. The direct-import pattern is an **initialization coupling** (`weft.md` §5 asterisk 2) scoped to the Wardline-aware plugin specifically; the Loomweave core and any non-Wardline-aware plugin do not depend on Wardline being importable. + +## Context + +Three identity schemes coexist and none are byte-equal for the same underlying symbol (`detailed-design.md:557-561`): + +| Scheme | Example | Owner | Format | +|---|---|---|---| +| Loomweave `EntityId` | `python:class:auth.tokens::TokenManager` | Loomweave | `{plugin_id}:{kind}:{canonical_qualified_name}` | +| Wardline `module` + `qualified_name` | `src/auth/tokens.py` + `TokenManager.verify` | Wardline `FingerprintEntry` | Source file path plus Python's bare `__qualname__`; not byte-equal to Loomweave's dotted-module-prefixed `qualified_name` | +| Wardline exception-register `location` | `src/wardline/scanner/engine.py::ScanEngine._scan_file` | `wardline.exceptions.json` | `{file_path}::{qualname}` | + +Any cross-tool query — "which Filigree findings attach to this Loomweave entity?", "which Wardline exception covers this qualname?" — has to bridge at least two of these. The ADR-003 decision to use symbolic canonical names produces Loomweave's scheme but leaves the translation question open. + +The Wardline integration adds a second dimension: Loomweave's Python plugin depends on Wardline's decorator vocabulary (`REGISTRY`) to detect annotations correctly. Two approaches existed before this ADR — heuristic name-matching against a hardcoded list, or a direct Python import of the vocabulary. The design picked direct import (`system-design.md:949`) with `REGISTRY_VERSION` pinning, but the *why* (and the retirement conditions) weren't formalised. + +The Weft doctrine is deliberate about identity (`weft.md` §6): Weft is **not** an identity-reconciliation service. "When cross-scheme translation is needed — e.g. Wardline qualname → Loomweave entity ID — the product that *cares* does the translation, because that product is the one whose authority needs it." Loomweave cares because Loomweave owns the catalog that makes Wardline's qualnames meaningful to anything other than Wardline itself. + +The 2026-04-17 panel's doctrine synthesis (`11-doctrine-panel-synthesis.md`) flagged the direct-import pattern as an explicit federation asterisk needing a retirement condition, not a quiet dependency. `weft.md` §5 now carries that asterisk: initialization coupling scoped to the Wardline-aware plugin specifically; non-Wardline-aware plugins and the Loomweave core remain Wardline-independent. + +## Decision + +### Translation direction and authority + +- **Inbound translation only.** Loomweave translates Wardline qualnames, exception-register locations, and SARIF logical locations into `EntityId`s. Loomweave does not push its ID scheme outbound — Wardline's emissions remain in Wardline's format, and Loomweave's translation layer maps them at ingest. +- **Wardline is authoritative for its own qualnames.** `FingerprintEntry.qualified_name` and its paired `module` path are Wardline's contract; Loomweave reads them and maps them. A future Wardline refactor that changes either field's format is a Wardline-side decision Loomweave adapts to; the inverse is not true. +- **Reverse mapping is recorded, not pushed.** `WardlineMeta.wardline_qualname` on each Loomweave entity property is the reverse lookup cache. It lives on Loomweave's entity, not on Wardline's side, and is re-computed on every `loomweave analyze`. + +### 2026-05-18 amendment: asymmetric qualname storage is canonical + +Sprint 1 verification proved that the two products encode the same Python symbol +with different field boundaries: + +- Loomweave's Python plugin emits `qualified_name = + "{dotted_module}.{python.__qualname__}"` on each entity, e.g. + `auth.tokens.TokenManager.verify`. +- Wardline's `FingerprintEntry` stores `module` as the source file path and + `qualified_name` as Python's bare `__qualname__`, e.g. + `src/auth/tokens.py` plus `TokenManager.verify`. + +Direct string equality between Loomweave's `qualified_name` and Wardline's +`qualified_name` is therefore forbidden for joins. Wardline-to-Loomweave +translation composes Loomweave's dotted module name from Wardline's `module` +using the same rules as the Python plugin's `module_dotted_name()` (`src/` +prefix stripped, `.py` removed, and `__init__.py` collapsed), then appends +Wardline's bare `qualified_name`. Loomweave-to-Wardline translation must prefer +the recorded `WardlineMeta` / translator output; naively splitting on dots is +unsafe because dotted class chains and `` markers are part of Python's +`__qualname__`, not the module path. + +### 2026-05-29 amendment: Wardline emits the dotted qualname pre-composed + +The 2026-05-29 Wardline ↔ Weft integration brief (§4.A) resolves the asymmetric-storage clash **in Loomweave's favor at the emission boundary**. Under the generic Wardline rebuild's native Filigree emitter ([ADR-015](./ADR-015-wardline-filigree-emission.md) Revision 2), each emitted finding carries `metadata.wardline.qualname` as the **combined dotted `module.qualified_name`** (e.g. `auth.tokens.TokenManager.issue`) — Loomweave's L7 form, not Wardline's `(file-path module, bare qualname)` storage pair. + +Two consequences: + +1. **A clarified entry point.** This is a fifth reconciliation surface alongside the four below: Loomweave reads `metadata.wardline.qualname` off a **Filigree finding** (via the federation read path / `issues_for` enrichment), not off a Wardline state file. The composition rule reverses — the 2026-05-18 amendment's "Loomweave composes the dotted module name from Wardline's `module`" becomes "**Wardline emits it pre-composed; Loomweave matches by direct qualname equality**" (`find_entity` by qualname). The state-file entry points (1–3) and their composition rule remain unchanged for any path that still reads Wardline's on-disk artifacts (e.g. historical SARIF baselines). + +2. **The normalization contract is now Wardline's, at the emission boundary.** Because Wardline now performs the dotting Loomweave used to do, byte-equality depends on Wardline replicating `module_dotted_name()` **exactly**: `src/` prefix stripped, `.py` removed, `__init__.py` collapsed (and the analogous handling for namespace-package / non-`src` layouts), while preserving `` markers and dotted class chains in `__qualname__` verbatim — those are part of the qualname, not the module path, and must not be re-dotted or stripped. If Wardline's composition diverges, reconciliation degrades silently to `resolution_confidence: heuristic | none` on exactly the nested-class and closure entities where it is least recoverable. Loomweave exposes the canonical rules via `module_dotted_name()` and the `GET /api/v1/entities/resolve?scheme=wardline_qualname` oracle; a divergence shows up there as a non-`exact` resolution. This contract is the open ask-back to Wardline recorded in ADR-015 Revision 2; it is an enrichment-quality concern and does not gate the (Wardline, Filigree) transport path. + +### Translation entry points (v0.1) + +1. **`wardline.fingerprint.json`**: for each `FingerprintEntry`, compute `(module, qualified_name) → EntityId` using Wardline's `module_file_map` (from `ScanContext`). Write `WardlineMeta.wardline_qualname = qualified_name` on the resolved Loomweave entity. +2. **`wardline.exceptions.json`**: parse `location` as `file_path::qualname` (split on the first `::`); same mapping rule. Unresolvable entries emit `LMWV-INFRA-WARDLINE-EXCEPTION-UNRESOLVED` and persist as dangling records with `entity_id: null`. +3. **`wardline.sarif.baseline.json`**: use `location.logicalLocations[].fullyQualifiedName` when present, or `partialFingerprints` as a fallback. Unresolvable SARIF results carry `metadata.loomweave.unresolved: true` through translation. +4. **`GET /api/v1/entities/resolve?scheme=&value=`**: HTTP read-API oracle. Schemes accepted: `wardline_qualname`, `wardline_exception_location`, `file_path`, `sarif_logical_location`. Response carries `resolution_confidence` (`exact | heuristic | none`) plus candidates for non-exact matches. 404-like misses return 200 with `resolution_confidence: "none"` to distinguish "Loomweave doesn't know" from "Loomweave is down." + +### Direct REGISTRY import with REGISTRY_VERSION pin + +The Python plugin imports `wardline.core.registry.REGISTRY` at startup. The `wardline` package is a dependency declared in the plugin's pipx venv. Skew behaviour (REQ-INTEG-WARDLINE-01, NFR-COMPAT-02): + +| Installed `REGISTRY_VERSION` vs pinned | Behaviour | +|---|---| +| Exact match | Normal operation | +| Additive-newer (same major, same or higher minor) | Proceed with warning; decorators in the installed REGISTRY beyond the pin detected with `confidence_basis: loomweave_augmentation`. Emit `LMWV-INFRA-WARDLINE-REGISTRY-ADDITIVE-SKEW` | +| Major-bump or older | Fall back to hardcoded registry mirror (`wardline_registry_v.py`). Findings carry `confidence_basis: mirror_only`. Emit `LMWV-INFRA-WARDLINE-REGISTRY-MIRRORED` | +| Wardline package not installable in plugin venv | Mirror mode from startup (same `MIRRORED` emission); `--no-wardline` declares the intent explicitly | + +Pin policy: `REGISTRY_VERSION` is updated at Loomweave release time alongside the hardcoded mirror. A Wardline minor bump between Loomweave releases degrades to additive-skew (safe); a major bump degrades to mirror mode (safe but lossy). + +### Why plugin-level, not core-level + +The REGISTRY import is a property of the Wardline-aware plugin specifically, not of the Loomweave core (`weft.md` §5 asterisk 2). The Rust core has no import path to Wardline; it's the Python plugin's startup that walks `sys.path` to load `wardline.core.registry`. The asterisk is named in `weft.md` §5 with an explicit retirement condition: when Wardline publishes a YAML/JSON descriptor export of its REGISTRY (NG-25, v0.2), non-Python plugins can consume it without a Python import, and the initialization coupling retires to a plain file-descriptor read. + +This preserves the federation test: removing Wardline breaks Wardline-derived annotation detection but does not prevent the Loomweave core from starting, does not prevent non-Wardline-aware plugins from running, and does not alter the meaning of Loomweave's own catalog entries. + +### Revision 3 (2026-06-05): direct-import asterisk retired + +Wardline now publishes the NG-25 trust-vocabulary descriptor as `vocabulary.yaml` +and through `wardline vocab`. Loomweave's Python plugin consumes that descriptor +instead of importing `wardline.core.registry.REGISTRY`. Resolution is +project-local `.wardline/vocabulary.yaml` first, then the installed Wardline +distribution data file `wardline/core/vocabulary.yaml`; both paths are plain +file reads and neither imports `wardline`, `wardline.core`, or +`wardline.core.registry`. + +The plugin records source-observed decorator facts on Loomweave entities as +Wardline metadata and `wardline:*` tags. Wardline remains authoritative for the +vocabulary and policy semantics; Loomweave stores only what it parsed from source +against the descriptor. Missing or invalid descriptors continue to degrade +honestly: normal structural extraction proceeds, `capabilities.wardline` reports +the degraded state, and no Wardline entity metadata is emitted. + +This closes the `weft.md` §5 initialization-coupling asterisk on the Loomweave +side. The identity translation rules and `REGISTRY_VERSION`/descriptor-version +skew posture remain bilateral compatibility concerns; the load-bearing change is +that plugin startup no longer requires Wardline to be importable. + +## Alternatives Considered + +### Alternative 1: Wardline adopts Loomweave's entity-ID scheme + +Wardline changes its `FingerprintEntry.qualified_name` to carry Loomweave's `{plugin_id}:{kind}:{canonical_qualified_name}` format directly. + +**Pros**: zero translation layer; one identity scheme across the suite; cross-tool queries are string-equal comparisons. + +**Cons**: Wardline can no longer stand alone — its internal data carries a format whose meaning depends on Loomweave's ID-generation conventions. `weft.md` §6 prohibition "no identity reconciliation service" is violated: the scheme *is* a reconciliation service, embedded in one product's data. If Loomweave changes its kind vocabulary or qualname normalisation, Wardline's historical data silently reinterprets. Wardline must re-analyze every commit Loomweave has ever indexed to keep its IDs coherent. + +**Why rejected**: it imports Loomweave's authority into Wardline. The solo-use failure mode ("Filigree + Wardline without Loomweave") breaks — Wardline cannot emit stable IDs without knowing Loomweave's ID conventions. + +### Alternative 2: Wardline qualnames become the suite-wide canonical identity + +Loomweave adopts Wardline's qualname format as its entity ID. + +**Pros**: Wardline already computes them deterministically; the existing format is proven in production. + +**Cons**: Python-specific (`TokenManager.verify` has no Java or Rust analogue that Wardline could produce); entity kinds Wardline doesn't scan — `file`, `subsystem`, `guidance` — have no qualname at all. Loomweave's multi-language roadmap breaks the moment a Java plugin emits an entity with no qualname-shaped identity. + +**Why rejected**: specialisation to one language + missing coverage for core-owned kinds. Same `weft.md` §6 violation inverted — now Wardline's authority is imported into Loomweave. + +### Alternative 3: Weft identity reconciliation service + +A neutral Weft-level service maintains a translation table that every product queries. + +**Pros**: symmetric; no product "owns" identity. + +**Cons**: violates `weft.md` §6 ("Weft is not an identity reconciliation service") categorically, and §5 pipeline-coupling (the (Wardline, Filigree) pair composes only through a Weft mediator). Introduces the stealth-monolith pattern Weft exists to prevent. + +**Why rejected**: categorical doctrine violation. The test for adding Weft-level services ("if the proposal introduces something that would need to be running or present for the suite to work, it violates federation") fails immediately. + +### Alternative 4: Heuristic-only reconciliation (no REGISTRY import; string matching) + +Loomweave's plugin does not import Wardline's REGISTRY. Decorator detection uses a hardcoded list of known Wardline decorator names, updated in Loomweave releases. + +**Pros**: no initialization coupling. Loomweave's plugin starts without Wardline present. + +**Cons**: every Wardline decorator addition creates a drift window — the decorator lands in Wardline, but Loomweave's plugin doesn't learn about it until the next Loomweave release. For a v0.1 suite where Loomweave and Wardline ship on independent cadences (and are the same author), the drift window is real and silent. The whole point of the REGISTRY is to be the shared vocabulary; refusing to import it re-creates the drift the REGISTRY was supposed to eliminate. + +**Why rejected**: trades a named, retirement-conditioned initialization coupling (this ADR) for silent vocabulary drift. The named coupling is strictly preferable. + +### Alternative 5: File-descriptor read of a YAML/JSON REGISTRY descriptor + +Wardline exports its REGISTRY as a declarative descriptor file (`wardline_registry.yaml` or similar). Loomweave's plugin reads the descriptor at startup instead of importing Python. + +**Pros**: language-neutral — works for non-Python plugins. No Python import dependency. Cleaner federation shape (plain file-descriptor consumption). + +**Cons**: Wardline has no such descriptor export today. Adding it is within-scope but is Wardline-side work the v0.1 Loomweave plan does not commit to. NG-25 already names it as a v0.2 Wardline prerequisite ("YAML/JSON descriptor of REGISTRY enables non-Python plugins"), and the asterisk's retirement condition in `weft.md` §5 is exactly this. + +**Why rejected for v0.1**: premature. v0.1 ships Python-only, so the Python-import path is sufficient and cheaper. The descriptor is the documented v0.2 path; the asterisk retires when it lands. + +## Consequences + +### Positive + +- Translation is load-bearing for exactly one product (Loomweave) in exactly one direction (inbound). Wardline and Filigree don't know translation happens. +- `REGISTRY_VERSION` pin with graduated skew response (exact / additive / mirror) means install skew degrades gracefully rather than hard-failing. An operator with a half-updated dev environment still gets useful output. +- The `entities/resolve` HTTP oracle exposes translation to siblings without requiring them to embed Loomweave's ID format. Wardline's v0.2 HTTP client uses it; Filigree MCP calls already use its spiritual equivalent. +- Reverse mapping (`WardlineMeta.wardline_qualname`) enables "what Wardline qualname corresponds to this entity?" without re-running the reconciliation, and it lives on Loomweave's data, not Wardline's. + +### Negative + +- Initialization coupling is named but not eliminated. Loomweave's Python plugin cannot start without `wardline` installed in its venv; the coupling retires when NG-25 (YAML/JSON REGISTRY descriptor) lands in v0.2. The asterisk lives in `weft.md` §5 with that condition. An operator who installs Loomweave without Wardline gets a plugin that runs in mirror mode from startup, which is noisy but functional; a misinstalled venv (wrong Python, missing dep) produces a clear startup failure with `LMWV-INFRA-WARDLINE-REGISTRY-MIRRORED` as the first signal. +- Heuristic reconciliation (file moves, symbol renames not tracked by EntityAlias — ADR-003 names the v0.1 limitation) produces `resolution_confidence: heuristic` or `none` results. Operators see `LMWV-INFRA-WARDLINE-EXCEPTION-UNRESOLVED` and run `loomweave analyze --repair-aliases` manually. +- Three-scheme translation is quadratic in failure modes — the `resolve` oracle has to know every scheme, and every scheme has its own unresolvable case. Tractable at v0.1 scale (four schemes) but bears watching as siblings grow. + +### Neutral + +- The translation layer lives entirely in Loomweave — plugin-side for REGISTRY import and qualname mapping, core-side for the `entities/resolve` oracle. No Wardline or Filigree changes are required by this ADR. +- The v0.2 YAML/JSON REGISTRY descriptor simplifies this ADR rather than replacing it: the import path becomes a file read, but the translation layer and the `REGISTRY_VERSION` pin semantics are unchanged. + +## Related Decisions + +- [ADR-002](./ADR-002-plugin-transport-json-rpc.md) — the subprocess transport is where the plugin's startup REGISTRY import happens. ADR-021's path jail does not apply (the import walks `sys.path`, not plugin-emitted paths). +- [ADR-003](./ADR-003-entity-id-scheme.md) — Loomweave's `EntityId` format is the target of every translation entry point here. The v0.1 limitation ADR-003 names (symbol renames without file move) is the specific case this ADR's heuristic fallback covers. +- [ADR-015](./ADR-015-wardline-filigree-emission.md) — the SARIF translator is translation entry point 3. ADR-015 decides the translator's Wardline role (v0.1 bridge, retiring in v0.2); this ADR's translation rules apply for as long as the translator exists for any SARIF source. +- [ADR-021](./ADR-021-plugin-authority-hybrid.md) — the plugin's REGISTRY import is an import, not a file read, so ADR-021's path jail does not apply. The `RLIMIT_AS` cap does apply; operators with unusually large Wardline REGISTRY installs see it first. +- [ADR-022](./ADR-022-core-plugin-ontology.md) — identity translation is plugin-side; the core does not embed Wardline's qualname format. A future non-Wardline-aware plugin follows ADR-022's rules without any Wardline coupling. + +## References + +- [Loomweave v0.1 requirements — REQ-INTEG-WARDLINE-01, NFR-COMPAT-02](../v0.1/requirements.md) (lines 599, 889) — REGISTRY pin and skew behaviour. +- [Loomweave v0.1 system design §2 (Direct REGISTRY import), §9 (state-file ingest), §9 (Entity resolve oracle)](../v0.1/system-design.md) — import pattern, ingest paths, HTTP oracle. +- [Loomweave v0.1 detailed design §2 (Identity reconciliation across the suite)](../v0.1/detailed-design.md) (lines 553-571) — three-scheme translation table; ingest-path rules. +- [Weft doctrine §5 (v0.1 asterisks), §6 (What Weft is NOT)](../../suite/weft.md) — initialization-coupling asterisk; "no identity reconciliation service" categorical. +- [Panel doctrine synthesis](../../implementation/v0.1-reviews/panel-2026-04-17/11-doctrine-panel-synthesis.md) — the asterisks framing originated here. diff --git a/docs/clarion/adr/ADR-021-plugin-authority-hybrid.md b/docs/loomweave/adr/ADR-021-plugin-authority-hybrid.md similarity index 77% rename from docs/clarion/adr/ADR-021-plugin-authority-hybrid.md rename to docs/loomweave/adr/ADR-021-plugin-authority-hybrid.md index dbf5ad62..f2be850e 100644 --- a/docs/clarion/adr/ADR-021-plugin-authority-hybrid.md +++ b/docs/loomweave/adr/ADR-021-plugin-authority-hybrid.md @@ -7,7 +7,7 @@ ## Summary -Plugins run as subprocesses at the user's UID (ADR-002). The v0.1 authority model is **hybrid**: the plugin declares its expected runtime envelope in the manifest (capabilities, RSS expectation, entity-emission shape), and the core unconditionally enforces a small set of minimum-safe controls at the transport and pipeline layer — a path jail refusing plugin-returned paths outside the project root, a Content-Length ceiling on every JSON-RPC frame, a per-run entity-count cap, and a per-plugin RSS limit applied via `prlimit`/`setrlimit` at spawn. Violations kill the plugin, emit `CLA-INFRA-PLUGIN-VIOLATION` with a specific subcode, and participate in the crash-loop circuit breaker. This is not a sandbox (no seccomp/landlock); it is a set of non-negotiable guardrails that turn "trusted-source-only" from a doctrine into an enforced floor. +Plugins run as subprocesses at the user's UID (ADR-002). The v0.1 authority model is **hybrid**: the plugin declares its expected runtime envelope in the manifest (capabilities, RSS expectation, entity-emission shape), and the core unconditionally enforces a small set of minimum-safe controls at the transport and pipeline layer — a path jail refusing plugin-returned paths outside the project root, a Content-Length ceiling on every JSON-RPC frame, a per-run entity-count cap, and a per-plugin RSS limit applied via `prlimit`/`setrlimit` at spawn. Violations kill the plugin, emit `LMWV-INFRA-PLUGIN-VIOLATION` with a specific subcode, and participate in the crash-loop circuit breaker. This is not a sandbox (no seccomp/landlock); it is a set of non-negotiable guardrails that turn "trusted-source-only" from a doctrine into an enforced floor. ## Context @@ -43,25 +43,25 @@ capabilities: # enumerate which paths (not supported in v0.1) ``` -A plugin declaring `reads_outside_project_root: true` in v0.1 is refused at `initialize` with `CLA-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY`; v0.1 has no mechanism to allow it. +A plugin declaring `reads_outside_project_root: true` in v0.1 is refused at `initialize` with `LMWV-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY`; v0.1 has no mechanism to allow it. ### Layer 2 — Core-enforced minimums (non-negotiable, applied to every plugin) These four controls are applied to every plugin unconditionally, regardless of manifest content. They are the enforcement floor this ADR commits to. -**2a — Path jail.** Every path the plugin returns (from `file_list`, every `file_analyzed` notification's `source.file_path`, every evidence anchor) is normalised via `std::fs::canonicalize` (follows symlinks) and checked against `project_root`. A path resolving outside `project_root` is dropped (the entity/edge/finding it anchors is refused), and `CLA-INFRA-PLUGIN-PATH-ESCAPE` is emitted with `metadata.clarion.offending_path` set to the rejected input. The plugin is **not** killed on first violation — the core treats path escape as a correctness bug more often than a live attack — but the crash-loop circuit breaker counts repeated escapes at a lower threshold (>10 escapes in 60s → plugin killed, `CLA-INFRA-PLUGIN-DISABLED-PATH-ESCAPE`). +**2a — Path jail.** Every path the plugin returns (from `file_list`, every `file_analyzed` notification's `source.file_path`, every evidence anchor) is normalised via `std::fs::canonicalize` (follows symlinks) and checked against `project_root`. A path resolving outside `project_root` is dropped (the entity/edge/finding it anchors is refused), and `LMWV-INFRA-PLUGIN-PATH-ESCAPE` is emitted with `metadata.loomweave.offending_path` set to the rejected input. The plugin is **not** killed on first violation — the core treats path escape as a correctness bug more often than a live attack — but the crash-loop circuit breaker counts repeated escapes at a lower threshold (>10 escapes in 60s → plugin killed, `LMWV-INFRA-PLUGIN-DISABLED-PATH-ESCAPE`). -**2b — Content-Length ceiling.** Every inbound JSON-RPC frame from the plugin has a Content-Length header (ADR-002). A frame exceeding the ceiling is a framing error. Default ceiling: **8 MiB** per frame (configurable via `clarion.yaml:plugin_limits.max_frame_bytes`, floor 1 MiB). The framing parser refuses the frame before deserialising; the plugin is killed with SIGTERM → SIGKILL if non-responsive; `CLA-INFRA-PLUGIN-FRAME-OVERSIZE` is emitted with `metadata.clarion.observed_bytes` and `metadata.clarion.ceiling_bytes`. Crash-loop counter increments. +**2b — Content-Length ceiling.** Every inbound JSON-RPC frame from the plugin has a Content-Length header (ADR-002). A frame exceeding the ceiling is a framing error. Default ceiling: **8 MiB** per frame (configurable via `loomweave.yaml:plugin_limits.max_frame_bytes`, floor 1 MiB). The framing parser refuses the frame before deserialising; the plugin is killed with SIGTERM → SIGKILL if non-responsive; `LMWV-INFRA-PLUGIN-FRAME-OVERSIZE` is emitted with `metadata.loomweave.observed_bytes` and `metadata.loomweave.ceiling_bytes`. Crash-loop counter increments. -**2c — Entity-count cap.** Per-run cumulative cap on `entity` + `edge` + `finding` notifications from a single plugin. Default: **500,000** combined records (configurable via `clarion.yaml:plugin_limits.max_records_per_run`, floor 10,000). On exceed: the current in-flight batch is flushed to the store; the plugin is killed; `CLA-INFRA-PLUGIN-ENTITY-CAP` is emitted. The run continues to Phase 2 (write-actor drains remaining queued records) but enters a partial-results state that forces `--force` to overwrite. +**2c — Entity-count cap.** Per-run cumulative cap on `entity` + `edge` + `finding` notifications from a single plugin. Default: **500,000** combined records (configurable via `loomweave.yaml:plugin_limits.max_records_per_run`, floor 10,000). On exceed: the current in-flight batch is flushed to the store; the plugin is killed; `LMWV-INFRA-PLUGIN-ENTITY-CAP` is emitted. The run continues to Phase 2 (write-actor drains remaining queued records) but enters a partial-results state that forces `--force` to overwrite. -**2d — Per-plugin RSS limit.** Applied at spawn via `prlimit(RLIMIT_AS)` on Linux, `setrlimit(RLIMIT_AS)` on macOS (POSIX path). Default: **2 GiB** virtual-memory cap (configurable via `clarion.yaml:plugin_limits.max_rss_mib`, floor 512 MiB). Process killed by OS on cap exceed; the core detects the SIGKILL exit (`WIFSIGNALED && WTERMSIG == 9`) and emits `CLA-INFRA-PLUGIN-OOM-KILLED`. Crash-loop counter increments. +**2d — Per-plugin RSS limit.** Applied at spawn via `prlimit(RLIMIT_AS)` on Linux, `setrlimit(RLIMIT_AS)` on macOS (POSIX path). Default: **2 GiB** virtual-memory cap (configurable via `loomweave.yaml:plugin_limits.max_rss_mib`, floor 512 MiB). Process killed by OS on cap exceed; the core detects the SIGKILL exit (`WIFSIGNALED && WTERMSIG == 9`) and emits `LMWV-INFRA-PLUGIN-OOM-KILLED`. Crash-loop counter increments. -The four subcodes of `CLA-INFRA-PLUGIN-VIOLATION` — `PATH-ESCAPE`, `FRAME-OVERSIZE`, `ENTITY-CAP`, `OOM-KILLED` — are plugin-independent and surface in every compat report. +The four subcodes of `LMWV-INFRA-PLUGIN-VIOLATION` — `PATH-ESCAPE`, `FRAME-OVERSIZE`, `ENTITY-CAP`, `OOM-KILLED` — are plugin-independent and surface in every compat report. ### Layer 3 — Crash-loop circuit breaker interaction (ADR-002 carries through) -ADR-002 defines a crash-loop breaker (>3 crashes in 60s → plugin disabled for the run). Violations from Layer 2 count as crashes. The breaker's existing finding (`CLA-INFRA-PLUGIN-DISABLED-CRASH-LOOP`) is augmented: when the triggering crashes are all Layer 2 violations of the same subcode, the disabled-finding carries `metadata.clarion.disabled_reason` with the dominant subcode. This is a diagnostics improvement, not a policy change. +ADR-002 defines a crash-loop breaker (>3 crashes in 60s → plugin disabled for the run). Violations from Layer 2 count as crashes. The breaker's existing finding (`LMWV-INFRA-PLUGIN-DISABLED-CRASH-LOOP`) is augmented: when the triggering crashes are all Layer 2 violations of the same subcode, the disabled-finding carries `metadata.loomweave.disabled_reason` with the dominant subcode. This is a diagnostics improvement, not a policy change. ### What is NOT in Layer 2 (explicit non-defences) @@ -78,7 +78,7 @@ Run plugins with a default-deny seccomp filter plus a landlock filesystem rulese **Pros**: strongest isolation story; neutralises T-01 materially even before hash-pinning lands in v0.2. -**Cons**: the reference Python plugin's `import wardline.core.registry` needs `sys.path` traversal outside the project root (pipx venv at `~/.local/pipx/venvs/...`); a landlock ruleset tight enough to be meaningful breaks the reference plugin. Cross-platform coverage is uneven (Linux kernel ≥5.13 for landlock; macOS has no equivalent surface). Engineering cost is large — ruleset tuning is per-plugin, and the core becomes responsible for expressing "allow the imports Python needs to import anything in `sys.path`" as a filesystem policy. Diagnostics are opaque (sandbox-denied syscalls surface as `EACCES` or `EPERM` from library code, not as a Clarion-owned finding). +**Cons**: the reference Python plugin's `import wardline.core.registry` needs `sys.path` traversal outside the project root (pipx venv at `~/.local/pipx/venvs/...`); a landlock ruleset tight enough to be meaningful breaks the reference plugin. Cross-platform coverage is uneven (Linux kernel ≥5.13 for landlock; macOS has no equivalent surface). Engineering cost is large — ruleset tuning is per-plugin, and the core becomes responsible for expressing "allow the imports Python needs to import anything in `sys.path`" as a filesystem policy. Diagnostics are opaque (sandbox-denied syscalls surface as `EACCES` or `EPERM` from library code, not as a Loomweave-owned finding). **Why rejected**: for a v0.1 shipping with one first-party plugin, hybrid closes the enumerated threats at ~10% of the engineering cost, and v0.2 adds hash-pinning which is a more durable answer to T-01 than syscall filtering. Sandbox is the right v0.2+ direction; it is the wrong v0.1 investment. @@ -108,7 +108,7 @@ Use cgroup v2 (`systemd-run --user --scope` or direct cgroup mounts) for per-plu **Pros**: richer than `RLIMIT_AS` — separate memory / swap / IO limits; structured accounting. -**Cons**: cgroup v2 cross-distro setup is inconsistent (rootless-cgroup support, v1/v2 mixed-mode systems, macOS has no cgroups at all). `prlimit`/`setrlimit` works on every POSIX target Clarion will ship to (Linux, macOS, Windows via WSL). For the specific control — "kill the plugin if it allocates too much memory" — `RLIMIT_AS` is sufficient. +**Cons**: cgroup v2 cross-distro setup is inconsistent (rootless-cgroup support, v1/v2 mixed-mode systems, macOS has no cgroups at all). `prlimit`/`setrlimit` works on every POSIX target Loomweave will ship to (Linux, macOS, Windows via WSL). For the specific control — "kill the plugin if it allocates too much memory" — `RLIMIT_AS` is sufficient. **Why rejected**: incremental control richness is not worth the cross-platform surface area at v0.1 scale. @@ -119,12 +119,12 @@ Use cgroup v2 (`systemd-run --user --scope` or direct cgroup mounts) for per-plu - Four threats rated by the panel as non-negotiable for v0.1 are closed: T-08 (path traversal), T-11 (framing DoS), T-12 (entity-count DoS), with T-01 partially mitigated (sandbox defers to v0.2 with hash-pinning, but resource exhaustion and path escape — two of T-01's exploitation paths — are closed). - "Trusted-source-only" stops being a doctrine and becomes an enforced minimum. The panel's framing ("configuration and enforcement changes, not new subsystems") is satisfied. - The manifest's `capabilities.runtime` block is an extension point. v0.2's hash-pinning (NG-16) and sandbox story can read and enforce more of it without redesigning the authority model. -- Every violation is a finding. Operators run `filigree list --label=clarion-infra --rule-id=CLA-INFRA-PLUGIN-VIOLATION` to audit plugin behaviour across runs. -- Defaults are conservative but tunable. Teams with unusually large codebases raise `max_records_per_run` in `clarion.yaml`; teams on memory-tight hosts lower `max_rss_mib`. The ADR names the configuration keys. +- Every violation is a finding. Operators run `filigree list --label=loomweave-infra --rule-id=LMWV-INFRA-PLUGIN-VIOLATION` to audit plugin behaviour across runs. +- Defaults are conservative but tunable. Teams with unusually large codebases raise `max_records_per_run` in `loomweave.yaml`; teams on memory-tight hosts lower `max_rss_mib`. The ADR names the configuration keys. ### Negative -- Plugin authors have one more contract to satisfy — the four limits are real and can bite a plugin that emits millions of noisy `CLA-FACT-*` findings. Mitigation: the `expected_entities_per_file` manifest declaration produces a sanity-warning (`CLA-INFRA-PLUGIN-ENTITY-OVERRUN-WARNING`) well before the hard cap, so the first sign of trouble isn't a killed plugin. +- Plugin authors have one more contract to satisfy — the four limits are real and can bite a plugin that emits millions of noisy `LMWV-FACT-*` findings. Mitigation: the `expected_entities_per_file` manifest declaration produces a sanity-warning (`LMWV-INFRA-PLUGIN-ENTITY-OVERRUN-WARNING`) well before the hard cap, so the first sign of trouble isn't a killed plugin. - The `prlimit` approach doesn't cover RSS only — `RLIMIT_AS` caps virtual memory, which overcounts for plugins that `mmap` large file ranges (e.g., tree-sitter's incremental parse buffers). Mitigation: default cap of 2 GiB is generous enough that a well-behaved plugin won't trip it; operators on constrained hosts who do trip it get a specific finding subcode. - Full sandbox is deferred; a malicious plugin that stays under the four caps can still exfiltrate source to a network destination. This is a known v0.2 gap and is named in the "NOT in Layer 2" list and in v0.1 release notes. @@ -132,7 +132,7 @@ Use cgroup v2 (`systemd-run --user --scope` or direct cgroup mounts) for per-plu - Enforcement point is ADR-002's subprocess supervision loop plus ADR-022's manifest-acceptance validator. No new core subsystem. - The `capabilities.runtime` manifest block is Python-first in v0.1 but language-neutral in shape; future Java/Rust plugins inherit it verbatim. -- Configuration keys live under `clarion.yaml:plugin_limits.*`. The v0.1 CLI documents the four keys and their floors; operators asking "can I raise this?" read one `clarion.yaml` section. +- Configuration keys live under `loomweave.yaml:plugin_limits.*`. The v0.1 CLI documents the four keys and their floors; operators asking "can I raise this?" read one `loomweave.yaml` section. ## Related Decisions @@ -145,7 +145,7 @@ Use cgroup v2 (`systemd-run --user --scope` or direct cgroup mounts) for per-plu - [Panel threat-model review §7 (risk matrix)](../../implementation/v0.1-reviews/panel-2026-04-17/09-threat-model.md) — T-01, T-08, T-11, T-12 scorings. - [Panel threat-model review §8, §12](../../implementation/v0.1-reviews/panel-2026-04-17/09-threat-model.md) — missing controls (1); three non-negotiable v0.1 controls (2). -- [Clarion v0.1 system design §10](../v0.1/system-design.md) — threat model summary table; `Defences NOT in v0.1` (seccomp/landlock deferred). -- [Clarion v0.1 detailed design §1.3](../v0.1/detailed-design.md) (lines 56-145) — plugin manifest shape; crash-loop circuit breaker. -- [Clarion v0.1 scope commitments — Q3](../../implementation/v0.1-scope-plans/v0.1-scope-commitments.md) — committed decision to hybrid (declared + enforced) over full sandbox. +- [Loomweave v0.1 system design §10](../v0.1/system-design.md) — threat model summary table; `Defences NOT in v0.1` (seccomp/landlock deferred). +- [Loomweave v0.1 detailed design §1.3](../v0.1/detailed-design.md) (lines 56-145) — plugin manifest shape; crash-loop circuit breaker. +- [Loomweave v0.1 scope commitments — Q3](../../implementation/v0.1-scope-plans/v0.1-scope-commitments.md) — committed decision to hybrid (declared + enforced) over full sandbox. - [NG-16](../v0.1/requirements.md) — plugin hash-pinning deferred to v0.2; companion v0.2 control for T-01/T-15. diff --git a/docs/clarion/adr/ADR-022-core-plugin-ontology.md b/docs/loomweave/adr/ADR-022-core-plugin-ontology.md similarity index 77% rename from docs/clarion/adr/ADR-022-core-plugin-ontology.md rename to docs/loomweave/adr/ADR-022-core-plugin-ontology.md index e9d6a37e..5d40322b 100644 --- a/docs/clarion/adr/ADR-022-core-plugin-ontology.md +++ b/docs/loomweave/adr/ADR-022-core-plugin-ontology.md @@ -19,7 +19,7 @@ The design review and the 2026-04-17 panel synthesis identified three sites wher 2. **Prompt dispatch (system-design §8)** chooses a template per entity kind. A `match entity.kind { "function" => …, "class" => … }` dispatch in core code forces a core release every time a plugin adds a kind. 3. **Core-reserved kinds.** `file`, `subsystem`, and `guidance` entities (`detailed-design.md:228-230`) are produced by core-owned algorithms: file discovery, Leiden clustering (ADR-006), guidance composition. A plugin emitting a `file` entity would be claiming authority the core already has. Conversely, the four core-reserved edge kinds (`contains`, `guides`, `emits_finding`, `in_subsystem`; `detailed-design.md:261`) carry semantics fixed across all plugins — a plugin cannot redefine `contains` to mean something other than structural containment. -Without an explicit decision, every downstream ADR re-litigates "should the core know about this kind?" and drift accumulates. ADR-014's `registry_backend: clarion` mode depends on the `file` entity kind being core-owned. ADR-006 needs "clustering operates on a named edge subgraph" to be true. ADR-021 needs the manifest to be a validatable contract the core can enforce. Those three ADRs share an unstated premise; this ADR states it. +Without an explicit decision, every downstream ADR re-litigates "should the core know about this kind?" and drift accumulates. ADR-014's `registry_backend: loomweave` mode depends on the `file` entity kind being core-owned. ADR-006 needs "clustering operates on a named edge subgraph" to be true. ADR-021 needs the manifest to be a validatable contract the core can enforce. Those three ADRs share an unstated premise; this ADR states it. ## Decision @@ -28,18 +28,18 @@ The boundary is drawn as follows. ### Core owns (ontology-shape) - **Record structs.** `Entity`, `Edge`, `Finding` as defined in `detailed-design.md:187-309`. `kind` and `rule_id` are `String` fields; no core `enum` enumerates valid values. -- **Identifier grammar.** Kind strings must match `[a-z][a-z0-9_]*`. Rule-ID prefixes must match `CLA-[A-Z]+(-[A-Z0-9]+)+`. A malformed identifier is rejected at `initialize` with `CLA-INFRA-MANIFEST-MALFORMED` (causes the plugin to fail to start). -- **Reserved entity kinds.** `file`, `subsystem`, `guidance`. These carry `plugin_id: core` only. A plugin manifest declaring any of them in its `kinds` list is rejected at `initialize` with `CLA-INFRA-MANIFEST-RESERVED-KIND`. +- **Identifier grammar.** Kind strings must match `[a-z][a-z0-9_]*`. Rule-ID prefixes must match `LMWV-[A-Z]+(-[A-Z0-9]+)+`. A malformed identifier is rejected at `initialize` with `LMWV-INFRA-MANIFEST-MALFORMED` (causes the plugin to fail to start). +- **Reserved entity kinds.** `file`, `subsystem`, `guidance`. These carry `plugin_id: core` only. A plugin manifest declaring any of them in its `kinds` list is rejected at `initialize` with `LMWV-INFRA-MANIFEST-RESERVED-KIND`. - **Reserved edge kinds.** `contains`, `guides`, `emits_finding`, `in_subsystem`. Semantics are fixed across all plugins and the core. Plugins *may emit* edges of these kinds (e.g., a Python `module` `contains` a `function`); plugins *may not redefine* them. A manifest that attaches non-structural semantics to `contains` (e.g., asserting it is commutative) is rejected. -- **Rule-ID namespace registry.** `CLA-INFRA-*` is core-only (pipeline/infrastructure findings). `CLA-FACT-*` is shared — any plugin or the core may emit factual findings under it. `CLA-{PLUGIN_ID_UPPERCASE}-*` is reserved to that plugin (`CLA-PY-*` for the Python plugin, `CLA-JAVA-*` for a future Java plugin). A plugin emitting a rule ID outside its namespace is refused at RPC with `CLA-INFRA-RULE-ID-NAMESPACE`. -- **Emission acceptance.** Entities whose `kind` is not declared in the emitting plugin's manifest are rejected at `analyze_file` notification with `CLA-INFRA-PLUGIN-UNDECLARED-KIND`. Edges whose `kind` is neither plugin-declared nor core-reserved are rejected likewise. +- **Rule-ID namespace registry.** `LMWV-INFRA-*` is core-only (pipeline/infrastructure findings). `LMWV-FACT-*` is shared — any plugin or the core may emit factual findings under it. `LMWV-{PLUGIN_ID_UPPERCASE}-*` is reserved to that plugin (`LMWV-PY-*` for the Python plugin, `LMWV-JAVA-*` for a future Java plugin). A plugin emitting a rule ID outside its namespace is refused at RPC with `LMWV-INFRA-RULE-ID-NAMESPACE`. +- **Emission acceptance.** Entities whose `kind` is not declared in the emitting plugin's manifest are rejected at `analyze_file` notification with `LMWV-INFRA-PLUGIN-UNDECLARED-KIND`. Edges whose `kind` is neither plugin-declared nor core-reserved are rejected likewise. ### Plugin owns (ontology-vocabulary) - All entity kinds other than the three core-reserved ones. - All edge kinds, including its declared use of the four core-reserved ones (plugin lists `contains` in its `edge_kinds` to signal that it emits containment edges, and binds itself to the core's semantics). - All tag vocabulary and per-kind properties. -- Plugin-specific rule IDs under its assigned namespace (`CLA-PY-STRUCTURE-001`, etc.). +- Plugin-specific rule IDs under its assigned namespace (`LMWV-PY-STRUCTURE-001`, etc.). - Prompt template selection. The plugin receives `build_prompt(entity_id, query_type, context, segments[])` and returns rendered segments. Core never inspects `entity.kind` to choose a template. ### Core validates shape without interpreting @@ -59,9 +59,9 @@ Core ships a closed `EntityKind` enum (`Function`, `Class`, `Module`, …) cover **Pros**: compile-time type-safety; stronger IDE support in core code; pattern-match dispatch feels natural. -**Cons**: every new plugin or new language-level abstraction requires a core release. The design's stated goal (`system-design.md:126-128`) — "adding a language must not require upstream changes to the core" — fails at the data model. Python adding a language-level concept Clarion wants to represent (pattern-matching bindings, structural protocols) forces a core release. Third-party plugin authorship dies the moment the upstream enum is missing the author's concept. +**Cons**: every new plugin or new language-level abstraction requires a core release. The design's stated goal (`system-design.md:126-128`) — "adding a language must not require upstream changes to the core" — fails at the data model. Python adding a language-level concept Loomweave wants to represent (pattern-matching bindings, structural protocols) forces a core release. Third-party plugin authorship dies the moment the upstream enum is missing the author's concept. -**Why rejected**: centralisation drift at the data-model level (`loom.md` §5 failure test). A closed enum is the stealth-monolith pattern applied to ontology. +**Why rejected**: centralisation drift at the data-model level (`weft.md` §5 failure test). A closed enum is the stealth-monolith pattern applied to ontology. ### Alternative 2: No validation — trust the plugin entirely @@ -69,7 +69,7 @@ Core stores whatever the plugin emits; no manifest-time or RPC-time checks on `k **Pros**: zero validation cost; plugin authors aren't fighting a schema. -**Cons**: typos survive to production (`functon` instead of `function`); diagnosis reduces to post-hoc SQL. Rule-ID namespacing collapses — a plugin could emit `CLA-INFRA-*` findings and break the pipeline-vs-analysis distinction. ADR-021's plugin authority model loses its data-layer half; a compromised plugin could mint synthetic `file` or `subsystem` entities that look authoritative but aren't. +**Cons**: typos survive to production (`functon` instead of `function`); diagnosis reduces to post-hoc SQL. Rule-ID namespacing collapses — a plugin could emit `LMWV-INFRA-*` findings and break the pipeline-vs-analysis distinction. ADR-021's plugin authority model loses its data-layer half; a compromised plugin could mint synthetic `file` or `subsystem` entities that look authoritative but aren't. **Why rejected**: shape-validation is O(kinds) at `initialize` and O(1) per emission — cheap. The downstream benefit (every emission is a contract check) is not. @@ -79,7 +79,7 @@ Core ships a schema listing every known language's kinds; plugins select from it **Pros**: cross-plugin queries on a shared kind name ("find all functions across Python and Java") work by name. -**Cons**: a shared name implies equivalent semantics, and it does not hold. Python's `class` and Java's `class` differ on MRO, metaclasses, `__init_subclass__`, nominal vs structural typing — the questions Clarion asks about classes are language-specific. False sharing produces worse outputs than no sharing (operators see "67 classes aggregated across languages" and can't reason about the count). Upstream maintenance of the superset reintroduces Alternative 1's core-release-per-language problem. +**Cons**: a shared name implies equivalent semantics, and it does not hold. Python's `class` and Java's `class` differ on MRO, metaclasses, `__init_subclass__`, nominal vs structural typing — the questions Loomweave asks about classes are language-specific. False sharing produces worse outputs than no sharing (operators see "67 classes aggregated across languages" and can't reason about the count). Upstream maintenance of the superset reintroduces Alternative 1's core-release-per-language problem. **Why rejected**: false economy. For the v0.2+ cross-plugin case, the right answer is a per-pair mapping table, not a forced common vocabulary. @@ -89,7 +89,7 @@ Every kind is plugin-owned, including `file`, `subsystem`, `guidance`. Plugins c **Pros**: maximum purity; zero core-owned ontology surface. -**Cons**: `file` crosses plugin boundaries (one codebase, many plugins; cross-plugin queries on "which entities live in this file" require shared file identity). `subsystem` is output of core-owned Leiden clustering — the algorithm produces subsystem entities, no plugin does. `guidance` is a core-composed construct. Pushing these into a plugin creates arbitrary ownership ("which plugin owns `file`?") with no principled answer. ADR-014's `registry_backend: clarion` protocol requires `file` to be core-owned; shifting it to a plugin breaks the Filigree integration that ADR commits to. +**Cons**: `file` crosses plugin boundaries (one codebase, many plugins; cross-plugin queries on "which entities live in this file" require shared file identity). `subsystem` is output of core-owned Leiden clustering — the algorithm produces subsystem entities, no plugin does. `guidance` is a core-composed construct. Pushing these into a plugin creates arbitrary ownership ("which plugin owns `file`?") with no principled answer. ADR-014's `registry_backend: loomweave` protocol requires `file` to be core-owned; shifting it to a plugin breaks the Filigree integration that ADR commits to. **Why rejected**: the core-algorithm / plugin-ontology split *is* the decision being made; a zero-reserved-kinds rule makes the split impossible to draw at all. @@ -100,8 +100,8 @@ Every kind is plugin-owned, including `file`, `subsystem`, `guidance`. Plugins c - Principle 3 is no longer only prose. It is a data-model invariant the core enforces at manifest acceptance and at every RPC. Manifest-time rejection surfaces plugin bugs the moment they appear. - Adding a language is authoring a plugin. Zero core changes required. The Python plugin is the reference implementation; adding Java or Rust needs no upstream coordination. - Clustering (ADR-006), prompt dispatch (§8), and neighbour queries are plugin-generic by construction. Their test surface is the shape-not-semantics contract, not a per-language matrix. -- Rule-ID namespacing (REQ-FINDING-02) becomes enforceable. The core refuses cross-namespace emissions, so the `CLA-INFRA-PARSE-ERROR` vs `CLA-PY-PARSE-ERROR` drift noted in `04-self-sufficiency.md` Issue 7 becomes a validation failure rather than a convention. -- ADR-014's file-kind ownership is principled, not incidental. `registry_backend: clarion` works *because* `file` is core-owned; this ADR states that explicitly. +- Rule-ID namespacing (REQ-FINDING-02) becomes enforceable. The core refuses cross-namespace emissions, so the `LMWV-INFRA-PARSE-ERROR` vs `LMWV-PY-PARSE-ERROR` drift noted in `04-self-sufficiency.md` Issue 7 becomes a validation failure rather than a convention. +- ADR-014's file-kind ownership is principled, not incidental. `registry_backend: loomweave` works *because* `file` is core-owned; this ADR states that explicitly. ### Negative @@ -119,14 +119,14 @@ Every kind is plugin-owned, including `file`, `subsystem`, `guidance`. Plugins c - [ADR-002](./ADR-002-plugin-transport-json-rpc.md) — the RPC surface validates shape, not semantics, at `initialize` and at every `file_analyzed` notification. This ADR names exactly which shape rules apply. - [ADR-003](./ADR-003-entity-id-scheme.md) — entity IDs are `{plugin_id}:{kind}:{canonical_qualified_name}`; this ADR constrains what `{kind}` can legitimately be and who owns each namespace. - [ADR-006](./ADR-006-clustering-algorithm.md) — clustering operates on a named edge subgraph (`imports`, `calls`); this ADR guarantees the core treats edge kinds as strings, not as a fixed vocabulary, which is what makes ADR-006's configurable `edge_types` work for non-Python plugins. -- [ADR-014](./ADR-014-filigree-registry-backend.md) — the `file` entity kind is core-owned; ADR-014's `registry_backend: clarion` protocol relies on this being a first-class decision rather than an incidental one. +- [ADR-014](./ADR-014-filigree-registry-backend.md) — the `file` entity kind is core-owned; ADR-014's `registry_backend: loomweave` protocol relies on this being a first-class decision rather than an incidental one. - [ADR-021](./ADR-021-plugin-authority-hybrid.md) — the process-layer plugin authority model has its data-layer counterpart here; manifest acceptance is the joint enforcement point for both. ## References -- [Clarion v0.1 requirements §Design principles](../v0.1/requirements.md) (line 37) — Principle 3. +- [Loomweave v0.1 requirements §Design principles](../v0.1/requirements.md) (line 37) — Principle 3. - [REQ-CATALOG-02, REQ-CATALOG-03](../v0.1/requirements.md) — plugin-declared kinds; reserved edge set. -- [Clarion v0.1 system design §2](../v0.1/system-design.md) (lines 120-234) — core/plugin responsibility split; plugin manifest contract. -- [Clarion v0.1 detailed design §1, §2](../v0.1/detailed-design.md) (lines 56-136, 187-309) — manifest shape; Entity/Edge/Finding structs; core-reserved edge kinds at line 261; core-minted `file`/`subsystem`/`guidance` IDs at lines 228-230. -- [Loom doctrine §5](../../suite/loom.md) — centralisation-drift failure test; a closed core-defined kind enum qualifies. +- [Loomweave v0.1 system design §2](../v0.1/system-design.md) (lines 120-234) — core/plugin responsibility split; plugin manifest contract. +- [Loomweave v0.1 detailed design §1, §2](../v0.1/detailed-design.md) (lines 56-136, 187-309) — manifest shape; Entity/Edge/Finding structs; core-reserved edge kinds at line 261; core-minted `file`/`subsystem`/`guidance` IDs at lines 228-230. +- [Weft doctrine §5](../../suite/weft.md) — centralisation-drift failure test; a closed core-defined kind enum qualifies. - [Panel synthesis — self-sufficiency review](../../implementation/v0.1-reviews/panel-2026-04-17/04-self-sufficiency.md) — Issue 7 (rule-ID inconsistency) is the empirical case this ADR's namespace rule resolves. diff --git a/docs/clarion/adr/ADR-023-tooling-baseline.md b/docs/loomweave/adr/ADR-023-tooling-baseline.md similarity index 97% rename from docs/clarion/adr/ADR-023-tooling-baseline.md rename to docs/loomweave/adr/ADR-023-tooling-baseline.md index 75efc729..e7247944 100644 --- a/docs/clarion/adr/ADR-023-tooling-baseline.md +++ b/docs/loomweave/adr/ADR-023-tooling-baseline.md @@ -7,12 +7,12 @@ documentation-only repository. The workspace's lint, format, edition, test-runner, supply-chain, CI, and type-check posture is about to be locked into the first commit graph — either deliberately or by default. Setting these surfaces now -costs close to zero; retrofitting after any Clarion/Wardline-scale code has +costs close to zero; retrofitting after any Loomweave/Wardline-scale code has been written is expensive. ## Summary -The Clarion workspace adopts a strict tooling baseline from its first code +The Loomweave workspace adopts a strict tooling baseline from its first code commit, before any implementation lands: - **Rust**: edition **2024**, workspace-level `[lints]` block with @@ -33,7 +33,7 @@ the Python equivalents. ## Context -The Clarion repository sat at zero lines of code on 2026-04-18. Sprint 1's +The Loomweave repository sat at zero lines of code on 2026-04-18. Sprint 1's WP1 was about to land a three-crate Cargo workspace, a SQLite migration, a writer-actor, and a CLI skeleton. The original WP1 plan (UQ-WP1-09 resolution at `docs/implementation/sprint-1/wp1-scaffold.md`) committed to Rust edition @@ -52,7 +52,7 @@ baselines. Three observations forced the re-examination: greenfield and expensive to retrofit.** Each crate that exists before pedantic is introduced must be audited and silenced or fixed; starting pedantic-clean means every new contribution passes against the strict - floor from day one. The scope-commitment memo already commits Clarion to + floor from day one. The scope-commitment memo already commits Loomweave to "enterprise rigor at lack of scale" (`plans/v0.1-scope-commitments.md`). Pedantic is the cheapest expression of that commitment that exists. 3. **Sprint 1 has four `cargo test` call sites today**; Sprint 2+ will have @@ -241,7 +241,7 @@ generates less value than the discipline floor it provides. ### Positive -- Every future Clarion commit passes pedantic clippy, rustfmt, cargo-deny, +- Every future Loomweave commit passes pedantic clippy, rustfmt, cargo-deny, and — for Python — ruff + mypy-strict. The debt load is structurally bounded at zero. - New contributors (or new-me after a context switch) inherit the same @@ -252,7 +252,7 @@ generates less value than the discipline floor it provides. gate later. - `cargo nextest` halves test-suite wall-clock time on workspace builds versus `cargo test`; for WP1's already-growing test count (12 integration - tests in `clarion-storage` alone), the compounded savings are non-trivial. + tests in `loomweave-storage` alone), the compounded savings are non-trivial. - ADR-023's existence as a discoverable decision record means "why is this pedantic?" answers itself without a git-archaeology trip. diff --git a/docs/clarion/adr/ADR-024-guidance-schema-vocabulary.md b/docs/loomweave/adr/ADR-024-guidance-schema-vocabulary.md similarity index 77% rename from docs/clarion/adr/ADR-024-guidance-schema-vocabulary.md rename to docs/loomweave/adr/ADR-024-guidance-schema-vocabulary.md index 77c7753e..04d09bf0 100644 --- a/docs/clarion/adr/ADR-024-guidance-schema-vocabulary.md +++ b/docs/loomweave/adr/ADR-024-guidance-schema-vocabulary.md @@ -3,19 +3,19 @@ **Status**: Accepted **Date**: 2026-05-03 **Deciders**: qacona@gmail.com -**Context**: Investigating filigree issue `clarion-4cd11905e2` — `entities.priority` TEXT-affinity bug — surfaced that the issue body assumed numeric urgency (Filigree's `P0..P4` convention) while Clarion's design defines `priority` as a six-level string enum for guidance composition. The same word means two unrelated things in two Loom siblings, with no managing ADR. A skeleton-audit pass surfaced two more unmanaged clashes (`critical`, `source`). This ADR resolves all three before Sprint 2 Tier B writes the first `ORDER BY` against the column and before new edge / catalog wire shapes learn the wrong names. +**Context**: Investigating filigree issue `clarion-4cd11905e2` — `entities.priority` TEXT-affinity bug — surfaced that the issue body assumed numeric urgency (Filigree's `P0..P4` convention) while Loomweave's design defines `priority` as a six-level string enum for guidance composition. The same word means two unrelated things in two Weft siblings, with no managing ADR. A skeleton-audit pass surfaced two more unmanaged clashes (`critical`, `source`). This ADR resolves all three before Sprint 2 Tier B writes the first `ORDER BY` against the column and before new edge / catalog wire shapes learn the wrong names. ## Summary -Three guidance-schema fields and one finding field are renamed: `entity.properties.priority` (string enum) becomes `entity.properties.scope_level`, with a new companion integer column `scope_rank` (CASE-mapped 1..6) for ordered queries; `entity.properties.critical` (bool) becomes `entity.properties.pinned`; `finding.source` (`{tool, tool_version, run_id}`) and `entity.properties.source` on guidance entities (`"manual" | "wardline_derived" | "filigree_promotion"`) both become `provenance`. `entity.source` (`SourceRange` on code entities) is unchanged — the type name disambiguates and the field is correctly named for the role. Migration `0001_initial_schema.sql` is edited in place; the in-place edit policy retires the moment any external operator pulls a published Clarion build, after which all schema changes stack as `0002_*.sql`, `0003_*.sql`, and so on. +Three guidance-schema fields and one finding field are renamed: `entity.properties.priority` (string enum) becomes `entity.properties.scope_level`, with a new companion integer column `scope_rank` (CASE-mapped 1..6) for ordered queries; `entity.properties.critical` (bool) becomes `entity.properties.pinned`; `finding.source` (`{tool, tool_version, run_id}`) and `entity.properties.source` on guidance entities (`"manual" | "wardline_derived" | "filigree_promotion"`) both become `provenance`. `entity.source` (`SourceRange` on code entities) is unchanged — the type name disambiguates and the field is correctly named for the role. Migration `0001_initial_schema.sql` is edited in place; the in-place edit policy retires the moment any external operator pulls a published Loomweave build, after which all schema changes stack as `0002_*.sql`, `0003_*.sql`, and so on. ## Context -The Loom federation axiom (`docs/suite/loom.md` §3–§5) requires solo-useful, pairwise-composable, enrich-only products. None of the three failure modes (semantic / initialization / pipeline coupling) directly applies to vocabulary, but cross-product readability is the *prerequisite* for honest pairwise composition. When a Loom user reads Clarion's design and Filigree's CLI in the same session and `priority` means different things in each, every cross-product debugging pass starts from a misframe. The bug at `clarion-4cd11905e2` is the concrete proof: the issue's filer assumed Filigree's meaning, the audit pass had to reframe before any fix could land. +The Weft federation axiom (`docs/suite/weft.md` §3–§5) requires solo-useful, pairwise-composable, enrich-only products. None of the three failure modes (semantic / initialization / pipeline coupling) directly applies to vocabulary, but cross-product readability is the *prerequisite* for honest pairwise composition. When a Weft user reads Loomweave's design and Filigree's CLI in the same session and `priority` means different things in each, every cross-product debugging pass starts from a misframe. The bug at `clarion-4cd11905e2` is the concrete proof: the issue's filer assumed Filigree's meaning, the audit pass had to reframe before any fix could land. Three mismatches are documented in the [skeleton audit](../../implementation/handoffs/2026-05-03-skeleton-audit.md): -1. **`priority`** — Clarion's guidance composition rank `project | subsystem | package | module | class | function` +1. **`priority`** — Loomweave's guidance composition rank `project | subsystem | package | module | class | function` (`detailed-design.md:453`, `system-design.md:346`) collides with Filigree's `priority` label vocabulary `P0 | P1 | P2 | P3 | P4`. The schema's `priority TEXT GENERATED ALWAYS AS (json_extract(...))` column at `0001_initial_schema.sql:163-165` cannot serve any future `ORDER BY priority` query @@ -24,7 +24,7 @@ Three mismatches are documented in the [skeleton audit](../../implementation/han TEXT-vs-INTEGER affinity discussion in the original bug body is moot — neither affinity helps when the input is a string enum. -2. **`critical`** — Clarion's guidance entity has `critical: bool` (`detailed-design.md:467`) +2. **`critical`** — Loomweave's guidance entity has `critical: bool` (`detailed-design.md:467`) meaning "preserved across token-budget pressure" (do-not-drop). The same word also surfaces as a `tags: ["critical"]` literal on the same entity (`:449`). Both clash with Filigree's `severity:critical` enum tier and the conventional reading of `P0` as "Critical." Unlike @@ -32,7 +32,7 @@ Three mismatches are documented in the [skeleton audit](../../implementation/han a categorically wrong answer — but the cross-product reader still has to disambiguate manually every time. -3. **`source`** — Used three different ways inside Clarion alone: +3. **`source`** — Used three different ways inside Loomweave alone: - `entity.source = SourceRange { file_id, byte_start, byte_end, line_start, line_end }` (`detailed-design.md:204`) — code-anchor location. - `finding.source = { tool: String, tool_version: String, run_id: String }` @@ -41,11 +41,11 @@ Three mismatches are documented in the [skeleton audit](../../implementation/han guidance entities (`detailed-design.md:471`) — guidance authorship origin. And a fourth meaning on Filigree's side: `source:` taxonomy label = `scanner | review | agent` - (how an issue was discovered; per `filigree taxonomy`). The within-Clarion overload is the + (how an issue was discovered; per `filigree taxonomy`). The within-Loomweave overload is the bigger problem; the type name `SourceRange` saves the first usage at the Rust level, but in prose docs and JSON keys the same word does three jobs four lines apart. -ADR-017 (severity vocabulary), ADR-022 (rule-ID namespacing), and ADR-004 (finding wire shape) are the v0.1 model managed clashes — same-word-different-meaning that got an explicit mapping or namespacing convention at design time and shipped clean. The unmanaged clashes above are the cases where the same recognition didn't happen. ADR-024 brings the unmanaged ones into the managed pattern. The companion doctrine — `docs/suite/glossary.md` plus the ADR-acceptance rule in `docs/clarion/adr/README.md` — addresses the *recurrence* mechanism so the next clash is blocked at design review rather than discovered during implementation. +ADR-017 (severity vocabulary), ADR-022 (rule-ID namespacing), and ADR-004 (finding wire shape) are the v0.1 model managed clashes — same-word-different-meaning that got an explicit mapping or namespacing convention at design time and shipped clean. The unmanaged clashes above are the cases where the same recognition didn't happen. ADR-024 brings the unmanaged ones into the managed pattern. The companion doctrine — `docs/suite/glossary.md` plus the ADR-acceptance rule in `docs/loomweave/adr/README.md` — addresses the *recurrence* mechanism so the next clash is blocked at design review rather than discovered during implementation. ## Decision @@ -66,12 +66,12 @@ ADR-017 (severity vocabulary), ADR-022 (rule-ID namespacing), and ADR-004 (findi **Decision**: edit `0001_initial_schema.sql` in place. **Rationale**: -- `v0.1-sprint-1` git tag is the immutable historical record. `git show v0.1-sprint-1:crates/clarion-storage/migrations/0001_initial_schema.sql` reproduces the pre-rename shape if anyone needs it. The tag does not lock the migration file's mutability; it locks the code at a published commit. -- No real consumers exist. The only writer of `.clarion/clarion.db` today is the walking-skeleton fixture (`tests/e2e/sprint_1_walking_skeleton.sh`). No external operator has run `clarion analyze` against a real codebase and produced a database whose `schema_migrations` ledger we would break by rewriting `0001`. +- `v0.1-sprint-1` git tag is the immutable historical record. `git show v0.1-sprint-1:crates/loomweave-storage/migrations/0001_initial_schema.sql` reproduces the pre-rename shape if anyone needs it. The tag does not lock the migration file's mutability; it locks the code at a published commit. +- No real consumers exist. The only writer of `.loomweave/loomweave.db` today is the walking-skeleton fixture (`tests/e2e/sprint_1_walking_skeleton.sh`). No external operator has run `loomweave analyze` against a real codebase and produced a database whose `schema_migrations` ledger we would break by rewriting `0001`. - Stacking `0002_*.sql` to preserve a migration that nobody has applied creates ledger debt for a reader-fiction. The same anti-pattern (legacy filenames preserved "for history") is explicitly rejected by repo convention. - ADR-011 + the comment at `0001_initial_schema.sql:1-9` says the full shape is "frozen at L1-lock time." L1 was a *design lock* (Sprint 1 lock-in #1: schema shape), not a *consumer lock*. The lock-ins exist to prevent in-flight Sprint-1 churn; they are not a no-edit-ever rule against post-Sprint-1 design correction. -**Retirement condition** (when this policy switches from "edit in place" to "stack-only"): the first time any external operator pulls a published Clarion build (release artefact, package install, or `git clone` followed by `clarion install` against their own codebase) and produces a `.clarion/clarion.db` with a `schema_migrations` ledger. After that point, rewriting `0001` would break their migration ledger; every subsequent schema change must stack as `0002_*.sql`, `0003_*.sql`, and so on. +**Retirement condition** (when this policy switches from "edit in place" to "stack-only"): the first time any external operator pulls a published Loomweave build (release artefact, package install, or `git clone` followed by `loomweave install` against their own codebase) and produces a `.loomweave/loomweave.db` with a `schema_migrations` ledger. After that point, rewriting `0001` would break their migration ledger; every subsequent schema change must stack as `0002_*.sql`, `0003_*.sql`, and so on. The retirement trigger is observable: a published release tag plus an out-of-repo bug report filing or a non-author git history accessing the binary. Until then, in-place edits are the lower-debt path. After the trigger fires, this ADR retires its in-place clause and stack-only policy applies; this ADR does not need to be superseded for that transition — the trigger is named here so the policy switch is recognisable when it happens. @@ -112,7 +112,7 @@ Leave the schema field as `priority`, fix the affinity by adding a `priority_ran **Pros**: smallest mechanical change; no rename churn in code or docs. -**Cons**: locks in the cross-product confusion. Every Loom user reading Clarion's `priority` field will hit the same misframing the bug filer hit. The audit's own diagnosis is that the *vocabulary* is the bug, not just the affinity. Refusing the rename means accepting that every cross-product reader pays the disambiguation cost forever; the cumulative cost outstrips the one-time rename cost within months of v0.1 release. +**Cons**: locks in the cross-product confusion. Every Weft user reading Loomweave's `priority` field will hit the same misframing the bug filer hit. The audit's own diagnosis is that the *vocabulary* is the bug, not just the affinity. Refusing the rename means accepting that every cross-product reader pays the disambiguation cost forever; the cumulative cost outstrips the one-time rename cost within months of v0.1 release. **Why rejected**: the audit's framing is correct. Cosmetic correctness here is structural correctness later. @@ -140,17 +140,17 @@ Add the audit's findings to the glossary as `open` clashes; defer the schema cor ### Positive -- Cross-product reader confusion on `priority` and `critical` ends. A reader of Clarion's docs and Filigree's CLI in the same session no longer has to disambiguate these words. +- Cross-product reader confusion on `priority` and `critical` ends. A reader of Loomweave's docs and Filigree's CLI in the same session no longer has to disambiguate these words. - The schema can serve correct ordered queries on guidance composition. WP6 (briefing serving) will write `ORDER BY scope_rank ASC` and get the documented composition order without query-time CASE expressions. -- The within-Clarion `source` overload is reduced from three uses to one (`entity.source = SourceRange`), with the type name carrying the namespace. +- The within-Loomweave `source` overload is reduced from three uses to one (`entity.source = SourceRange`), with the type name carrying the namespace. - The glossary becomes a real artefact backed by an Accepted ADR, not a wishful one. Future cross-product field names cite the ADR-acceptance rule that produced this ADR. - The migration-policy question that was implicit until now becomes explicit. Future schema changes have a written trigger condition for when to switch from in-place edits to stacking. ### Negative - The rename has a wider blast radius than the schema alone. `requirements.md`, `system-design.md`, and `detailed-design.md` all carry references that must move; this ADR's commit updates them in lockstep. Historical ADRs (e.g., [ADR-007](./ADR-007-summary-cache-key.md) at line 56 still references `critical: true` in its example) are **not** touched — Accepted ADRs are immutable per repo convention. Readers of those historical ADRs should map the old field names to the post-ADR-024 ones using this ADR's rename table. Plan and review documents archived under `docs/implementation/` (`agent-plans/`, `v0.1-reviews/`) are also left alone (historical snapshots, not normative). -- The walking-skeleton fixture's `.clarion/clarion.db` (committed for the e2e test) is rebuilt with the new schema as part of this change. The test script's expectations stay the same; the database file changes. Mitigation: the e2e script doesn't depend on the schema's column names; it asserts on the persisted entity row's `id` and `kind`. -- An external operator who *did* run a pre-publication build of Clarion against a real codebase between Sprint 1 close and this ADR has a `.clarion/clarion.db` with the old `priority` column. Mitigation: nobody has — this is the explicit pre-condition for the in-place policy. Any future operator who runs the new build sees the corrected schema only. +- The walking-skeleton fixture's `.loomweave/loomweave.db` (committed for the e2e test) is rebuilt with the new schema as part of this change. The test script's expectations stay the same; the database file changes. Mitigation: the e2e script doesn't depend on the schema's column names; it asserts on the persisted entity row's `id` and `kind`. +- An external operator who *did* run a pre-publication build of Loomweave against a real codebase between Sprint 1 close and this ADR has a `.loomweave/loomweave.db` with the old `priority` column. Mitigation: nobody has — this is the explicit pre-condition for the in-place policy. Any future operator who runs the new build sees the corrected schema only. - Re-using the `0001` migration version number means the `schema_migrations` ledger row is identical (`version=1`, `name='0001_initial_schema'`) before and after the edit. A consumer who applied the pre-edit `0001` and then upgrades will not see any migration to apply, and their database will diverge silently from the new shape. Mitigation: the same pre-condition — no such consumer exists; the retirement trigger names exactly this case. ### Neutral @@ -161,18 +161,18 @@ Add the audit's findings to the glossary as `open` clashes; defer the schema cor ## Related Decisions -- [ADR-017](./ADR-017-severity-and-dedup.md) — the model managed-clash pattern this ADR follows. Severity vocabulary's `metadata.clarion.internal_severity` round-trip slot is the kind of explicit mapping that prevents the unmanaged-clash failure mode this ADR retrofits. +- [ADR-017](./ADR-017-severity-and-dedup.md) — the model managed-clash pattern this ADR follows. Severity vocabulary's `metadata.loomweave.internal_severity` round-trip slot is the kind of explicit mapping that prevents the unmanaged-clash failure mode this ADR retrofits. - [ADR-022](./ADR-022-core-plugin-ontology.md) — the rule-ID grammar enforcement at the plugin boundary is the namespacing model. This ADR does not introduce a new namespace; it removes a vocabulary collision. - [ADR-011](./ADR-011-writer-actor-concurrency.md) — names migration `0001_initial_schema.sql` as the L1 lock target. This ADR clarifies that the lock is design-shape, not file-mutability, while no consumers exist. -- [ADR-018](./ADR-018-identity-reconciliation.md) — analogous cross-product field-name divergence (Wardline `FingerprintEntry` vs. Clarion L7 qualname). Different field, different resolution path (deferred), same class of problem. +- [ADR-018](./ADR-018-identity-reconciliation.md) — analogous cross-product field-name divergence (Wardline `FingerprintEntry` vs. Loomweave L7 qualname). Different field, different resolution path (deferred), same class of problem. ## References - [Skeleton audit](../../implementation/handoffs/2026-05-03-skeleton-audit.md) — durable record of the audit pass, reviewer feedback (architecture-critic + leverage-analyst), and the reconciled plan that produced this ADR. -- [Loom suite glossary](../../suite/glossary.md) — the federation-safe design-review artefact this ADR moves three entries from `open` to `managed` within. -- [Loom federation axiom](../../suite/loom.md) §3–§5 — the doctrine the glossary defends; the failure-test mode this ADR addresses is reader-side cross-product disambiguation cost. -- `crates/clarion-storage/migrations/0001_initial_schema.sql:163-191` — the schema sites this ADR edits in place. -- `crates/clarion-storage/tests/schema_apply.rs:138-169` — the test that documented the bug and is rewritten to test the corrected design. -- `docs/clarion/1.0/detailed-design.md:204, 270, 449, 453, 467, 471, 737-748` — the design-doc sites this ADR's renames touch. -- `docs/clarion/1.0/system-design.md:346, 675` — the system-level guidance composition references. +- [Weft suite glossary](../../suite/glossary.md) — the federation-safe design-review artefact this ADR moves three entries from `open` to `managed` within. +- [Weft federation axiom](../../suite/weft.md) §3–§5 — the doctrine the glossary defends; the failure-test mode this ADR addresses is reader-side cross-product disambiguation cost. +- `crates/loomweave-storage/migrations/0001_initial_schema.sql:163-191` — the schema sites this ADR edits in place. +- `crates/loomweave-storage/tests/schema_apply.rs:138-169` — the test that documented the bug and is rewritten to test the corrected design. +- `docs/loomweave/1.0/detailed-design.md:204, 270, 449, 453, 467, 471, 737-748` — the design-doc sites this ADR's renames touch. +- `docs/loomweave/1.0/system-design.md:346, 675` — the system-level guidance composition references. - Filigree issue `clarion-4cd11905e2` — the misframed bug whose triage produced this audit. diff --git a/docs/clarion/adr/ADR-025-minor-shared-standards.md b/docs/loomweave/adr/ADR-025-minor-shared-standards.md similarity index 100% rename from docs/clarion/adr/ADR-025-minor-shared-standards.md rename to docs/loomweave/adr/ADR-025-minor-shared-standards.md diff --git a/docs/clarion/adr/ADR-026-containment-wire-and-edge-identity.md b/docs/loomweave/adr/ADR-026-containment-wire-and-edge-identity.md similarity index 85% rename from docs/clarion/adr/ADR-026-containment-wire-and-edge-identity.md rename to docs/loomweave/adr/ADR-026-containment-wire-and-edge-identity.md index 01f46a1a..5d524bcd 100644 --- a/docs/clarion/adr/ADR-026-containment-wire-and-edge-identity.md +++ b/docs/loomweave/adr/ADR-026-containment-wire-and-edge-identity.md @@ -3,7 +3,7 @@ **Status**: Accepted; amended by [ADR-043](./ADR-043-edge-reanalysis-replacement.md) **Date**: 2026-05-05 **Deciders**: qacona@gmail.com -**Context**: B.3 (Sprint 2 Tier B) introduces the first edge kind Clarion has ever persisted (`contains`). Sprint-1 locked entity wire shape but explicitly deferred edge wire shape: the kickoff handoff at `docs/implementation/handoffs/2026-04-30-sprint-2-kickoff.md` §"Edge wire shape" states "B.3's first edge will define the protocol-level wire shape". Three coupled decisions arise — the wire envelope, the edge-row identity in storage, and the per-kind contract for `source_byte_start/end`. Locking them together prevents the four later edge kinds (`calls`, `imports`, `decorates`, `inherits_from`) from inheriting an under-specified precedent. +**Context**: B.3 (Sprint 2 Tier B) introduces the first edge kind Loomweave has ever persisted (`contains`). Sprint-1 locked entity wire shape but explicitly deferred edge wire shape: the kickoff handoff at `docs/implementation/handoffs/2026-04-30-sprint-2-kickoff.md` §"Edge wire shape" states "B.3's first edge will define the protocol-level wire shape". Three coupled decisions arise — the wire envelope, the edge-row identity in storage, and the per-kind contract for `source_byte_start/end`. Locking them together prevents the four later edge kinds (`calls`, `imports`, `decorates`, `inherits_from`) from inheriting an under-specified precedent. ## Summary @@ -21,9 +21,9 @@ The wire envelope (decision 1) determines what crosses the JSON-RPC boundary. Th ### Pre-decision state (Sprint-1 close, 2026-04-28) -- `AnalyzeFileResult { entities: Vec }` at `crates/clarion-core/src/plugin/protocol.rs:320-337`. No `edges` field. Sprint-1 plugins cannot emit edges; the host's typed-deserialise path at `host.rs:824` would silently ignore them. -- `edges` table at `crates/clarion-storage/migrations/0001_initial_schema.sql:66-79` carries `id TEXT PRIMARY KEY` AND `UNIQUE (kind, from_id, to_id)`. Two redundant identification surfaces. Three indexes (`ix_edges_from_kind`, `ix_edges_to_kind`, `ix_edges_kind`) — none on `id`. -- `WriterCmd` enum at `crates/clarion-storage/src/commands.rs:71-105` has no `InsertEdge` variant; the file-level comment names this as a later-WP addition. +- `AnalyzeFileResult { entities: Vec }` at `crates/loomweave-core/src/plugin/protocol.rs:320-337`. No `edges` field. Sprint-1 plugins cannot emit edges; the host's typed-deserialise path at `host.rs:824` would silently ignore them. +- `edges` table at `crates/loomweave-storage/migrations/0001_initial_schema.sql:66-79` carries `id TEXT PRIMARY KEY` AND `UNIQUE (kind, from_id, to_id)`. Two redundant identification surfaces. Three indexes (`ix_edges_from_kind`, `ix_edges_to_kind`, `ix_edges_kind`) — none on `id`. +- `WriterCmd` enum at `crates/loomweave-storage/src/commands.rs:71-105` has no `InsertEdge` variant; the file-level comment names this as a later-WP addition. - `EntityRecord.parent_id: Option` exists; Sprint-1 always emits `None`. - ADR-022:33 says plugins emit `contains` edges. ADR-022:52 says plugins emit `parent_id` chains. The dual-encoding question is addressed in the B.3 design doc; this ADR locks the wire/identity/source-range surface those encodings ride on. @@ -84,7 +84,7 @@ CREATE TABLE edges ( Two changes from the Sprint-1 schema: the `id TEXT PRIMARY KEY` column is removed; `(kind, from_id, to_id)` becomes the primary key (replacing the earlier `UNIQUE` constraint). `WITHOUT ROWID` is added because the natural PK makes the rowid redundant. -The `id` column never had a reader. No query in `clarion-storage`, `clarion-core`, or `clarion-cli` selects edges by `id`; lookups go through the three indexes (`ix_edges_from_kind`, `ix_edges_to_kind`, `ix_edges_kind`) which become covering indexes when the natural PK lands. The byte-cost analysis: at the v0.1 elspeth-slice target (~425k LOC × ~5 edges/entity ≈ 2M edges), an unused 16-byte hex-encoded `id` plus its B-tree costs ~120 MB of dead storage. Drop it. +The `id` column never had a reader. No query in `loomweave-storage`, `loomweave-core`, or `loomweave-cli` selects edges by `id`; lookups go through the three indexes (`ix_edges_from_kind`, `ix_edges_to_kind`, `ix_edges_kind`) which become covering indexes when the natural PK lands. The byte-cost analysis: at the v0.1 elspeth-slice target (~425k LOC × ~5 edges/entity ≈ 2M edges), an unused 16-byte hex-encoded `id` plus its B-tree costs ~120 MB of dead storage. Drop it. The natural PK provides idempotency-by-construction: re-running `analyze` on the same file produces the same `(kind, from_id, to_id)` triples; SQLite's `INSERT OR IGNORE` (or its equivalent in the writer-actor's batch logic) handles the duplicate without surfacing a rejected-edge condition. @@ -107,7 +107,7 @@ The `edges.source_byte_start` and `edges.source_byte_end` columns are nullable, The structural / non-derivable distinction is the discriminator. `contains` and `in_subsystem` are facts about graph structure with no separate textual occurrence — the location of "module M contains function F" is identical to F's source range, already stored on the entity. `guides` and `emits_finding` are core-emitted edges connecting structural entities to guidance / findings; the guidance's or finding's own source citation lives elsewhere. `calls`, `imports`, `decorates`, `inherits_from` all have a specific token in the source code that IS the edge — the call site, the import statement, the `@decorator_name` line, the base-class identifier in `class X(Base):`. -The writer-actor enforces this invariant at insert time: an edge whose `kind` requires `Some` arrives with `None` (or vice versa) is rejected with `CLA-INFRA-EDGE-SOURCE-RANGE-CONTRACT`. This converts the schema's permissiveness into a contract every consumer can rely on. +The writer-actor enforces this invariant at insert time: an edge whose `kind` requires `Some` arrives with `None` (or vice versa) is rejected with `LMWV-INFRA-EDGE-SOURCE-RANGE-CONTRACT`. This converts the schema's permissiveness into a contract every consumer can rely on. ### Plugin-side mechanical implications @@ -171,14 +171,14 @@ Instead of typed `source_byte_start/end` columns, edges carry a `properties: { s ### Negative -- **Migration churn.** Dropping `edges.id` is a schema migration. Per ADR-024, this is permissible pre-publication (the column has never been written). Once a published Clarion build emits its first edge row, this ADR's edit-in-place permission retires; future column changes require additive migration files. -- **Invariant enforcement adds a writer-actor responsibility.** The per-kind source-range contract is enforced by the writer; an edge violating it is rejected with a finding (`CLA-INFRA-EDGE-SOURCE-RANGE-CONTRACT`). One more thing the writer can refuse, one more failure mode plugin authors must understand. +- **Migration churn.** Dropping `edges.id` is a schema migration. Per ADR-024, this is permissible pre-publication (the column has never been written). Once a published Loomweave build emits its first edge row, this ADR's edit-in-place permission retires; future column changes require additive migration files. +- **Invariant enforcement adds a writer-actor responsibility.** The per-kind source-range contract is enforced by the writer; an edge violating it is rejected with a finding (`LMWV-INFRA-EDGE-SOURCE-RANGE-CONTRACT`). One more thing the writer can refuse, one more failure mode plugin authors must understand. - **`#[serde(flatten)] extra` on `RawEdge`** — like `RawEntity.extra`, consumers should not depend on unflattening this map for typed fields. Adding a typed field on `RawEdge` later requires migrating consumers off `extra`. ### Neutral - The cross-file edge-resolution rule has no v0.1 implementation cost (B.3 only emits within-file `contains`). The cost is documentation discipline, paid in this ADR and the B.3 design doc. -- `WITHOUT ROWID` is a SQLite-specific optimisation. If Clarion ever adopts a non-SQLite storage backend (not currently planned), this clause needs reconsideration. +- `WITHOUT ROWID` is a SQLite-specific optimisation. If Loomweave ever adopts a non-SQLite storage backend (not currently planned), this clause needs reconsideration. ## Related Decisions diff --git a/docs/clarion/adr/ADR-027-ontology-version-semver.md b/docs/loomweave/adr/ADR-027-ontology-version-semver.md similarity index 99% rename from docs/clarion/adr/ADR-027-ontology-version-semver.md rename to docs/loomweave/adr/ADR-027-ontology-version-semver.md index fd4a4efe..8e9be358 100644 --- a/docs/clarion/adr/ADR-027-ontology-version-semver.md +++ b/docs/loomweave/adr/ADR-027-ontology-version-semver.md @@ -91,7 +91,7 @@ For Python plugins specifically, the manifest `[ontology].ontology_version` and `ontology_version` is just an integer (or a single-component "1", "2", "3"). Bump on any change. -**Why rejected**: defeats the purpose of having a version field. Consumers cannot tell "additive" from "breaking" without reading the changelog. The handshake validator at the Rust host (`crates/clarion-core/src/plugin/protocol.rs`) already validates non-empty; it can also enforce semver shape with no extra cost. +**Why rejected**: defeats the purpose of having a version field. Consumers cannot tell "additive" from "breaking" without reading the changelog. The handshake validator at the Rust host (`crates/loomweave-core/src/plugin/protocol.rs`) already validates non-empty; it can also enforce semver shape with no extra cost. ### Alternative 2: ABI-style four-part version (MAJOR.MINOR.PATCH.BUILD) diff --git a/docs/clarion/adr/ADR-028-edge-confidence-tiers.md b/docs/loomweave/adr/ADR-028-edge-confidence-tiers.md similarity index 92% rename from docs/clarion/adr/ADR-028-edge-confidence-tiers.md rename to docs/loomweave/adr/ADR-028-edge-confidence-tiers.md index 9e987dce..069fdefc 100644 --- a/docs/clarion/adr/ADR-028-edge-confidence-tiers.md +++ b/docs/loomweave/adr/ADR-028-edge-confidence-tiers.md @@ -11,7 +11,7 @@ Three decisions, locked together: 1. **Three confidence tiers on every `calls` / `references` edge**: `resolved` (pyright/AST resolved the symbol unambiguously), `ambiguous` (resolution returned N>1 candidates from static analysis; no LLM was consulted), `inferred` (no static resolution; an LLM was asked to guess the callee from caller context). The tier is a load-bearing field on the edge wire shape and storage row. 2. **MCP query default is `confidence >= resolved`.** Tools like `callers_of`, `execution_paths_from`, `neighborhood` accept a `confidence` parameter; absent the parameter, only `resolved` edges are returned. Inferred edges must be explicitly opted into. This prevents the consult-mode agent from treating LLM hallucinations as ground truth. -3. **Inferred edges are lazy-computed at MCP query time, not at scan time.** Static analysis produces only `resolved` and `ambiguous` edges during `clarion analyze`. The first MCP query that touches an unresolved call site triggers an LLM call (subject to the same `LlmProvider` discipline as summaries — RecordingProvider in tests, cost ceiling, model-tier mapping). Results cache keyed on the content hash of the caller and the candidate set. +3. **Inferred edges are lazy-computed at MCP query time, not at scan time.** Static analysis produces only `resolved` and `ambiguous` edges during `loomweave analyze`. The first MCP query that touches an unresolved call site triggers an LLM call (subject to the same `LlmProvider` discipline as summaries — RecordingProvider in tests, cost ceiling, model-tier mapping). Results cache keyed on the content hash of the caller and the candidate set. ## Context @@ -81,9 +81,9 @@ ALTER TABLE edges ADD COLUMN confidence TEXT NOT NULL DEFAULT 'resolved' CREATE INDEX ix_edges_kind_confidence ON edges(kind, confidence); ``` -Per ADR-024's edit-in-place migration policy, this lands as a modification to `0001_initial_schema.sql` rather than a new migration file — Clarion has not published a build that writes a `calls` or `references` row, so no consumer-side migration is required. (The policy retires the first time an external operator pulls a published build; ADR-028's amendment of `0001` is the last edit it permits to the edges table.) +Per ADR-024's edit-in-place migration policy, this lands as a modification to `0001_initial_schema.sql` rather than a new migration file — Loomweave has not published a build that writes a `calls` or `references` row, so no consumer-side migration is required. (The policy retires the first time an external operator pulls a published build; ADR-028's amendment of `0001` is the last edit it permits to the edges table.) -**Per-kind invariant.** Plugins emitting `calls` or `references` MUST emit `confidence`. Plugins emitting structural edges (`contains`, `in_subsystem`, `guides`, `emits_finding`) MUST emit `confidence = Resolved` (these edges are facts about graph structure, not inferences). The writer-actor enforces this by kind; an edge violating it is rejected with `CLA-INFRA-EDGE-CONFIDENCE-CONTRACT`. +**Per-kind invariant.** Plugins emitting `calls` or `references` MUST emit `confidence`. Plugins emitting structural edges (`contains`, `in_subsystem`, `guides`, `emits_finding`) MUST emit `confidence = Resolved` (these edges are facts about graph structure, not inferences). The writer-actor enforces this by kind; an edge violating it is rejected with `LMWV-INFRA-EDGE-CONFIDENCE-CONTRACT`. **`properties.candidates` for ambiguous edges.** An `ambiguous` edge whose `to_id` is the agent's best static guess MAY carry `properties.candidates: ["id1", "id2", ...]` listing the other candidates pyright surfaced. This lets `callers_of` / `execution_paths_from` expand a single ambiguous edge into the full candidate set when the consumer asks. The properties shape is documented per-kind in the plugin's manifest. @@ -100,7 +100,7 @@ The MCP tool descriptions surface the parameter and its semantics explicitly so ### Decision 3 — Inferred edges are lazy-computed at MCP query time -Scan-time (`clarion analyze`) populates only `resolved` and `ambiguous` edges. The plugin's pyright-walk emits both tiers; no LLM is invoked during analyze. +Scan-time (`loomweave analyze`) populates only `resolved` and `ambiguous` edges. The plugin's pyright-walk emits both tiers; no LLM is invoked during analyze. At MCP query time, when a tool is called with `confidence >= Inferred` and the traversal reaches an entity with unresolved call sites (sites where pyright produced no edge at all), the MCP server: @@ -111,7 +111,7 @@ At MCP query time, when a tool is called with `confidence >= Inferred` and the t The same cost ceiling, retry, and RecordingProvider replay discipline that governs `summary(id)` (ADR-007, ADR-030) governs inferred-edge dispatch. -**`MAX_INFERRED_EDGES_PER_CALLER`.** A single LLM call returns at most N inferred candidate callees per caller entity (default 8, configurable in `clarion.yaml`). Pyright's unresolved-site count for the caller bounds the cost: if the caller has zero unresolved sites, no LLM call fires regardless of `confidence >= Inferred`. +**`MAX_INFERRED_EDGES_PER_CALLER`.** A single LLM call returns at most N inferred candidate callees per caller entity (default 8, configurable in `loomweave.yaml`). Pyright's unresolved-site count for the caller bounds the cost: if the caller has zero unresolved sites, no LLM call fires regardless of `confidence >= Inferred`. ### 2026-05-17 B.6 implementation resolution — query-time inference @@ -155,9 +155,9 @@ Collapse `ambiguous` into `inferred`. ### Alternative 3 — Eager inferred-edge computation at scan time -Compute inferred edges for every unresolved site during `clarion analyze`. +Compute inferred edges for every unresolved site during `loomweave analyze`. -**Why rejected**: cost at scale. At elspeth's ~425k LOC with the unresolved-site density typical of mid-typed Python code, the upfront LLM bill is in the hundreds-to-thousands-of-dollars range for value most queries never consume. The lazy path makes the cost proportional to actual query traffic; the eager path makes it proportional to codebase size. The eager path also blocks every `clarion analyze` run on LLM availability, which conflicts with the existing `--no-llm` mode (still supported per WP1 §5.1 and `system-design.md:580`). +**Why rejected**: cost at scale. At elspeth's ~425k LOC with the unresolved-site density typical of mid-typed Python code, the upfront LLM bill is in the hundreds-to-thousands-of-dollars range for value most queries never consume. The lazy path makes the cost proportional to actual query traffic; the eager path makes it proportional to codebase size. The eager path also blocks every `loomweave analyze` run on LLM availability, which conflicts with the existing `--no-llm` mode (still supported per WP1 §5.1 and `system-design.md:580`). ### Alternative 4 — Inferred edges live only in a separate read-only cache table, never in `edges` @@ -176,7 +176,7 @@ Compute inferred edges for every unresolved site during `clarion analyze`. ### Negative -- **Three places to enforce the per-kind invariant.** Plugin emission, writer-actor accept, MCP query path. A bug in any of the three leaks the wrong tier into the consumer. The writer-actor invariant (`CLA-INFRA-EDGE-CONFIDENCE-CONTRACT`) is the load-bearing one; plugin-side bugs surface as rejected runs rather than silent corruption. +- **Three places to enforce the per-kind invariant.** Plugin emission, writer-actor accept, MCP query path. A bug in any of the three leaks the wrong tier into the consumer. The writer-actor invariant (`LMWV-INFRA-EDGE-CONFIDENCE-CONTRACT`) is the load-bearing one; plugin-side bugs surface as rejected runs rather than silent corruption. - **MCP tools grow a `confidence` parameter.** Every traversal tool has one more argument. The default mitigates this for casual use, but the tool surface is wider than the v0.1-scope-commitments memo originally specified. - **Cache key has one more component.** The summary cache key (ADR-007) is a 5-tuple. The inferred-edge cache key is a 4-tuple `(caller_entity_id, caller_content_hash, model_id, prompt_version)`. Different shape, similar discipline. ADR-030 reconciles the two caching shapes. - **`#[serde(rename_all = "lowercase")]`** on `EdgeConfidence` couples the wire representation to the Rust enum name casing. Renaming the enum requires a wire-protocol pass. This is a tolerable trade. @@ -199,7 +199,7 @@ Compute inferred edges for every unresolved site during `clarion analyze`. changes invalidate the inference cache key and re-analysis removes old-hash unresolved-site anchors. Candidate target churn is handled by the cache row's materialization step; static duplicate edges win over inferred rows. -- **Per-model confidence-tier mapping** — if Haiku produces an inference and Sonnet produces a different inference for the same caller, are both stored? Initial answer: keyed on `model_id`, so yes; query path returns the model the operator's `clarion.yaml` names as the inference tier. Refinable in ADR-030. +- **Per-model confidence-tier mapping** — if Haiku produces an inference and Sonnet produces a different inference for the same caller, are both stored? Initial answer: keyed on `model_id`, so yes; query path returns the model the operator's `loomweave.yaml` names as the inference tier. Refinable in ADR-030. ## Related Decisions diff --git a/docs/clarion/adr/ADR-029-entity-associations-binding.md b/docs/loomweave/adr/ADR-029-entity-associations-binding.md similarity index 60% rename from docs/clarion/adr/ADR-029-entity-associations-binding.md rename to docs/loomweave/adr/ADR-029-entity-associations-binding.md index 5fc697b0..930c7590 100644 --- a/docs/clarion/adr/ADR-029-entity-associations-binding.md +++ b/docs/loomweave/adr/ADR-029-entity-associations-binding.md @@ -1,17 +1,17 @@ -# ADR-029: Entity Associations — Filigree Binding for Clarion Entities +# ADR-029: Entity Associations — Filigree Binding for Loomweave Entities **Status**: Accepted **Date**: 2026-05-16 **Deciders**: qacona@gmail.com -**Context**: Sprint 2's mid-sprint scope amendment (`docs/implementation/sprint-2/scope-amendment-2026-05.md`) names "an issue tracker that knows what code an issue is about" as the day-one value the MVP MCP surface delivers. Filigree already has `file_associations` (issue↔file relationships) but no concept of an issue being about a Clarion *entity* (function / class / module). WP9 in `v0.1-plan.md` scopes Loom integrations around findings emission (Clarion `findings.jsonl` → Filigree `/api/v1/scan-results`). Entity associations are a different concern that the WP9 scope did not name. This ADR (a) defines the binding shape, (b) splits WP9 into A (entity binding, v0.1) and B (findings emission, v0.1 or v0.2), (c) argues federation §5 compliance, and (d) specifies the content-hash drift detection that makes the binding survive code edits. +**Context**: Sprint 2's mid-sprint scope amendment (`docs/implementation/sprint-2/scope-amendment-2026-05.md`) names "an issue tracker that knows what code an issue is about" as the day-one value the MVP MCP surface delivers. Filigree already has `file_associations` (issue↔file relationships) but no concept of an issue being about a Loomweave *entity* (function / class / module). WP9 in `v0.1-plan.md` scopes Weft integrations around findings emission (Loomweave `findings.jsonl` → Filigree `/api/v1/scan-results`). Entity associations are a different concern that the WP9 scope did not name. This ADR (a) defines the binding shape, (b) splits WP9 into A (entity binding, v0.1) and B (findings emission, v0.1 or v0.2), (c) argues federation §5 compliance, and (d) specifies the content-hash drift detection that makes the binding survive code edits. ## Summary Three decisions: -1. **Filigree owns the binding table.** A new `entity_associations(issue_id, clarion_entity_id, content_hash_at_attach, attached_at, attached_by)` table lives in Filigree, not Clarion. Clarion entity IDs (per ADR-003 — `{plugin_id}:{kind}:{canonical_qualified_name}`) are stored as opaque strings; Filigree does not parse them or know what a "Clarion entity" is semantically. The federation §5 enrich-only rule passes (argued in §"Federation check" below). -2. **Two MCP tools front the binding.** `add_entity_association(issue_id, entity_id)` on Filigree's MCP server attaches an entity to an issue (and snapshots the current content_hash). `issues_for(entity_id, include_contained: bool = true)` on Clarion's MCP server queries Filigree's HTTP API and returns the issues attached to the entity (and, transitively, anything contained beneath it). -3. **Drift detection via content_hash snapshot at attach time.** When an association is created, Filigree stores Clarion's current `entities.content_hash` for that entity. Clarion's `issues_for` query compares the snapshotted hash to the current hash; mismatches are returned in a `drifted: [...]` envelope alongside the matched issues. Drifted associations are not automatically broken — the consult-mode agent decides whether the issue still applies. The MCP tool surfaces the drift; Filigree does not unilaterally invalidate. +1. **Filigree owns the binding table.** A new `entity_associations(issue_id, loomweave_entity_id, content_hash_at_attach, attached_at, attached_by)` table lives in Filigree, not Loomweave. Loomweave entity IDs (per ADR-003 — `{plugin_id}:{kind}:{canonical_qualified_name}`) are stored as opaque strings; Filigree does not parse them or know what a "Loomweave entity" is semantically. The federation §5 enrich-only rule passes (argued in §"Federation check" below). +2. **Two MCP tools front the binding.** `add_entity_association(issue_id, entity_id)` on Filigree's MCP server attaches an entity to an issue (and snapshots the current content_hash). `issues_for(entity_id, include_contained: bool = true)` on Loomweave's MCP server queries Filigree's HTTP API and returns the issues attached to the entity (and, transitively, anything contained beneath it). +3. **Drift detection via content_hash snapshot at attach time.** When an association is created, Filigree stores Loomweave's current `entities.content_hash` for that entity. Loomweave's `issues_for` query compares the snapshotted hash to the current hash; mismatches are returned in a `drifted: [...]` envelope alongside the matched issues. Drifted associations are not automatically broken — the consult-mode agent decides whether the issue still applies. The MCP tool surfaces the drift; Filigree does not unilaterally invalidate. ## Context @@ -25,26 +25,26 @@ Three decisions: Both kinds of association coexist. An issue can be associated with a file AND with one or more entities inside that file. The two are read independently. -### Why Filigree owns the table, not Clarion +### Why Filigree owns the table, not Loomweave -The candidate decision is "who is the durable store for this fact" — Filigree or Clarion. Filigree wins on three counts: +The candidate decision is "who is the durable store for this fact" — Filigree or Loomweave. Filigree wins on three counts: -- **Lifecycle alignment.** An issue outlives any single Clarion scan. Clarion is re-scannable from source; Filigree is not (issues, comments, history are durable). Storing entity associations on the Clarion side would mean a `clarion install` in a fresh checkout has no associations until the next scan-and-attach pass, which contradicts "I cloned the repo and want to ask which issues are about this function." +- **Lifecycle alignment.** An issue outlives any single Loomweave scan. Loomweave is re-scannable from source; Filigree is not (issues, comments, history are durable). Storing entity associations on the Loomweave side would mean a `loomweave install` in a fresh checkout has no associations until the next scan-and-attach pass, which contradicts "I cloned the repo and want to ask which issues are about this function." - **Existing pattern.** Filigree already stores `file_associations`, `dependencies`, `comments`, `labels` — all the issue-side metadata layers. `entity_associations` is the same shape of decision: an issue-side fact about what the issue is about. -- **Query direction.** The two relevant queries are "given an issue, what entity is it about?" (Filigree-side: cheap join from `issues`) and "given an entity, what issues are attached?" (Clarion calls Filigree's HTTP API). Both are served better when the durable store is on the Filigree side. +- **Query direction.** The two relevant queries are "given an issue, what entity is it about?" (Filigree-side: cheap join from `issues`) and "given an entity, what issues are attached?" (Loomweave calls Filigree's HTTP API). Both are served better when the durable store is on the Filigree side. -### Federation check (loom.md §5 — enrich-only rule) +### Federation check (weft.md §5 — enrich-only rule) The §5 failure modes: -1. **Semantic coupling** — does Filigree depend on Clarion to function? - No. The `entity_associations` table stores opaque string IDs. Filigree does not parse them, validate them, or know what a "Clarion entity" is. An installation of Filigree without Clarion sees empty `entity_associations` and operates normally. Issue create / update / close / dependency / label / comment all work without Clarion. +1. **Semantic coupling** — does Filigree depend on Loomweave to function? + No. The `entity_associations` table stores opaque string IDs. Filigree does not parse them, validate them, or know what a "Loomweave entity" is. An installation of Filigree without Loomweave sees empty `entity_associations` and operates normally. Issue create / update / close / dependency / label / comment all work without Loomweave. -2. **Initialization coupling** — must Filigree wait for Clarion to start, or vice versa? - No. The binding is created and queried only when both products are present. Clarion startup does not check for Filigree (the existing `--no-filigree` flag short-circuits the integration cleanly). Filigree startup does not check for Clarion. +2. **Initialization coupling** — must Filigree wait for Loomweave to start, or vice versa? + No. The binding is created and queried only when both products are present. Loomweave startup does not check for Filigree (the existing `--no-filigree` flag short-circuits the integration cleanly). Filigree startup does not check for Loomweave. -3. **Pipeline coupling** — does Filigree's data become wrong if Clarion goes away? - No. An issue with an entity association is a complete, semantically-valid Filigree issue with extra metadata. The association becomes "unresolvable" (Clarion can't tell you what entity the string refers to), but the issue itself is intact. +3. **Pipeline coupling** — does Filigree's data become wrong if Loomweave goes away? + No. An issue with an entity association is a complete, semantically-valid Filigree issue with extra metadata. The association becomes "unresolvable" (Loomweave can't tell you what entity the string refers to), but the issue itself is intact. The binding enriches both sides without making either depend on the other for core semantics. This is the federation axiom satisfied. @@ -52,10 +52,10 @@ The binding enriches both sides without making either depend on the other for co The original WP9 in `v0.1-plan.md` bundles two distinct integration stories: -- **WP9-A** (this ADR): entity_associations binding. Issue ↔ entity. New durable Filigree state. Surfaces issues_for() on Clarion's MCP for the "where am I and what's outstanding here" agent question. -- **WP9-B** (deferred): findings emission. Clarion's `findings.jsonl` → Filigree's `/api/v1/scan-results`. Adds Clarion as a finding source alongside Wardline and other scanners. +- **WP9-A** (this ADR): entity_associations binding. Issue ↔ entity. New durable Filigree state. Surfaces issues_for() on Loomweave's MCP for the "where am I and what's outstanding here" agent question. +- **WP9-B** (deferred): findings emission. Loomweave's `findings.jsonl` → Filigree's `/api/v1/scan-results`. Adds Loomweave as a finding source alongside Wardline and other scanners. -WP9-A is on the MVP critical path because `issues_for` is one of the seven MCP tools. WP9-B is valuable but not MVP — Clarion can produce a `findings.jsonl` artifact locally without POSTing it; agents can ask the human to import it later. The split lets WP9-A land in v0.1 without dragging WP9-B's full ADR-004/017/018 reconciliation along. +WP9-A is on the MVP critical path because `issues_for` is one of the seven MCP tools. WP9-B is valuable but not MVP — Loomweave can produce a `findings.jsonl` artifact locally without POSTing it; agents can ask the human to import it later. The split lets WP9-A land in v0.1 without dragging WP9-B's full ADR-004/017/018 reconciliation along. The two halves are independent: WP9-A introduces `entity_associations`, `add_entity_association`, `issues_for`. WP9-B introduces `scan_source` field on findings, dedup policy, severity round-trip. No cross-cutting refactor; no flag-day. @@ -68,22 +68,22 @@ The new Filigree migration introduces: ```sql CREATE TABLE entity_associations ( issue_id TEXT NOT NULL REFERENCES issues(id) ON DELETE CASCADE, - clarion_entity_id TEXT NOT NULL, + loomweave_entity_id TEXT NOT NULL, content_hash_at_attach TEXT NOT NULL, attached_at TEXT NOT NULL, -- ISO 8601 attached_by TEXT NOT NULL, -- actor identity - PRIMARY KEY (issue_id, clarion_entity_id) + PRIMARY KEY (issue_id, loomweave_entity_id) ); -CREATE INDEX ix_entity_assoc_entity ON entity_associations(clarion_entity_id); +CREATE INDEX ix_entity_assoc_entity ON entity_associations(loomweave_entity_id); ``` Notes: -- `clarion_entity_id` is opaque to Filigree. No `CHECK` constraint validates its grammar (validation would couple Filigree to ADR-003's segment format; the federation axiom forbids it). Malformed IDs are an MCP-tool-side concern. -- `content_hash_at_attach` is a snapshot of Clarion's `entities.content_hash` at the moment of attachment. Filigree does not interpret it — it's a blob Filigree hands back to Clarion at query time so Clarion can compare against the current state. +- `loomweave_entity_id` is opaque to Filigree. No `CHECK` constraint validates its grammar (validation would couple Filigree to ADR-003's segment format; the federation axiom forbids it). Malformed IDs are an MCP-tool-side concern. +- `content_hash_at_attach` is a snapshot of Loomweave's `entities.content_hash` at the moment of attachment. Filigree does not interpret it — it's a blob Filigree hands back to Loomweave at query time so Loomweave can compare against the current state. - `ix_entity_assoc_entity` lets `issues_for` resolve in O(log N) per entity_id rather than scanning. The primary key already covers the issue-side direction. -- `ON DELETE CASCADE` on `issue_id` ensures association rows die with the issue. There is no cascade on the entity side (Filigree cannot detect entity deletion; that's Clarion's domain). +- `ON DELETE CASCADE` on `issue_id` ensures association rows die with the issue. There is no cascade on the entity side (Filigree cannot detect entity deletion; that's Loomweave's domain). ### Decision 2 — Two MCP tools front the binding @@ -92,11 +92,11 @@ Notes: ``` add_entity_association(issue_id: str, entity_id: str, content_hash: str) -> AssociationResult - Attaches a Clarion entity to a Filigree issue. The content_hash argument is + Attaches a Loomweave entity to a Filigree issue. The content_hash argument is the entity's current content_hash, snapshotted at attach time for later - drift detection. The caller (typically Clarion's MCP server proxying for a + drift detection. The caller (typically Loomweave's MCP server proxying for a consult-mode agent, or a human operator) is responsible for fetching the - current content_hash from Clarion before calling. + current content_hash from Loomweave before calling. Returns the created or updated association row; the operation is idempotent on (issue_id, entity_id) — re-attaching updates content_hash_at_attach and @@ -105,7 +105,7 @@ add_entity_association(issue_id: str, entity_id: str, content_hash: str) -> Asso A peer `remove_entity_association(issue_id, entity_id)` tool removes the binding. A peer `list_entity_associations(issue_id)` tool enumerates associations for an issue (used by `issues_for`'s inverse direction and by issue-detail UI). -**On Clarion's MCP server** (new tool): +**On Loomweave's MCP server** (new tool): ``` issues_for(entity_id: str, include_contained: bool = true) -> IssuesForResult { @@ -119,13 +119,13 @@ issues_for(entity_id: str, include_contained: bool = true) -> IssuesForResult { reachable through contains edges (so an issue attached to a class shows up when asking about one of its methods). - Clarion performs the HTTP call to Filigree (using the existing + Loomweave performs the HTTP call to Filigree (using the existing client/auth from --no-filigree's enable path), fetches the association rows, then for each row computes drift_status by comparing the stored content_hash_at_attach to the current entities.content_hash. ``` -The Clarion-side tool is the integration surface for consult-mode agents. The Filigree-side tools are the durable-state surface for write operations. The two MCP servers do not share state — they communicate only via Filigree's existing HTTP API. +The Loomweave-side tool is the integration surface for consult-mode agents. The Filigree-side tools are the durable-state surface for write operations. The two MCP servers do not share state — they communicate only via Filigree's existing HTTP API. ### Decision 3 — Drift detection via snapshot, not invalidation @@ -137,15 +137,15 @@ When an association is created, the content_hash of the entity at that moment is **Why snapshot-and-flag, not invalidate-on-write.** Invalidating an association the moment code changes would create thrash: every commit to a frequently-edited file would orphan its associations. Snapshotting the hash at attach time and surfacing drift at query time lets the agent decide whether the change is material (the issue's still valid, just on slightly newer code) or invalidating (the issue's about a function that's been completely rewritten). The flag is information; the action is the agent's. -**Why hash snapshots, not version snapshots.** Clarion's `entities.content_hash` is already computed and indexed (`detailed-design.md` §3). Snapshotting it is a string copy. The alternative — snapshotting Clarion's analyze run_id — would require Filigree to know about Clarion's run lifecycle, which violates the federation axiom. +**Why hash snapshots, not version snapshots.** Loomweave's `entities.content_hash` is already computed and indexed (`detailed-design.md` §3). Snapshotting it is a string copy. The alternative — snapshotting Loomweave's analyze run_id — would require Filigree to know about Loomweave's run lifecycle, which violates the federation axiom. ## Alternatives Considered -### Alternative 1 — Clarion owns the binding table +### Alternative 1 — Loomweave owns the binding table -`entity_associations` lives in `.clarion/clarion.db`; Filigree calls Clarion's HTTP read API to enumerate associations per issue. +`entity_associations` lives in `.loomweave/loomweave.db`; Filigree calls Loomweave's HTTP read API to enumerate associations per issue. -**Why rejected**: lifecycle inversion. Issues are durable; `.clarion/` is reproducible from source plus a re-scan. Storing the binding on Clarion's side means associations vanish when `.clarion/` is rebuilt (fresh checkout, schema migration, deliberate reset). The "fresh-checkout-and-ask-issues_for" use case becomes broken-by-design. +**Why rejected**: lifecycle inversion. Issues are durable; `.loomweave/` is reproducible from source plus a re-scan. Storing the binding on Loomweave's side means associations vanish when `.loomweave/` is rebuilt (fresh checkout, schema migration, deliberate reset). The "fresh-checkout-and-ask-issues_for" use case becomes broken-by-design. ### Alternative 2 — Generalize `file_associations` to support entity IDs @@ -153,23 +153,23 @@ Add an `association_type` column to `file_associations` distinguishing 'file' fr **Why rejected**: overloading. The existing `file_associations` consumers (Filigree-native scanners, file-context displays) would all need to filter by `association_type`, and the drift-detection mechanism (content_hash snapshot) is meaningless for the 'file' kind. Two tables with different semantics are clearer than one table with a discriminator the readers must handle. -### Alternative 3 — Shared database between Clarion and Filigree +### Alternative 3 — Shared database between Loomweave and Filigree Skip the HTTP indirection; both products read from a shared SQLite DB. -**Why rejected**: explicit federation violation (loom.md §5). Shared schema couples upgrade cycles; one product's schema migration becomes the other product's deployment blocker. The two-database, HTTP-mediated design is the federation axiom in action. +**Why rejected**: explicit federation violation (weft.md §5). Shared schema couples upgrade cycles; one product's schema migration becomes the other product's deployment blocker. The two-database, HTTP-mediated design is the federation axiom in action. -### Alternative 4 — Eager drift propagation: Clarion notifies Filigree when entities change +### Alternative 4 — Eager drift propagation: Loomweave notifies Filigree when entities change -Clarion's `analyze` posts a delta to Filigree's MCP after each run; Filigree updates a `drift_status` column on `entity_associations`. +Loomweave's `analyze` posts a delta to Filigree's MCP after each run; Filigree updates a `drift_status` column on `entity_associations`. -**Why rejected**: introduces pipeline coupling. Filigree starts requiring Clarion to keep its drift_status field current. The lazy "snapshot-at-attach + compare-at-query" path moves the same information without coupling the write path. +**Why rejected**: introduces pipeline coupling. Filigree starts requiring Loomweave to keep its drift_status field current. The lazy "snapshot-at-attach + compare-at-query" path moves the same information without coupling the write path. -### Alternative 5 — Two-way binding: Clarion stores a list of (entity → issue_ids) too +### Alternative 5 — Two-way binding: Loomweave stores a list of (entity → issue_ids) too For query performance. -**Why rejected**: cache invalidation. Two writers to the same logical fact. The `issues_for` query path goes through Filigree's HTTP API once per query, which is acceptable at MCP query rates (one-shot agent question, not high-frequency). If query latency becomes a bottleneck, a TTL'd Clarion-side cache is the right intervention — not a second source of truth. +**Why rejected**: cache invalidation. Two writers to the same logical fact. The `issues_for` query path goes through Filigree's HTTP API once per query, which is acceptable at MCP query rates (one-shot agent question, not high-frequency). If query latency becomes a bottleneck, a TTL'd Loomweave-side cache is the right intervention — not a second source of truth. ## Consequences @@ -182,15 +182,15 @@ For query performance. ### Negative -- **Two MCP servers in play for one workflow.** The agent calls `add_entity_association` on Filigree's MCP (write path) and `issues_for` on Clarion's MCP (read path). The agent harness needs both MCP servers configured. Filigree's MCP is already a thing (per CLAUDE.md instruction); Clarion's MCP is WP8 (B.6 in the resumed sprint). No new infrastructure, but the configuration surface widens. -- **Cross-product schema migration.** Filigree gets a new migration. The Filigree codebase is at v2.0.1 on its own release rhythm; the migration lands as part of a Filigree minor release (target: 2.1.0). Coordination cost is one PR on the Filigree side, sequenced before Clarion's MCP server can call `add_entity_association`. +- **Two MCP servers in play for one workflow.** The agent calls `add_entity_association` on Filigree's MCP (write path) and `issues_for` on Loomweave's MCP (read path). The agent harness needs both MCP servers configured. Filigree's MCP is already a thing (per CLAUDE.md instruction); Loomweave's MCP is WP8 (B.6 in the resumed sprint). No new infrastructure, but the configuration surface widens. +- **Cross-product schema migration.** Filigree gets a new migration. The Filigree codebase is at v2.0.1 on its own release rhythm; the migration lands as part of a Filigree minor release (target: 2.1.0). Coordination cost is one PR on the Filigree side, sequenced before Loomweave's MCP server can call `add_entity_association`. - **`include_contained` semantics need design care.** "Issues attached to anything contained beneath this entity" assumes a clean contains-graph traversal. For module → class → method depths of 3-4, this is cheap. For pathological cases (a huge module with thousands of contained entities, each with attached issues), `include_contained` could return a long list. The MCP tool should paginate; the v0.1 implementation can cap at a reasonable size (e.g., 100 issues) with a `truncated: bool` flag. - **Reverse-lookup index size.** `ix_entity_assoc_entity` on Filigree grows with the number of associations across all issues. At expected v0.1 scale (hundreds of associations across the elspeth scale-test corpus), negligible. At enterprise scale (millions of associations), partition-by-prefix or similar. Out of scope for v0.1. ### Neutral - The `attached_by` actor field uses Filigree's existing actor-identity mechanism (`--actor` flag, MCP session identity). No new identity surface. -- The HTTP call from Clarion → Filigree uses the existing Filigree HTTP API; the `--no-filigree` flag (per WP9 scope in `v0.1-plan.md`) short-circuits the `issues_for` call, returning an empty result with a `filigree_unreachable: true` flag. +- The HTTP call from Loomweave → Filigree uses the existing Filigree HTTP API; the `--no-filigree` flag (per WP9 scope in `v0.1-plan.md`) short-circuits the `issues_for` call, returning an empty result with a `filigree_unreachable: true` flag. ## Open Questions @@ -199,14 +199,14 @@ For query performance. ## Related Decisions -- [ADR-003](./ADR-003-entity-id-scheme.md) — entity ID grammar. Filigree treats the ID as opaque; the grammar is Clarion's contract with itself, surfaced through `issues_for`. -- [ADR-014](./ADR-014-filigree-registry-backend.md) — Filigree's pluggable `RegistryProtocol`. The `entity_associations` table is a peer concept, not a use of `RegistryProtocol`. The two can coexist; the `clarion` backend mode in ADR-014 is a separate (still-scheduled) v0.1 deliverable. -- [ADR-022](./ADR-022-core-plugin-ontology.md) — core/plugin ontology boundary. Filigree is a peer product, not a Clarion plugin; the ontology rules do not apply to the Filigree-Clarion link. -- [`loom.md` §5](../../suite/loom.md) — federation failure modes. ADR-029 cites the three rules and argues compliance per-mode; failed audits would block this ADR. -- [WP9 in `v0.1-plan.md`](../../implementation/v0.1-plan.md#wp9--loom-integrations-filigree--wardline-clarion-side) — the work package this ADR splits in two. +- [ADR-003](./ADR-003-entity-id-scheme.md) — entity ID grammar. Filigree treats the ID as opaque; the grammar is Loomweave's contract with itself, surfaced through `issues_for`. +- [ADR-014](./ADR-014-filigree-registry-backend.md) — Filigree's pluggable `RegistryProtocol`. The `entity_associations` table is a peer concept, not a use of `RegistryProtocol`. The two can coexist; the `loomweave` backend mode in ADR-014 is a separate (still-scheduled) v0.1 deliverable. +- [ADR-022](./ADR-022-core-plugin-ontology.md) — core/plugin ontology boundary. Filigree is a peer product, not a Loomweave plugin; the ontology rules do not apply to the Filigree-Loomweave link. +- [`weft.md` §5](../../suite/weft.md) — federation failure modes. ADR-029 cites the three rules and argues compliance per-mode; failed audits would block this ADR. +- [WP9 in `v0.1-plan.md`](../../implementation/v0.1-plan.md#wp9--weft-integrations-filigree--wardline-loomweave-side) — the work package this ADR splits in two. ## References - [Sprint 2 scope-amendment memo](../../implementation/sprint-2/scope-amendment-2026-05.md) — the larger context: MVP MCP surface, entity_associations as the day-one value, sequencing. - [Filigree CLAUDE.md (project-root)](/home/john/CLAUDE.md) — existing MCP tool inventory; this ADR adds `add_entity_association`, `remove_entity_association`, `list_entity_associations`. -- [Filigree schema discovery — `GET /api/files/_schema`](https://github.com/tachyon-beep/filigree) — current file-side endpoints; entity-side endpoints are net-new. +- [Filigree schema discovery — `GET /api/files/_schema`](https://github.com/foundryside-dev/filigree) — current file-side endpoints; entity-side endpoints are net-new. diff --git a/docs/clarion/adr/ADR-030-on-demand-summary-scope.md b/docs/loomweave/adr/ADR-030-on-demand-summary-scope.md similarity index 85% rename from docs/clarion/adr/ADR-030-on-demand-summary-scope.md rename to docs/loomweave/adr/ADR-030-on-demand-summary-scope.md index ae506779..9f236634 100644 --- a/docs/clarion/adr/ADR-030-on-demand-summary-scope.md +++ b/docs/loomweave/adr/ADR-030-on-demand-summary-scope.md @@ -3,13 +3,13 @@ **Status**: Accepted **Date**: 2026-05-16 **Deciders**: qacona@gmail.com -**Context**: WP6 in `v0.1-plan.md` scopes LLM summarisation as a batched pipeline pass: Phase 4 (leaf summaries), Phase 5 (module summaries), Phase 6 (subsystem summaries), all executed during `clarion analyze`. Sprint 2's mid-sprint scope amendment narrows WP6 to the minimum the MVP MCP surface needs: `summary(entity_id)` as an on-demand MCP tool, computed lazily, cached on the existing 5-tuple cache key (ADR-007). The batched pipeline shape and the module/subsystem aggregation tiers move to v0.2. This ADR locks the narrowing and reconciles the summary-cache and inferred-edge-cache (ADR-028) shapes. +**Context**: WP6 in `v0.1-plan.md` scopes LLM summarisation as a batched pipeline pass: Phase 4 (leaf summaries), Phase 5 (module summaries), Phase 6 (subsystem summaries), all executed during `loomweave analyze`. Sprint 2's mid-sprint scope amendment narrows WP6 to the minimum the MVP MCP surface needs: `summary(entity_id)` as an on-demand MCP tool, computed lazily, cached on the existing 5-tuple cache key (ADR-007). The batched pipeline shape and the module/subsystem aggregation tiers move to v0.2. This ADR locks the narrowing and reconciles the summary-cache and inferred-edge-cache (ADR-028) shapes. ## Summary Three decisions: -1. **`summary(entity_id)` is an MCP tool, not a pipeline phase.** Summaries are generated on the first MCP call that asks for them; subsequent calls hit the cache. `clarion analyze` does not generate summaries unless explicitly requested via a `--prewarm-summaries` flag (deferred to v0.2; out of scope for the resumed Sprint 2). +1. **`summary(entity_id)` is an MCP tool, not a pipeline phase.** Summaries are generated on the first MCP call that asks for them; subsequent calls hit the cache. `loomweave analyze` does not generate summaries unless explicitly requested via a `--prewarm-summaries` flag (deferred to v0.2; out of scope for the resumed Sprint 2). 2. **The cache key remains ADR-007's 5-tuple `(entity_id, content_hash, prompt_template_id, model_tier, guidance_fingerprint)`.** No new components; ADR-030 does not amend ADR-007. The `inferred edges` cache (ADR-028 Decision 3) is a separate 4-tuple cache; the two are explicitly distinct cache shapes for distinct query types. 3. **Module-tier and subsystem-tier summarisation are deferred to v0.2.** The MVP MCP surface answers `summary(entity_id)` for leaf entities (functions, classes) and modules-as-leaves (where the module summary is a one-shot of the module's docstring + signature list, not an aggregation across leaf summaries). The hierarchical-aggregation shape from `system-design.md` §6 Phase 5–6 is not built in v0.1. @@ -19,16 +19,16 @@ Three decisions: The original `v0.1-plan.md` WP6 scope: -- `clarion.yaml` config loader for LLM policy. +- `loomweave.yaml` config loader for LLM policy. - `LlmProvider` trait with `OpenRouterProvider` + `RecordingProvider` (the latter from WP1 §5.1). - Prompt templates for leaf / module / subsystem tiers. - Five-tuple cache key (ADR-007) with TTL backstop and churn-eager invalidation. - **Phase 4** — leaf summarisation (per-entity Haiku calls). - **Phase 5** — module summarisation (aggregate leaf summaries). - **Phase 6** — subsystem summarisation (aggregate module summaries). -- Phases gated behind RecordingProvider replay; live runs require `CLARION_LLM_LIVE=1`. +- Phases gated behind RecordingProvider replay; live runs require `LOOMWEAVE_LLM_LIVE=1`. -This is a batched-pipeline design: every entity gets a summary during `clarion analyze`, written to the cache, served from the cache by downstream surfaces (MCP, HTTP, briefings). +This is a batched-pipeline design: every entity gets a summary during `loomweave analyze`, written to the cache, served from the cache by downstream surfaces (MCP, HTTP, briefings). ### Why the MVP doesn't need batched-pipeline summarisation @@ -36,14 +36,14 @@ The MVP MCP surface (`summary(entity_id)` as one of the 7 tools) is a single-ent The cost asymmetry makes the case sharper: -- **Batched pipeline at elspeth scale**: ~50,000 entities × ~1,500 tokens/leaf Haiku call ≈ 75M tokens upfront, well into the hundreds of dollars per `clarion analyze` run. +- **Batched pipeline at elspeth scale**: ~50,000 entities × ~1,500 tokens/leaf Haiku call ≈ 75M tokens upfront, well into the hundreds of dollars per `loomweave analyze` run. - **On-demand**: the agent asks for ~10–100 summaries per consultation session. Cost is bounded by query traffic, not codebase size. The 5-tuple cache key (ADR-007) already supports both shapes — a cache lookup is the same operation whether the cache was populated lazily or eagerly. Narrowing to lazy population costs nothing in cache architecture; the prompt templates and `LlmProvider` plumbing are unchanged. ### What about prewarming -A future operator may want to prewarm summaries for a subset of the project (high-traffic entities, recently-changed entities) so the first consult-mode query doesn't incur LLM latency. This is the right v0.2 enhancement: a `--prewarm-summaries [SELECTOR]` flag on `clarion analyze` that drives the same `summary(id)` code path against a pre-selected entity set. The infrastructure for it lands in v0.1 (the MCP tool + cache); the orchestration around prewarming lands in v0.2. +A future operator may want to prewarm summaries for a subset of the project (high-traffic entities, recently-changed entities) so the first consult-mode query doesn't incur LLM latency. This is the right v0.2 enhancement: a `--prewarm-summaries [SELECTOR]` flag on `loomweave analyze` that drives the same `summary(id)` code path against a pre-selected entity set. The infrastructure for it lands in v0.1 (the MCP tool + cache); the orchestration around prewarming lands in v0.2. ### Why module/subsystem summarisation slips to v0.2 @@ -83,9 +83,9 @@ WP6 does NOT ship in the resumed Sprint 2: - Pipeline Phase 4 / 5 / 6 orchestrator (the batched pass). - Module and subsystem prompt templates. - Hierarchical aggregation. -- Per-run cost-ceiling enforced across thousands of leaf calls (the per-query cost-ceiling still applies; the cross-query budget is an operator concern via `clarion.yaml`). +- Per-run cost-ceiling enforced across thousands of leaf calls (the per-query cost-ceiling still applies; the cross-query budget is an operator concern via `loomweave.yaml`). -The `clarion analyze` pipeline in v0.1 produces no summaries. A consult-mode agent or operator calls `summary(entity_id)` via MCP when they want a summary. +The `loomweave analyze` pipeline in v0.1 produces no summaries. A consult-mode agent or operator calls `summary(entity_id)` via MCP when they want a summary. ### Decision 2 — Cache key remains ADR-007's 5-tuple @@ -122,8 +122,8 @@ The v0.2 shape (Phase 4 / 5 / 6 hierarchical pass) is preserved in `system-desig The OpenRouter provider swap resolves provider accounting as an MCP server-session token budget: -- `clarion.yaml` exposes `llm_policy.session_token_ceiling`, defaulting to 1,000,000 - tokens. The budget is process-local and resets when `clarion serve` restarts. +- `loomweave.yaml` exposes `llm_policy.session_token_ceiling`, defaulting to 1,000,000 + tokens. The budget is process-local and resets when `loomweave serve` restarts. - `summary()` and lazy inferred-edge dispatch share the same in-memory `BudgetLedger` with `spent`, `reserved`, and `blocked` state. Cache hits do not reserve or spend budget. @@ -136,7 +136,7 @@ server-session token budget: over the ceiling, that response returns `token-ceiling-exceeded` and the session is blocked for further live LLM dispatch. Provider failures release the reservation. -- Ceiling responses emit `CLA-LLM-TOKEN-CEILING-EXCEEDED` and +- Ceiling responses emit `LMWV-LLM-TOKEN-CEILING-EXCEEDED` and `token_ceiling_exceeded_total: 1` in the MCP response envelope. ## Alternatives Considered @@ -155,9 +155,9 @@ Defer all LLM-bearing work to v0.2. ### Alternative 3 — Eager leaf-tier summarisation only (no module/subsystem) -Pre-compute leaf summaries during `clarion analyze`; defer hierarchical tiers to v0.2. +Pre-compute leaf summaries during `loomweave analyze`; defer hierarchical tiers to v0.2. -**Why rejected**: still hundreds of dollars per `clarion analyze` run for entities that may never be queried. The on-demand path is strictly cheaper at MVP query traffic (agent navigates a small subset of entities per consultation). The "prewarming" path described above lets an operator opt into eager population when they want it; that's a better default than imposing it on every run. +**Why rejected**: still hundreds of dollars per `loomweave analyze` run for entities that may never be queried. The on-demand path is strictly cheaper at MVP query traffic (agent navigates a small subset of entities per consultation). The "prewarming" path described above lets an operator opt into eager population when they want it; that's a better default than imposing it on every run. ### Alternative 4 — Merge summary cache and inferred-edge cache into one shape @@ -169,11 +169,11 @@ Add edge-vs-summary as a cache-key component; share one cache implementation. ### Positive -- **MVP ships with $0 amortised LLM cost.** Operators who never call `summary()` pay nothing for LLM access. Operators who do pay per-query, bounded by their `clarion.yaml` token ceiling and OpenRouter-side billing controls. +- **MVP ships with $0 amortised LLM cost.** Operators who never call `summary()` pay nothing for LLM access. Operators who do pay per-query, bounded by their `loomweave.yaml` token ceiling and OpenRouter-side billing controls. - **WP6 scope shrinks dramatically.** From three pipeline phases plus aggregation prompts plus parallel rate-limit management to: trait + provider + one prompt template + lazy cache population + one MCP tool. Order-of-magnitude reduction in implementation surface. - **Cache shape locked early.** ADR-007's 5-tuple becomes battle-tested under the on-demand path before v0.2 adds the eager prewarming and hierarchical tiers. Cache bugs that would have been discovered in v0.2 under hierarchical load get found in v0.1 under interactive load. - **v0.2 path is additive, not migratory.** Module/subsystem tiers add new `prompt_template_id` values to the same cache. Hierarchical aggregation is new pipeline code that consumes the existing cache. Nothing in v0.1 has to be rewritten or migrated. -- **The v0.1 provider is explicit.** Clarion ships an OpenRouter-specific provider implementation using OpenAI-compatible Chat Completions HTTP. The trait remains in place, and the common wire format keeps future providers or proxies additive rather than a call-site rewrite. +- **The v0.1 provider is explicit.** Loomweave ships an OpenRouter-specific provider implementation using OpenAI-compatible Chat Completions HTTP. The trait remains in place, and the common wire format keeps future providers or proxies additive rather than a call-site rewrite. ### Negative @@ -194,8 +194,8 @@ Add edge-vs-summary as a cache-key component; share one cache implementation. `llm_policy.session_token_ceiling`. Dollar budgets are handled in OpenRouter's operator-facing billing controls; a daily or cross-process aggregate ceiling is deferred beyond v0.1. -- **Cache eviction.** ADR-007 names a TTL backstop but no eviction policy for the lazy-populated case (the eager case naturally bounds cache size to entity count). Decision deferred; initial implementation can run unbounded with manual cache rotation, since v0.1 operators are the same humans who can `rm .clarion/cache/`. -- **Multi-model cache coexistence.** If an operator changes `model_tier` in `clarion.yaml`, the cache key changes and prior entries become unreachable but not garbage-collected. Same TTL discipline applies. Acceptable for v0.1. +- **Cache eviction.** ADR-007 names a TTL backstop but no eviction policy for the lazy-populated case (the eager case naturally bounds cache size to entity count). Decision deferred; initial implementation can run unbounded with manual cache rotation, since v0.1 operators are the same humans who can `rm .loomweave/cache/`. +- **Multi-model cache coexistence.** If an operator changes `model_tier` in `loomweave.yaml`, the cache key changes and prior entries become unreachable but not garbage-collected. Same TTL discipline applies. Acceptable for v0.1. ## Related Decisions diff --git a/docs/clarion/adr/ADR-031-schema-validation-policy.md b/docs/loomweave/adr/ADR-031-schema-validation-policy.md similarity index 95% rename from docs/clarion/adr/ADR-031-schema-validation-policy.md rename to docs/loomweave/adr/ADR-031-schema-validation-policy.md index f278c954..8febd48a 100644 --- a/docs/clarion/adr/ADR-031-schema-validation-policy.md +++ b/docs/loomweave/adr/ADR-031-schema-validation-policy.md @@ -121,7 +121,7 @@ not knowable at schema-creation time. Migration `0001_initial_schema.sql` is edited in place under the ADR-024 in-place edit policy (retirement trigger remains: first -external operator pulling a published Clarion build). The following +external operator pulling a published Loomweave build). The following CHECK clauses are added: | Column | CHECK clause | Source of truth | @@ -146,7 +146,7 @@ The defense-in-depth model is two layers: from typed Rust enums. `enforce_edge_contract` (`writer.rs:411`) is the additional per-kind contract layer for `edges`. This layer prevents bad values from being inserted in normal use; it produces - structured `StorageError::WriterProtocol` errors with `CLA-INFRA-*` + structured `StorageError::WriterProtocol` errors with `LMWV-INFRA-*` codes that surface in `runs.stats.failure_reason`. 2. **Layer B — SQL CHECK constraints (defense).** SQLite enforces the @@ -166,7 +166,7 @@ The defense-in-depth model is two layers: ### Test layer -The schema-apply test suite (`crates/clarion-storage/tests/schema_apply.rs`) +The schema-apply test suite (`crates/loomweave-storage/tests/schema_apply.rs`) already asserts CHECK rejection on `edges.confidence` (test `edges_confidence_column_rejects_unknown_tier` at line 538). This ADR adds parallel rejection tests for each of the four newly CHECK-constrained @@ -175,7 +175,7 @@ is: insert a row with a deliberately invalid value, assert the error message contains `"CHECK constraint failed"`. Adding a new valid value without updating both the CHECK clause and these tests will fail CI. -The writer-actor tests (`crates/clarion-storage/tests/writer_actor.rs`) +The writer-actor tests (`crates/loomweave-storage/tests/writer_actor.rs`) already exercise the Layer-A typed enums on the runs.status side (`RunStatus::Completed`, `RunStatus::Failed`); no change required there. @@ -222,8 +222,8 @@ This ADR does not address: The policy switches from "edit `0001` in place" to "stack `0002_*.sql`" at the same retirement trigger as ADR-024: the first time any external -operator pulls a published Clarion build and produces a -`.clarion/clarion.db`. After that point, vocabulary expansions for any +operator pulls a published Loomweave build and produces a +`.loomweave/loomweave.db`. After that point, vocabulary expansions for any CHECK-constrained column require a stacked migration that rewrites the table (SQLite cannot `ALTER TABLE ... DROP CHECK`; the canonical workaround is the [SQLite "make other kinds of table schema changes" recipe](https://www.sqlite.org/lang_altertable.html#otheralter) @@ -278,7 +278,7 @@ unique in `registered_kinds`, which is fine, but the FK then forbids deleting a `registered_kinds` row while any entity uses it — a startup ordering nightmare when plugins change between runs. The mechanism is also heavier than the threat: ADR-022 already enforces manifest-declared kinds -at the `analyze_file` notification boundary (`CLA-INFRA-PLUGIN-UNDECLARED-KIND` +at the `analyze_file` notification boundary (`LMWV-INFRA-PLUGIN-UNDECLARED-KIND` in the writer-actor and in the host's emission-acceptance check). Adding a data-layer enforcement of a property the protocol layer already enforces is duplicated work that lands in the wrong place. @@ -408,15 +408,15 @@ permanent. - [Skeleton audit](../../implementation/handoffs/2026-05-03-skeleton-audit.md) — F-13 in the Reviewer additions table; the audit framing that motivated this ADR. -- `crates/clarion-storage/migrations/0001_initial_schema.sql:80-81, 134` +- `crates/loomweave-storage/migrations/0001_initial_schema.sql:80-81, 134` — the two existing CHECK precedents this ADR generalises. -- `crates/clarion-storage/src/commands.rs:26-43` — `RunStatus` Rust enum; +- `crates/loomweave-storage/src/commands.rs:26-43` — `RunStatus` Rust enum; the typed Layer-A validator for `runs.status` writes. -- `crates/clarion-storage/src/writer.rs:322` — the `'running'` hand-typed +- `crates/loomweave-storage/src/writer.rs:322` — the `'running'` hand-typed literal at `BeginRun`; the kind of fat-finger the CHECK catches. -- `crates/clarion-storage/src/writer.rs:411` — `enforce_edge_contract`, +- `crates/loomweave-storage/src/writer.rs:411` — `enforce_edge_contract`, the Layer-A validator for `edges` writes. -- `crates/clarion-storage/tests/schema_apply.rs:538` — +- `crates/loomweave-storage/tests/schema_apply.rs:538` — `edges_confidence_column_rejects_unknown_tier`, the test pattern this ADR's tests follow. - Filigree issue `clarion-fbe50aa6e1` — the audit issue this ADR closes. diff --git a/docs/clarion/adr/ADR-032-weighted-components-clustering-fallback.md b/docs/loomweave/adr/ADR-032-weighted-components-clustering-fallback.md similarity index 93% rename from docs/clarion/adr/ADR-032-weighted-components-clustering-fallback.md rename to docs/loomweave/adr/ADR-032-weighted-components-clustering-fallback.md index bf215542..e6b2a865 100644 --- a/docs/clarion/adr/ADR-032-weighted-components-clustering-fallback.md +++ b/docs/loomweave/adr/ADR-032-weighted-components-clustering-fallback.md @@ -7,7 +7,7 @@ ## Summary -Clarion v0.1 keeps Leiden as the default Phase 3 clustering algorithm. The +Loomweave v0.1 keeps Leiden as the default Phase 3 clustering algorithm. The fallback algorithm is named `weighted_components`, not `louvain`, because the implementation does not perform Louvain modularity optimisation. The config surface is: @@ -24,7 +24,7 @@ fallback supplies the partition. `runs.stats.clustering.configured_algorithm` records the requested config value for auditability. When the configured algorithm is `leiden`, the fallback trigger is explicit: -Clarion computes `weighted_components` if Leiden returns zero or one community, +Loomweave computes `weighted_components` if Leiden returns zero or one community, and uses the fallback only when it produces more communities than Leiden. ## Decision diff --git a/docs/clarion/adr/ADR-033-v1.0-distribution.md b/docs/loomweave/adr/ADR-033-v1.0-distribution.md similarity index 74% rename from docs/clarion/adr/ADR-033-v1.0-distribution.md rename to docs/loomweave/adr/ADR-033-v1.0-distribution.md index 657523d5..1b91fc71 100644 --- a/docs/clarion/adr/ADR-033-v1.0-distribution.md +++ b/docs/loomweave/adr/ADR-033-v1.0-distribution.md @@ -3,13 +3,13 @@ **Status**: Accepted **Date**: 2026-05-19 **Deciders**: qacona@gmail.com -**Context**: Clarion v1.0 is the first publishable release; an outside operator must be able to install without `git clone` + `cargo build`. The packaging path was not previously committed. +**Context**: Loomweave v1.0 is the first publishable release; an outside operator must be able to install without `git clone` + `cargo build`. The packaging path was not previously committed. ## Summary -Clarion v1.0 distributes via **GitHub Releases** with pre-built binaries for +Loomweave v1.0 distributes via **GitHub Releases** with pre-built binaries for the Rust core and a Python sdist for the language plugin. Public registries -(crates.io for `clarion-cli`, PyPI for `clarion-plugin-python`) are deferred to +(crates.io for `loomweave-cli`, PyPI for `loomweave-plugin-python`) are deferred to v2.0, after names are reserved and a publish cadence is established. ## Decision @@ -17,17 +17,17 @@ v2.0, after names are reserved and a publish cadence is established. A tag matching `v[0-9]+.*` pushed to `main` triggers `.github/workflows/release.yml`, which: -1. Builds the `clarion` binary on the v1.0 supported matrix: +1. Builds the `loomweave` binary on the v1.0 supported matrix: - `x86_64-unknown-linux-gnu` - `x86_64-apple-darwin` - `aarch64-apple-darwin` Windows is intentionally out of v1.0 scope: the plugin host's resource limits use `setrlimit`, which is Unix-only - (`crates/clarion-core/src/plugin/limits.rs`). + (`crates/loomweave-core/src/plugin/limits.rs`). 2. Builds the Python plugin sdist via `python -m build --sdist plugins/python`. 3. Creates a GitHub Release for the tag, attaching: - - One `clarion-.tar.gz` per platform (binary + LICENSE + README). - - `clarion-plugin-python-.tar.gz` (one sdist, multi-platform). + - One `loomweave-.tar.gz` per platform (binary + LICENSE + README). + - `loomweave-plugin-python-.tar.gz` (one sdist, multi-platform). 4. Auto-generates release notes from `git log` between the previous tag and the new one, filtered to merge commits. @@ -35,11 +35,11 @@ Install instructions (per `README.md` and `docs/operator/getting-started.md`): ```bash # Rust binary — download + extract to ~/.local/bin or equivalent -curl -L https://github.com/tachyon-beep/clarion/releases/download/v1.0.0/clarion-x86_64-unknown-linux-gnu.tar.gz \ - | tar xz -C ~/.local/bin --strip-components=1 clarion-x86_64-unknown-linux-gnu/clarion +curl -L https://github.com/foundryside-dev/loomweave/releases/download/v1.0.0/loomweave-x86_64-unknown-linux-gnu.tar.gz \ + | tar xz -C ~/.local/bin --strip-components=1 loomweave-x86_64-unknown-linux-gnu/loomweave # Python plugin -pipx install https://github.com/tachyon-beep/clarion/releases/download/v1.0.0/clarion-plugin-python-1.0.0.tar.gz +pipx install https://github.com/foundryside-dev/loomweave/releases/download/v1.0.0/loomweave-plugin-python-1.0.0.tar.gz ``` ## Alternatives considered @@ -71,7 +71,7 @@ learn. A future ADR can flip to `cargo-dist` if the matrix grows. - The release workflow is part of the v1.0 surface — broken release builds block the v1.0 publish via WS-D (external-operator smoke test). - crates.io / PyPI publishing remains v2.0 work; until then the install path - carries an explicit `github.com/tachyon-beep` URL. + carries an explicit `github.com/foundryside-dev` URL. ## Related diff --git a/docs/clarion/adr/ADR-034-federation-http-read-api-hardening.md b/docs/loomweave/adr/ADR-034-federation-http-read-api-hardening.md similarity index 67% rename from docs/clarion/adr/ADR-034-federation-http-read-api-hardening.md rename to docs/loomweave/adr/ADR-034-federation-http-read-api-hardening.md index ab6e39cf..6915e20a 100644 --- a/docs/clarion/adr/ADR-034-federation-http-read-api-hardening.md +++ b/docs/loomweave/adr/ADR-034-federation-http-read-api-hardening.md @@ -3,31 +3,31 @@ **Status**: Accepted; HMAC freshness amended by [ADR-042](./ADR-042-hmac-freshness-and-replay-window.md) **Date**: 2026-05-19 **Deciders**: qacona@gmail.com -**Context**: Sprint 3 Loom federation hardening (see [`docs/implementation/sprint-3/2026-05-19-loom-federation-hardening-tasking.md`](../../implementation/sprint-3/2026-05-19-loom-federation-hardening-tasking.md)); extends ADR-014's read-API §"Security Posture" and §"Error Envelope" +**Context**: Sprint 3 Weft federation hardening (see [`docs/implementation/sprint-3/2026-05-19-weft-federation-hardening-tasking.md`](../../implementation/sprint-3/2026-05-19-weft-federation-hardening-tasking.md)); extends ADR-014's read-API §"Security Posture" and §"Error Envelope" **Extends**: [ADR-014](./ADR-014-filigree-registry-backend.md) Security Posture and Error Envelope sections only — the registry-backend protocol decision in ADR-014 §"Decision" remains in force. ## Summary -Sprint 3 hardens Clarion's HTTP read API beyond ADR-014's original posture: protected routes can require Loom component identity via `X-Loom-Component: clarion:` resolved from an operator-named environment variable, a new `POST /api/v1/files/batch` endpoint handles bulk path resolution in a single round trip, a distinct `403 BRIEFING_BLOCKED` response distinguishes blocked entities from "not found" entities without leaking identity, and `GET /api/v1/_capabilities` echoes a stable per-project `instance_id` so siblings can detect endpoint rebinding. These additions extend ADR-014's wire-contract surface without breaking it — `api_version` remains `1`. +Sprint 3 hardens Loomweave's HTTP read API beyond ADR-014's original posture: protected routes can require Weft component identity via `X-Weft-Component: loomweave:` resolved from an operator-named environment variable, a new `POST /api/v1/files/batch` endpoint handles bulk path resolution in a single round trip, a distinct `403 BRIEFING_BLOCKED` response distinguishes blocked entities from "not found" entities without leaking identity, and `GET /api/v1/_capabilities` echoes a stable per-project `instance_id` so siblings can detect endpoint rebinding. These additions extend ADR-014's wire-contract surface without breaking it — `api_version` remains `1`. ## Context ADR-014 §"Security Posture" pinned the HTTP read API as "unauthenticated and loopback-only by default" with non-loopback binds gated by `serve.http.allow_non_loopback: true`. ADR-014 §"Error Envelope" closed the initial `code` enum to `INVALID_PATH`, `PATH_OUTSIDE_PROJECT`, `NOT_FOUND`, `STORAGE_ERROR`, `INTERNAL`. Both were the right posture for the immediate ADR-014 use case (loopback-only Filigree sidecar) but were under-specified for the broader Sprint 3 federation goals: -- Filigree may run on a different host than Clarion in production deployments, and the operator should not have to layer a separate auth proxy for what is a publisher-side concern. -- Filigree's `ClarionRegistry` cold-start hydrates N file records by re-asking Clarion for each path. The single-file `GET` endpoint costs one pooled SQLite connection per call; for a 1000-file warm-up that is 1000 round trips and 1000 pool acquisitions. -- `briefing_blocked` entities (per ADR-013 secret-scan briefing block) are not "not found" — they exist but are policy-blocked. The original `NOT_FOUND` masking conflates "Clarion does not know this file" (registry coverage gap; escalate) with "Clarion knows this file but refuses to expose it" (briefing block; wait for re-scan or fix the secret). Filigree needs to distinguish the two. -- `_capabilities` is the only handshake point Filigree sees before issuing requests. If an operator rebinds the same `bind` to a different Clarion project (different `.clarion/` root, different cache), Filigree has no fingerprint to detect the swap. +- Filigree may run on a different host than Loomweave in production deployments, and the operator should not have to layer a separate auth proxy for what is a publisher-side concern. +- Filigree's `LoomweaveRegistry` cold-start hydrates N file records by re-asking Loomweave for each path. The single-file `GET` endpoint costs one pooled SQLite connection per call; for a 1000-file warm-up that is 1000 round trips and 1000 pool acquisitions. +- `briefing_blocked` entities (per ADR-013 secret-scan briefing block) are not "not found" — they exist but are policy-blocked. The original `NOT_FOUND` masking conflates "Loomweave does not know this file" (registry coverage gap; escalate) with "Loomweave knows this file but refuses to expose it" (briefing block; wait for re-scan or fix the secret). Filigree needs to distinguish the two. +- `_capabilities` is the only handshake point Filigree sees before issuing requests. If an operator rebinds the same `bind` to a different Loomweave project (different `.loomweave/` root, different cache), Filigree has no fingerprint to detect the swap. -Sprint 3 implemented these four hardenings (`1109560`, `acbf465`, `eb6200d`, `2c3311a`) and pinned the resulting wire contract in [`docs/federation/contracts.md`](../../federation/contracts.md). The body of ADR-014, per the ADR immutability rule in [`/home/john/clarion/CLAUDE.md`](../../../CLAUDE.md) §"Editorial conventions", cannot be rewritten in place. This ADR is therefore the authoritative source for the four hardenings; ADR-014 carries the standard `Accepted; partially extended by ADR-034 (Security Posture)` status amendment. +Sprint 3 implemented these four hardenings (`1109560`, `acbf465`, `eb6200d`, `2c3311a`) and pinned the resulting wire contract in [`docs/federation/contracts.md`](../../federation/contracts.md). The body of ADR-014, per the ADR immutability rule in [`/home/john/loomweave/CLAUDE.md`](../../../CLAUDE.md) §"Editorial conventions", cannot be rewritten in place. This ADR is therefore the authoritative source for the four hardenings; ADR-014 carries the standard `Accepted; partially extended by ADR-034 (Security Posture)` status amendment. ## Decision -### 1. Loom component identity on protected routes +### 1. Weft component identity on protected routes -`/api/v1/files`-family endpoints require `X-Loom-Component: clarion:` when Clarion has resolved `serve.http.identity_token_env` at startup. `/api/v1/_capabilities` is **always** unauthenticated so siblings can probe the API surface before they hold a secret. +`/api/v1/files`-family endpoints require `X-Weft-Component: loomweave:` when Loomweave has resolved `serve.http.identity_token_env` at startup. `/api/v1/_capabilities` is **always** unauthenticated so siblings can probe the API surface before they hold a secret. -The HMAC is lowercase hex HMAC-SHA256 over a newline-separated canonical message: request method, path plus query string, and SHA-256 hex of the request body. This signs the request line and body digest without turning the shared secret into a bearer credential on the wire. Clarion preserves the older `serve.http.token_env` bearer-token mode for compatibility when `identity_token_env` is not configured. +The HMAC is lowercase hex HMAC-SHA256 over a newline-separated canonical message: request method, path plus query string, and SHA-256 hex of the request body. This signs the request line and body digest without turning the shared secret into a bearer credential on the wire. Loomweave preserves the older `serve.http.token_env` bearer-token mode for compatibility when `identity_token_env` is not configured. Token-resolution and bind-policy trust matrix, enforced at startup by `HttpReadConfig::validate_auth_trust` before the listener binds: @@ -35,16 +35,16 @@ Token-resolution and bind-policy trust matrix, enforced at startup by `HttpReadC |---|---|---|---| | Loopback | unset | unset | Unauthenticated; allow all requests (matches ADR-014's original posture for the loopback case). | | Loopback | set | any | HMAC required on protected routes; capabilities always allowed. | -| Loopback | configured but env missing | any | **Refuse to start** with `CLA-CONFIG-HTTP-IDENTITY-MISSING`. | +| Loopback | configured but env missing | any | **Refuse to start** with `LMWV-CONFIG-HTTP-IDENTITY-MISSING`. | | Non-loopback | set | any | HMAC required on protected routes. | | Non-loopback | unset | set | Bearer required on protected routes. | -| Non-loopback | unset | unset | **Refuse to start** with `CLA-CONFIG-HTTP-NO-AUTH`. | +| Non-loopback | unset | unset | **Refuse to start** with `LMWV-CONFIG-HTTP-NO-AUTH`. | Non-loopback-without-auth refusal extends ADR-014's `allow_non_loopback` opt-in: there is no longer a "non-loopback unauthenticated" mode. Non-loopback binds require **both** `allow_non_loopback: true` **and** a resolved HMAC identity secret or legacy bearer token; either alone is insufficient. The opt-in remains the gate that admits non-loopback binds at all, but it no longer admits them unauthenticated. Authentication rejection (any of: header absent, wrong scheme/prefix, wrong token or signature, blank token or signature) returns `401 Unauthorized` with the standard error envelope and `code: "UNAUTHENTICATED"`. Secret comparison is constant-time so a wrong-length client cannot distinguish "header absent" from "secret mismatch" via timing. The secret value is never logged; the bind-time log line records `auth=hmac`, `auth=bearer`, or `auth=none`, not the secret itself. -The preferred secret is resolved from an operator-named environment variable; the config field is `serve.http.identity_token_env`. The legacy bearer-token config field is `serve.http.token_env` (default `CLARION_LOOM_TOKEN`, matching Filigree's pinned client default). Secret values never appear in `clarion.yaml`. +The preferred secret is resolved from an operator-named environment variable; the config field is `serve.http.identity_token_env`. The legacy bearer-token config field is `serve.http.token_env` (default `WEFT_TOKEN`, matching Filigree's pinned client default). Secret values never appear in `loomweave.yaml`. ### 2. `POST /api/v1/files/batch` @@ -72,13 +72,13 @@ Response body shape (closed; arrays are disjoint partitions of the input): } ``` -The per-batch cap is **256 queries** (`BATCH_MAX_QUERIES` in [`crates/clarion-cli/src/http_read.rs`](../../../crates/clarion-cli/src/http_read.rs); referenced as `queries.len() > 256` in `contracts.md` §"`POST /api/v1/files/batch`"). The request body is additionally bounded at the transport layer to 16 KiB. A `queries` array that exceeds the cap returns `400 BATCH_TOO_LARGE` (new error code, see §3). The cap is not operator-configurable in v1.0 — it is pinned on the wire so Filigree client splitting logic can compile-in the limit. A future incompatible change to the cap is the trigger for `api_version: 2`, not a per-server override. +The per-batch cap is **256 queries** (`BATCH_MAX_QUERIES` in [`crates/loomweave-cli/src/http_read.rs`](../../../crates/loomweave-cli/src/http_read.rs); referenced as `queries.len() > 256` in `contracts.md` §"`POST /api/v1/files/batch`"). The request body is additionally bounded at the transport layer to 16 KiB. A `queries` array that exceeds the cap returns `400 BATCH_TOO_LARGE` (new error code, see §3). The cap is not operator-configurable in v1.0 — it is pinned on the wire so Filigree client splitting logic can compile-in the limit. A future incompatible change to the cap is the trigger for `api_version: 2`, not a per-server override. Individual-item errors (`INVALID_PATH`, `PATH_OUTSIDE_PROJECT`, `STORAGE_ERROR`, `INTERNAL`) go into the `errors` array; the whole request still returns `200 OK` so partial-success semantics are explicit on the wire. Briefing-blocked items are partitioned to the `briefing_blocked` array; the per-item envelope deliberately does not include `entity_id`, `content_hash`, `canonical_path`, or `language` so callers cannot infer file identity from a block-classified item. ### 3. `BRIEFING_BLOCKED` 403 on single-file `GET` -`GET /api/v1/files?path=` for an entity whose `briefing_blocked` anchor is set (per ADR-013) returns `403 Forbidden` with `code: "BRIEFING_BLOCKED"`. The 403 envelope omits `entity_id`, `content_hash`, `canonical_path`, and `language`. The structural signal that "Clarion knows this file but is refusing to expose it" is therefore *only* the status code and `code` discriminator, not any payload field. +`GET /api/v1/files?path=` for an entity whose `briefing_blocked` anchor is set (per ADR-013) returns `403 Forbidden` with `code: "BRIEFING_BLOCKED"`. The 403 envelope omits `entity_id`, `content_hash`, `canonical_path`, and `language`. The structural signal that "Loomweave knows this file but is refusing to expose it" is therefore *only* the status code and `code` discriminator, not any payload field. ADR-014's original error-code set is extended to: @@ -103,9 +103,9 @@ The set remains closed. Clients must switch on `code`, not on `error` text. } ``` -`instance_id` is a v4 UUID created lazily on first `clarion serve` (via `instance::load_or_create` at [`crates/clarion-cli/src/serve.rs:29`](../../../crates/clarion-cli/src/serve.rs)) and persisted to `.clarion/instance_id`. Subsequent `clarion serve` invocations read the existing value. The file is created with mode `0600` on Unix. The ID is stable for the life of that `.clarion/` directory; deleting `.clarion/instance_id` (or the whole `.clarion/` tree) on the next `clarion serve` produces a fresh UUID, and that is intended — siblings use the change as the trigger to invalidate cached identity bindings. The file is excluded from git per ADR-005's exclusion list for per-machine state. +`instance_id` is a v4 UUID created lazily on first `loomweave serve` (via `instance::load_or_create` at [`crates/loomweave-cli/src/serve.rs:29`](../../../crates/loomweave-cli/src/serve.rs)) and persisted to `.loomweave/instance_id`. Subsequent `loomweave serve` invocations read the existing value. The file is created with mode `0600` on Unix. The ID is stable for the life of that `.loomweave/` directory; deleting `.loomweave/instance_id` (or the whole `.loomweave/` tree) on the next `loomweave serve` produces a fresh UUID, and that is intended — siblings use the change as the trigger to invalidate cached identity bindings. The file is excluded from git per ADR-005's exclusion list for per-machine state. -`instance_id` is **not** the same as a deployment fingerprint, a Filigree project ID, or a Clarion release. It is exactly the identity of one local `.clarion/` directory. +`instance_id` is **not** the same as a deployment fingerprint, a Filigree project ID, or a Loomweave release. It is exactly the identity of one local `.loomweave/` directory. ## Alternatives Considered @@ -113,7 +113,7 @@ The set remains closed. Clients must switch on `code`, not on `error` text. Bearer-only authentication was the first Sprint 3 hardening because it was simple to operate. It is retained for compatibility through `serve.http.token_env`, but -new Loom deployments should prefer `identity_token_env`: the HMAC shape +new Weft deployments should prefer `identity_token_env`: the HMAC shape authenticates the request line and body digest instead of sending the shared secret as the credential on every request. @@ -136,12 +136,12 @@ The Sprint 3 tasking doc originally proposed pinning these decisions into ADR-01 - The federation HTTP read API now has a per-request authentication primitive. ADR-014's "unauthenticated, loopback-only" promise was a deliberate Sprint 1/2 simplification that was load-bearing for the v1.0 federation surface; the gap is now closed. - Filigree's cold-start hydration cost drops from O(N) round trips to O(1) with the batch endpoint. The per-item error partitioning means a single malformed path in a 1000-path batch does not poison the whole request. - `BRIEFING_BLOCKED` is mechanically distinguishable from `NOT_FOUND`. Filigree's escalation logic can branch on the two cases without parsing error text or re-issuing probe requests. -- `instance_id` gives Filigree a stable handle to detect endpoint rebinding. Pre-Sprint-3, a `bind` pointed at a swapped `.clarion/` was indistinguishable from a fresh start; that ambiguity is now resolved on the first capability probe after the swap. +- `instance_id` gives Filigree a stable handle to detect endpoint rebinding. Pre-Sprint-3, a `bind` pointed at a swapped `.loomweave/` was indistinguishable from a fresh start; that ambiguity is now resolved on the first capability probe after the swap. ### Negative -- Operators upgrading from ADR-014's unauthenticated posture to a non-loopback federation deployment must now provide a token at startup. The `CLA-CONFIG-HTTP-NO-AUTH` startup refusal makes this a fail-closed migration, not a silent one, but the operator-facing diff is non-trivial. -- HMAC identity still uses a shared secret; a leak requires rotation across both Clarion and every sibling client. Replay windows, key identifiers, and rotation metadata remain future hardening. +- Operators upgrading from ADR-014's unauthenticated posture to a non-loopback federation deployment must now provide a token at startup. The `LMWV-CONFIG-HTTP-NO-AUTH` startup refusal makes this a fail-closed migration, not a silent one, but the operator-facing diff is non-trivial. +- HMAC identity still uses a shared secret; a leak requires rotation across both Loomweave and every sibling client. Replay windows, key identifiers, and rotation metadata remain future hardening. - The error-code enum is now wider than ADR-014's original. Filigree's `code`-switch logic must handle `BRIEFING_BLOCKED`, `UNAUTHENTICATED`, and `BATCH_TOO_LARGE` in addition to the original five. The federation contract documents this; clients that ignore the new codes will surface them as unhandled errors rather than misinterpret them. ### Neutral @@ -159,10 +159,10 @@ The Sprint 3 tasking doc originally proposed pinning these decisions into ADR-01 ## References - Wire spec: [`docs/federation/contracts.md`](../../federation/contracts.md) -- Sprint 3 tasking: [`docs/implementation/sprint-3/2026-05-19-loom-federation-hardening-tasking.md`](../../implementation/sprint-3/2026-05-19-loom-federation-hardening-tasking.md) +- Sprint 3 tasking: [`docs/implementation/sprint-3/2026-05-19-weft-federation-hardening-tasking.md`](../../implementation/sprint-3/2026-05-19-weft-federation-hardening-tasking.md) - Implementing commits: - `1109560` feat(http_read): return 403 BRIEFING_BLOCKED for blocked entities - `acbf465` feat(http_read): require Authorization: Bearer for /api/v1/files - - C-4 follow-up: prefer `X-Loom-Component: clarion:` when `serve.http.identity_token_env` is configured + - C-4 follow-up: prefer `X-Weft-Component: loomweave:` when `serve.http.identity_token_env` is configured - `eb6200d` feat(http_read): add POST /api/v1/files/batch + document path normalization - `2c3311a` feat(http_read): formatting / fix(instance): UUID generation comments / test(serve) diff --git a/docs/clarion/adr/ADR-035-operational-tuning-discipline.md b/docs/loomweave/adr/ADR-035-operational-tuning-discipline.md similarity index 71% rename from docs/clarion/adr/ADR-035-operational-tuning-discipline.md rename to docs/loomweave/adr/ADR-035-operational-tuning-discipline.md index f80013ba..7b049a64 100644 --- a/docs/clarion/adr/ADR-035-operational-tuning-discipline.md +++ b/docs/loomweave/adr/ADR-035-operational-tuning-discipline.md @@ -8,11 +8,11 @@ ## Summary -Clarion ships with strong **runtime** balancing loops — the path-escape breaker, the crash-loop breaker, the writer-actor's `parent_contains_mismatch` bijection check, the L2 cross-language fixture parity test — and no **design-time** balancing loops. The result is silent drift: hardcoded operational constants accrete without recorded basis, crate-level doc-comments diverge from contents, file LOC grows past the point of legibility, and the artifact that would name the right tuning surface (a config schema, a split plan, a crate boundary) is never written. The five §8 open questions are five surface symptoms of one pattern. +Loomweave ships with strong **runtime** balancing loops — the path-escape breaker, the crash-loop breaker, the writer-actor's `parent_contains_mismatch` bijection check, the L2 cross-language fixture parity test — and no **design-time** balancing loops. The result is silent drift: hardcoded operational constants accrete without recorded basis, crate-level doc-comments diverge from contents, file LOC grows past the point of legibility, and the artifact that would name the right tuning surface (a config schema, a split plan, a crate boundary) is never written. The five §8 open questions are five surface symptoms of one pattern. -This ADR commits the project to a uniform discipline. Every operational constant in Clarion source that gates externally observable behaviour MUST declare four things — **stated basis**, **override surface**, **retune trigger**, **coupling** — in a code comment immediately adjacent to the constant (or, for wire-contract constants whose declaration lives in a sibling document, a cross-reference to that document). The same rule shape extends to **file-LOC budgets** (any source file over 1,500 LOC declares a split-trigger condition in its module doc-comment) and to **crate-boundary budgets** (any crate that takes a dependency widening its trust surface or contradicting its `lib.rs` doc-comment declares the trigger for crate extraction). The discipline is enforced by a lint script in `scripts/` that scans Rust + Python sources for the required declarations and fails CI on undeclared operational constants or oversize files. +This ADR commits the project to a uniform discipline. Every operational constant in Loomweave source that gates externally observable behaviour MUST declare four things — **stated basis**, **override surface**, **retune trigger**, **coupling** — in a code comment immediately adjacent to the constant (or, for wire-contract constants whose declaration lives in a sibling document, a cross-reference to that document). The same rule shape extends to **file-LOC budgets** (any source file over 1,500 LOC declares a split-trigger condition in its module doc-comment) and to **crate-boundary budgets** (any crate that takes a dependency widening its trust surface or contradicting its `lib.rs` doc-comment declares the trigger for crate extraction). The discipline is enforced by a lint script in `scripts/` that scans Rust + Python sources for the required declarations and fails CI on undeclared operational constants or oversize files. -This is a level-5 (rules) Meadows intervention — not a level-12 (parameters) one. A `clarion.yaml` config file alone is rejected as insufficient: a config file without the rule that gates how constants graduate from hardcoded → tunable → deprecated would, in the systems thinker's words, "decay back to hardcoded constants by the next sprint — that is exactly how Clarion got here." +This is a level-5 (rules) Meadows intervention — not a level-12 (parameters) one. A `loomweave.yaml` config file alone is rejected as insufficient: a config file without the rule that gates how constants graduate from hardcoded → tunable → deprecated would, in the systems thinker's words, "decay back to hardcoded constants by the next sprint — that is exactly how Loomweave got here." ## Context @@ -22,7 +22,7 @@ The 2026-05-22 architecture analysis (`docs/arch-analysis-2026-05-22-1924/04-fin 1. **Why `MAX_FILES_PER_PYRIGHT_SESSION = 25`?** Empirical? Conservative bound on Pyright memory growth? Not knowing makes future tuning a guess. 2. **What is the post-1.0 plan for the four monolith files?** Each has a natural refactor split. Are these on the roadmap, or is the policy "no split until the file actively impedes a change"? -3. **Will `clarion-llm` become a crate?** The `llm_provider.rs` placement in `clarion-core` is the largest single argument against the `lib.rs:1` doc-comment. +3. **Will `loomweave-llm` become a crate?** The `llm_provider.rs` placement in `loomweave-core` is the largest single argument against the `lib.rs:1` doc-comment. 4. **What is the architect's stance on `application_id` / `user_version`?** Trivial to add; non-trivial to add retroactively once installed DBs exist in the wild. 5. **Operational tuning roadmap.** Eleven hardcoded limits, plus the 25-file restart, plus the 256/50 batch-cadence constants. WP6 is named in code comments — what is its current status? @@ -34,8 +34,8 @@ Five SME reports (archived under `docs/archive/working-notes/arch-analysis-2026- The archetype is Meadows' **Drift to Low Performance**: each individual deferral is locally rational ("ship 1.0; tune later"; "don't split `host.rs` mid-sprint"; "PRAGMAs are post-1.0") but there is no countervailing signal pushing the other way. Two literal tells in the codebase make the diagnosis concrete: -- **`crates/clarion-cli/src/analyze.rs:65`** carries `#[allow(clippy::too_many_lines)]` — the standard-lowering act made literal in code. Two more sites at lines 650 and 1190 carry the same allow. -- **`crates/clarion-core/src/plugin/breaker.rs:7`** says: *"Sprint 1 hard-codes the threshold and window per UQ-WP2-10; the config surface (`clarion.yaml:plugin_limits.crash_*`) lands in WP6."* The placeholder where a discipline should be. +- **`crates/loomweave-cli/src/analyze.rs:65`** carries `#[allow(clippy::too_many_lines)]` — the standard-lowering act made literal in code. Two more sites at lines 650 and 1190 carry the same allow. +- **`crates/loomweave-core/src/plugin/breaker.rs:7`** says: *"Sprint 1 hard-codes the threshold and window per UQ-WP2-10; the config surface (`loomweave.yaml:plugin_limits.crash_*`) lands in WP6."* The placeholder where a discipline should be. ADR-030 narrowed WP6's 1.0 scope to the on-demand `summary(id)` MCP tool. The operator-tunables work the `breaker.rs:7` comment references **was not folded into the narrowed WP6 scope and is currently un-homed** (solution architect's reading: `answer-solution-architect.md` §5). ADR-021 §4 names four of the eleven constants as config keys (`plugin_limits.max_frame_bytes`, `plugin_limits.max_records_per_run`, `plugin_limits.max_rss_mib`, the manifest-declared `expected_max_rss_mb`) — those are *promised by an Accepted ADR and not implemented*, with no governing rule about how the other seven graduate from hardcoded to tunable. @@ -44,29 +44,29 @@ ADR-030 narrowed WP6's 1.0 scope to the on-demand `summary(id)` MCP tool. The op The five SME reports each examined a different facet of the same pattern: - **Solution architect** (`answer-solution-architect.md`): triangulates the WP6 home from ADR-021 §4 + ADR-030 + the 2026-04-19 WP2 handoff doc; recommends shipping 1.0 with limits hardcoded and landing the config surface in WP6 post-1.0 *as one ADR-021-aligned change rather than dripping per-constant overrides*. The "one ADR-aligned change" framing is what this ADR operationalises. -- **Systems thinker** (`answer-systems-thinker.md`): identifies Level 5 (rules) as the correct leverage point and rejects the level-12 (parameters) intervention of "just add `clarion.yaml`" — the rule about how/when constants graduate is the discipline, not the constants themselves. Names the "drift to low performance" archetype. +- **Systems thinker** (`answer-systems-thinker.md`): identifies Level 5 (rules) as the correct leverage point and rejects the level-12 (parameters) intervention of "just add `loomweave.yaml`" — the rule about how/when constants graduate is the discipline, not the constants themselves. Names the "drift to low performance" archetype. - **Python engineer** (`answer-python-engineer.md`): classifies the Python-side constants into wire-contract-pinned (must track Rust counterparts; `MAX_CONTENT_LENGTH = 8 MiB` in `server.py:48` mirrors `ContentLengthCeiling::DEFAULT`; `MAX_UNRESOLVED_CALLEE_EXPR_BYTES = 512` in `pyright_session.py:43` mirrors a same-named Rust constant; `STDERR_TAIL_LIMIT = 65536` in `pyright_session.py:49` mirrors `STDERR_TAIL_BYTES = 64 KiB` in `host.rs`) versus operational tunables (six Pyright-session constants). **None carry a comment naming the Rust counterpart.** This forced the fourth declaration axis — *coupling* — into this ADR's rule shape: without it, a wire-pinned constant and a freely-tunable one get the same declaration form, which obscures the more dangerous case. - **Quality engineer** (`answer-quality-engineer.md`): enumerates per-constant test coverage (twelve constants — eight Tested, three Weak, one Untested, one Partially tested). The Weak/Untested cluster is `DEFAULT_MAX_RSS_MIB`/`DEFAULT_MAX_NOFILE`/`DEFAULT_MAX_NPROC` — security-enforcement constants whose behavioural coverage is "does not panic." A value change here has no regression net. The discipline this ADR establishes interlocks with that gap: a constant whose retune trigger is named is a constant whose regression test is also nameable. - **Security engineer** (`answer-security-engineer.md`): names Q5 as "the question with the most teeth" from a STRIDE-D + STRIDE-E perspective. The 11+ values include the entity cap (500k), Content-Length ceiling (8 MiB), path-escape breaker threshold (10/60s), `RLIMIT_AS` (2 GiB), `RLIMIT_NOFILE` (256), `RLIMIT_NPROC` (32), HTTP body limit (16 KiB), concurrency limit (64), request timeout (10s), batch maxima (256/1000), and the Pyright restart. Recompile-to-tune is itself a security posture stance — an operator under active adversarial-plugin pressure cannot tighten the breaker threshold without a rebuild. The recommendation: *at v1.0 these are deliberately frozen so the security policy is uniform across deployments; post-1.0, the path-escape breaker threshold, the entity cap, and the `RLIMIT_AS` ceiling should become operator-tunable with hard floors enforced at config-load time.* That stance survives intact in this ADR's "internal, no override" allowed state. ### What this ADR is not -This ADR is **not** a tracking surface in Filigree, an entity-association registry, or a derived-metric dashboard. It is a source-comment + lint-script discipline, period. ADR-029's entity-association binding is a different mechanism for a different problem; the two do not overlap. This ADR also does not invent a `clarion.yaml` field set — ADR-021 §4 already names four of the eleven config keys, and the per-constant override-surface placement is the matter of subsequent WPs (post-1.0 WP6 by the solution architect's recommendation). What this ADR commits is the **rule** that gates whether a constant is declared, where its override surface lives, and what would prompt its retune. +This ADR is **not** a tracking surface in Filigree, an entity-association registry, or a derived-metric dashboard. It is a source-comment + lint-script discipline, period. ADR-029's entity-association binding is a different mechanism for a different problem; the two do not overlap. This ADR also does not invent a `loomweave.yaml` field set — ADR-021 §4 already names four of the eleven config keys, and the per-constant override-surface placement is the matter of subsequent WPs (post-1.0 WP6 by the solution architect's recommendation). What this ADR commits is the **rule** that gates whether a constant is declared, where its override surface lives, and what would prompt its retune. ## Decision ### 1. The four-axis declaration rule -Every operational constant in Clarion source that gates externally observable behaviour MUST declare four things, either in a code comment immediately adjacent to the constant or in a sibling doc cross-reference if the constant's authoritative declaration lives elsewhere: +Every operational constant in Loomweave source that gates externally observable behaviour MUST declare four things, either in a code comment immediately adjacent to the constant or in a sibling doc cross-reference if the constant's authoritative declaration lives elsewhere: 1. **Stated basis** — the empirical or design rationale for the current value. Acceptable values include "empirical placeholder, see retune trigger" if the value is currently unmeasured; the placeholder string is itself a basis statement and the retune trigger is what discharges it. 2. **Override surface** — where the value can be tuned. The closed enum is: - - `clarion.yaml:` — operator-tunable via the project config file. + - `loomweave.yaml:` — operator-tunable via the project config file. - `env:` — operator-tunable via environment variable. - `plugin.toml:` — plugin-author-tunable per ADR-021's plugin-authority split. - `recompile` — internal; not exposed. - `wire:` — pinned on the wire by a wire-contract document; tunable only via an incompatible-version bump in that document. -3. **Retune trigger** — the observable condition that should prompt re-evaluation. The trigger must be expressible as either a metric threshold (e.g., "Pyright RSS exceeds 1.5 GiB before file 25 on a corpus sized at the elspeth scale") or a finding subcode (e.g., "any `CLA-INFRA-PLUGIN-OOM-KILLED` finding observed in production runs"). "If something feels wrong" does not satisfy the rule. +3. **Retune trigger** — the observable condition that should prompt re-evaluation. The trigger must be expressible as either a metric threshold (e.g., "Pyright RSS exceeds 1.5 GiB before file 25 on a corpus sized at the elspeth scale") or a finding subcode (e.g., "any `LMWV-INFRA-PLUGIN-OOM-KILLED` finding observed in production runs"). "If something feels wrong" does not satisfy the rule. 4. **Coupling** — the constant's relationship to other declared values. The closed enum is: - `independent` — standalone; tunable without affecting any sibling constant. - `wire-paired-with:` — must match a same-shape constant on the other side of a wire contract. A change requires updating both sides in lockstep. @@ -83,7 +83,7 @@ For the Rust workspace, the canonical adjacent-comment shape is: /// Operational constant. /// /// Basis: . -/// Override: . +/// Override: . /// Retune: . /// Coupling: | policy-paired-with: | floor-of:>. pub const MAX_EXAMPLE_BYTES: usize = 8 * 1024; @@ -95,7 +95,7 @@ For the Python plugin, equivalent shape using a module-level `#:` comment block: #: Operational constant. #: #: Basis: . -#: Override: . +#: Override: . #: Retune: . #: Coupling: | policy-paired-with: | floor-of:>. MAX_EXAMPLE_BYTES = 8 * 1024 @@ -103,9 +103,9 @@ MAX_EXAMPLE_BYTES = 8 * 1024 The lint script (see §5) parses the four named tags exactly as written. -### 2. The eleven operational constants in `clarion-core` +### 2. The eleven operational constants in `loomweave-core` -The 2026-05-22 architecture catalog (`docs/arch-analysis-2026-05-22-1924/02-subsystem-catalog.md` §1 "Concerns") enumerated eleven hardcoded limit constants across `clarion-core`: +The 2026-05-22 architecture catalog (`docs/arch-analysis-2026-05-22-1924/02-subsystem-catalog.md` §1 "Concerns") enumerated eleven hardcoded limit constants across `loomweave-core`: ``` MAX_PROTOCOL_ERROR_FIELD_BYTES (4 KiB, protocol.rs) @@ -137,14 +137,14 @@ MAX_UNRESOLVED_CALLEE_EXPR_BYTES (512, pyright_session.py:43, wire-paired-wit STDERR_TAIL_LIMIT (65536, pyright_session.py:49, wire-paired-with STDERR_TAIL_BYTES) ``` -Plus the writer-actor cadence constants in `crates/clarion-storage/src/writer.rs`: +Plus the writer-actor cadence constants in `crates/loomweave-storage/src/writer.rs`: ``` DEFAULT_BATCH_SIZE (50, writer.rs:38) DEFAULT_CHANNEL_CAPACITY (256, writer.rs:35) ``` -And the crash-loop breaker constants in `crates/clarion-core/src/plugin/breaker.rs`: +And the crash-loop breaker constants in `crates/loomweave-core/src/plugin/breaker.rs`: ``` CRASH_LOOP_THRESHOLD (rolling-window count) @@ -155,11 +155,11 @@ These are the constants the lint script will fail CI on if undeclared after the ### 3. The 25-file Pyright restart constant — instance application -`MAX_FILES_PER_PYRIGHT_SESSION = 25` (`plugins/python/src/clarion_plugin_python/server.py:49`) is the canonical worked example. Per `answer-python-engineer.md` and `answer-solution-architect.md`, the value was introduced in commit `68b719c` ("Bound Pyright dogfood analysis", 2026-05-20) with no commit-message rationale and no in-file comment. It is an empirical placeholder. The four-axis declaration for it, after retrofit, must be: +`MAX_FILES_PER_PYRIGHT_SESSION = 25` (`plugins/python/src/loomweave_plugin_python/server.py:49`) is the canonical worked example. Per `answer-python-engineer.md` and `answer-solution-architect.md`, the value was introduced in commit `68b719c` ("Bound Pyright dogfood analysis", 2026-05-20) with no commit-message rationale and no in-file comment. It is an empirical placeholder. The four-axis declaration for it, after retrofit, must be: - **Basis**: empirical placeholder; bound chosen during dogfood analysis to cap observed Pyright RSS growth across `textDocument/didOpen` cycles; not yet validated against a per-file RSS delta curve. - **Override**: `plugin.toml:pyright.files_per_session` (plugin-author-tunable per ADR-021's plugin-authority split — the Pyright session recycle is plugin-owned policy, not core-side). -- **Retune**: any sampled Pyright subprocess RSS curve on the elspeth-scale corpus showing the inflection point is below 25 (recycle is wasted re-init cost) or above 25 (`CLA-INFRA-PLUGIN-OOM-KILLED` risk inside the 25-file window). +- **Retune**: any sampled Pyright subprocess RSS curve on the elspeth-scale corpus showing the inflection point is below 25 (recycle is wasted re-init cost) or above 25 (`LMWV-INFRA-PLUGIN-OOM-KILLED` risk inside the 25-file window). - **Coupling**: `policy-paired-with:MAX_PYRIGHT_RESTARTS_PER_RUN` — server-side recycling at 25 files and session-side restart caps interact: the Python engineer documented a concrete failure mode where `_disabled` and `_restart_count` are instance-scoped on `PyrightSession` and reset at every 25-file boundary, breaking the "per run" intent of the restart cap. That interaction is a coupling fact the rule forces to be visible. The policy-paired-with coupling annotation interlocks with the Python engineer's `answer-python-engineer.md` "Failure mode A": once both constants declare the pairing, the next reviewer asking "is this safe to change in isolation?" reads the coupling and finds the answer in the source. @@ -182,10 +182,10 @@ The 1,500 LOC threshold is the floor. Files **already** over budget at this ADR' | File | Catalog LOC (2026-05-22) | LOC at ADR authoring | Split-trigger (per `answer-solution-architect.md` §2) | |---|---|---|---| -| `crates/clarion-mcp/src/lib.rs` | 4,703 | 3,449 | Adding a 20th MCP tool, or any concurrent-tool-execution work. | -| `crates/clarion-core/src/plugin/host.rs` | 2,935 | 2,935 | Adding a fifth enforcement layer or changing the four-stage pipeline ordering. | -| `crates/clarion-cli/src/analyze.rs` | 2,549 | 2,427 | A 14th phase added to `run_with_options`'s current 13-phase linearisation. | -| `crates/clarion-core/src/llm_provider.rs` | 2,467 | 2,467 | See ADR-035 §6 and `answer-solution-architect.md` §3 — this file's split is not "within crate" but "extract to `clarion-llm`." | +| `crates/loomweave-mcp/src/lib.rs` | 4,703 | 3,449 | Adding a 20th MCP tool, or any concurrent-tool-execution work. | +| `crates/loomweave-core/src/plugin/host.rs` | 2,935 | 2,935 | Adding a fifth enforcement layer or changing the four-stage pipeline ordering. | +| `crates/loomweave-cli/src/analyze.rs` | 2,549 | 2,427 | A 14th phase added to `run_with_options`'s current 13-phase linearisation. | +| `crates/loomweave-core/src/llm_provider.rs` | 2,467 | 2,467 | See ADR-035 §6 and `answer-solution-architect.md` §3 — this file's split is not "within crate" but "extract to `loomweave-llm`." | The triggers above are recommendations from `answer-solution-architect.md` §2 and become the declared triggers in each file's module doc-comment via the grace-period retrofit. Each file's owner may revise its declared trigger in a later commit; the rule binds *declaration*, not *which specific trigger is declared*. @@ -203,9 +203,9 @@ Any crate that takes a dependency widening its trust surface or contradicting it //! Currently in-scope but extraction-candidate: . ``` -The current cited contradiction (per `answer-solution-architect.md` §3, `answer-security-engineer.md` Q3) is `clarion-core/src/lib.rs:1`'s doc-comment versus `clarion-core/src/llm_provider.rs`'s content. The boundary statement says "domain types, identifiers, and provider traits"; the content includes the OpenRouter `reqwest` HTTP transport and two CLI-subprocess providers — neither domain types, nor identifiers, nor trait definitions. **`detailed-design.md:1745` already names `clarion-llm` as one of the intended workspaces.** That intent + the security argument (an outbound-HTTP-stack CVE inside the plugin-supervisor crate widens the supervisor's trust surface) supplies the extraction trigger: *the `clarion-llm` extraction MUST land before any new LLM provider is added or before any change to `reqwest` / `rustls` / `hyper` that introduces a new transitive trust dependency.* +The current cited contradiction (per `answer-solution-architect.md` §3, `answer-security-engineer.md` Q3) is `loomweave-core/src/lib.rs:1`'s doc-comment versus `loomweave-core/src/llm_provider.rs`'s content. The boundary statement says "domain types, identifiers, and provider traits"; the content includes the OpenRouter `reqwest` HTTP transport and two CLI-subprocess providers — neither domain types, nor identifiers, nor trait definitions. **`detailed-design.md:1745` already names `loomweave-llm` as one of the intended workspaces.** That intent + the security argument (an outbound-HTTP-stack CVE inside the plugin-supervisor crate widens the supervisor's trust surface) supplies the extraction trigger: *the `loomweave-llm` extraction MUST land before any new LLM provider is added or before any change to `reqwest` / `rustls` / `hyper` that introduces a new transitive trust dependency.* -`clarion-core/src/lib.rs` carries this declaration as part of the 1.1 grace-period retrofit. The same rule applies prospectively: a new crate whose `lib.rs` doc-comment is contradicted by its contents must either fix the contradiction or declare an extraction trigger. +`loomweave-core/src/lib.rs` carries this declaration as part of the 1.1 grace-period retrofit. The same rule applies prospectively: a new crate whose `lib.rs` doc-comment is contradicted by its contents must either fix the contradiction or declare an extraction trigger. ### 6. Lint script and CI gate @@ -216,50 +216,50 @@ A lint script lives at `scripts/operational-tuning-lint.{rs,py}` (the implementa 3. For each candidate, verifies that an adjacent `Basis: … / Override: … / Retune: … / Coupling: …` declaration is present and that `Override` and `Coupling` values come from the closed enums in §1. 4. For each `.rs` file over 1,500 LOC, verifies that the module doc-comment contains an `## LOC budget` section with `Current LOC`, `Split trigger`, `Rationale for current state` lines. 5. For each `lib.rs` whose doc-comment contradicts its content (heuristic: presence of `reqwest` / `tokio::process` / network crates not named in the boundary statement), verifies that a `Crate-boundary budget` section is present. -6. Emits findings in the same JSON shape Clarion's other tooling uses (subcode prefix `CLA-DISC-TUNING-*`), one per undeclared constant or undeclared oversize file. +6. Emits findings in the same JSON shape Loomweave's other tooling uses (subcode prefix `LMWV-DISC-TUNING-*`), one per undeclared constant or undeclared oversize file. The script is wired into CI as a non-blocking warning until the 1.1 release. **At the 1.1 release the gate flips from warning to failure** — any undeclared operational constant, oversize file, or contradicted crate boundary fails the CI build. The flip date is the trigger for landing the retrofits enumerated in §2, §4, and §5. -The three `#[allow(clippy::too_many_lines)]` sites in `crates/clarion-cli/src/analyze.rs` (lines 65, 650, 1190) MUST be either re-enabled (the underlying functions split) or replaced with a documented `// allow: ADR-035 §4 — declared split-trigger: ` comment before the 1.1 release. The clippy threshold itself (`too-many-lines-threshold = 120` per ADR-023's `clippy.toml`) remains the baseline; an `#[allow]` without an ADR-035 reference fails the lint script regardless of whether `cargo clippy` itself passes. +The three `#[allow(clippy::too_many_lines)]` sites in `crates/loomweave-cli/src/analyze.rs` (lines 65, 650, 1190) MUST be either re-enabled (the underlying functions split) or replaced with a documented `// allow: ADR-035 §4 — declared split-trigger: ` comment before the 1.1 release. The clippy threshold itself (`too-many-lines-threshold = 120` per ADR-023's `clippy.toml`) remains the baseline; an `#[allow]` without an ADR-035 reference fails the lint script regardless of whether `cargo clippy` itself passes. ### 7. Constant-graduation lifecycle A constant's life under the rule has three states: 1. **Hardcoded with declaration.** The constant is in source, has the four-axis declaration, and `Override = recompile`. This is the steady state for constants the operator should not touch — security-uniformity constants per `answer-security-engineer.md` Q5, wire-contract constants whose change is an `api_version` bump, and constants whose retune trigger has not yet fired. -2. **Tunable.** The constant has `Override = clarion.yaml:` (or `env:` or `plugin.toml:`) and the config-loader actually reads the value. Hard floors enforced at config-load time per ADR-021 §2b are part of this state — the operator can raise the value but not lower it past the floor. +2. **Tunable.** The constant has `Override = loomweave.yaml:` (or `env:` or `plugin.toml:`) and the config-loader actually reads the value. Hard floors enforced at config-load time per ADR-021 §2b are part of this state — the operator can raise the value but not lower it past the floor. 3. **Deprecated.** The constant's basis is replaced by a superseding constant or rule; the declaration carries a `Deprecated: see ` line; the constant is removed at the next major version bump. -Graduating a constant from state 1 to state 2 is a normal commit; graduating from state 2 to state 3 requires either an ADR or a documented field-deprecation note in `docs/clarion/1.0/detailed-design.md`. Demoting from state 2 back to state 1 — removing an operator surface — requires an ADR. This asymmetry codifies the "ratchet" the systems thinker named. +Graduating a constant from state 1 to state 2 is a normal commit; graduating from state 2 to state 3 requires either an ADR or a documented field-deprecation note in `docs/loomweave/1.0/detailed-design.md`. Demoting from state 2 back to state 1 — removing an operator surface — requires an ADR. This asymmetry codifies the "ratchet" the systems thinker named. ### 8. What is explicitly out of scope for this ADR - **The wire-pinned batch cap of 256 queries** in `POST /api/v1/files/batch` is pinned by ADR-034 §3 with `Override = wire:contracts.md#batch-cap` semantics. ADR-035's rule applies (the constant must carry a four-axis declaration), but the override surface is not operator-tunable; a change is an `api_version: 2` event by ADR-034's existing rule. -- **`application_id` and `user_version` PRAGMAs** for SQLite are tracked as filigree issue `clarion-f2a984fd6d` per `answer-solution-architect.md` §4 and `answer-quality-engineer.md` Q4. ADR-035's "schema-identity" instance of the rule applies (the PRAGMA value MUST carry a `Basis: identifies the file as Clarion's per ADR-035` declaration), but the implementation lands as the filigree issue's deliverable, not this ADR's. -- **Specific config-schema design** for `clarion.yaml` post-1.0 belongs in WP6 per `answer-solution-architect.md` §5. This ADR commits the rule about how constants graduate; the specific YAML key shape is the matter of the work package that implements the graduation. +- **`application_id` and `user_version` PRAGMAs** for SQLite are tracked as filigree issue `clarion-f2a984fd6d` per `answer-solution-architect.md` §4 and `answer-quality-engineer.md` Q4. ADR-035's "schema-identity" instance of the rule applies (the PRAGMA value MUST carry a `Basis: identifies the file as Loomweave's per ADR-035` declaration), but the implementation lands as the filigree issue's deliverable, not this ADR's. +- **Specific config-schema design** for `loomweave.yaml` post-1.0 belongs in WP6 per `answer-solution-architect.md` §5. This ADR commits the rule about how constants graduate; the specific YAML key shape is the matter of the work package that implements the graduation. ## Consequences ### Positive -- The five §8 open questions collapse to one durable artifact. Q1 (the 25-file constant), Q4 (PRAGMA identity), and Q5 (eleven hardcoded limits) each gain a recorded basis and a tuning surface; Q2 (four monoliths) and Q3 (`clarion-llm` extraction) become budgeted properties with named triggers rather than aesthetic preferences competing with sprint load. +- The five §8 open questions collapse to one durable artifact. Q1 (the 25-file constant), Q4 (PRAGMA identity), and Q5 (eleven hardcoded limits) each gain a recorded basis and a tuning surface; Q2 (four monoliths) and Q3 (`loomweave-llm` extraction) become budgeted properties with named triggers rather than aesthetic preferences competing with sprint load. - The runtime balancing loops already in the project (path-escape breaker, crash-loop breaker, parent-contains bijection, L2 parity fixture) gain a design-time peer. Per the systems thinker, "the runtime has back-pressure; the architecture itself does not." This ADR is the missing back-pressure at the design-time layer. -- The constant-coupling axis surfaces dangerous interactions that today are invisible. Once `MAX_FILES_PER_PYRIGHT_SESSION` and `MAX_PYRIGHT_RESTARTS_PER_RUN` both carry `policy-paired-with:`, the Python engineer's "Failure mode A" (the 25-file boundary silently resetting the per-run restart cap) becomes a fact a reviewer reads in the source, not a defect a future SME has to rediscover. Similarly, the three wire-paired-with Python ↔ Rust constants gain the `# NOTE: must match clarion-core/…` comment the Python engineer flagged as missing. +- The constant-coupling axis surfaces dangerous interactions that today are invisible. Once `MAX_FILES_PER_PYRIGHT_SESSION` and `MAX_PYRIGHT_RESTARTS_PER_RUN` both carry `policy-paired-with:`, the Python engineer's "Failure mode A" (the 25-file boundary silently resetting the per-run restart cap) becomes a fact a reviewer reads in the source, not a defect a future SME has to rediscover. Similarly, the three wire-paired-with Python ↔ Rust constants gain the `# NOTE: must match loomweave-core/…` comment the Python engineer flagged as missing. - The lint script is the enforcement mechanism that prevents this ADR from being aspirational. A rule the project does not enforce is, per the same drift-to-low-performance archetype, a rule the project does not have. The CI gate is the countervailing signal. - ADR-021's "configurable" claim becomes a substantive promise on a known timeline. Today, "operator can tune" is aspirational (per `02-subsystem-catalog.md` §1); under this ADR, every constant that says it is tunable has a config-load-time floor and a retune trigger. Constants that say they are not tunable carry that as a deliberate stance (security-uniformity per `answer-security-engineer.md` Q5), not a deferral. -- Cross-product trust-surface arguments become first-class. The `clarion-llm` extraction (per `answer-security-engineer.md` Q3) is a defense-in-depth win once it lands: an outbound-HTTP-stack CVE no longer reaches the plugin-supervisor crate. The crate-boundary budget rule (§5) gives that extraction a declared trigger rather than a vague intention in `detailed-design.md`. +- Cross-product trust-surface arguments become first-class. The `loomweave-llm` extraction (per `answer-security-engineer.md` Q3) is a defense-in-depth win once it lands: an outbound-HTTP-stack CVE no longer reaches the plugin-supervisor crate. The crate-boundary budget rule (§5) gives that extraction a declared trigger rather than a vague intention in `detailed-design.md`. ### Negative -- Source comment volume increases. Every operational constant gains 4–5 lines of comment. For the twelve constants in `clarion-core` plus the nine in the Python plugin plus the two writer-actor cadence constants plus the two breaker constants, the retrofit is on the order of 100 lines of comment across the workspace — bounded but non-trivial. +- Source comment volume increases. Every operational constant gains 4–5 lines of comment. For the twelve constants in `loomweave-core` plus the nine in the Python plugin plus the two writer-actor cadence constants plus the two breaker constants, the retrofit is on the order of 100 lines of comment across the workspace — bounded but non-trivial. - The lint script is one more piece of in-repo tooling to maintain. ADR-023's existing five CI gates become six (fmt, clippy, nextest, doc, deny, **tuning-lint**). The script must keep pace with new constant-naming patterns (e.g., a future `MAX_CALLEE_DEPTH` that doesn't match the current `MAX_*_BYTES` heuristic). Mitigation: the script's pattern set is in source, reviewable, and one PR away from a fix. - The four-axis declaration imposes authoring friction on new constants. A contributor adding a new `MAX_FOO` must, before merging, identify the constant's basis, decide its override surface, name its retune trigger, and trace its coupling. This is the intended shape — the friction is the rule — but it raises the per-commit cost of adding limits compared to today. - The 1,500-LOC budget threshold is a judgement call. Set lower, it would force more files to declare triggers (potentially valuable but noisier); set higher, it would let `host.rs` and `analyze.rs` evade declaration. 1,500 LOC is chosen as the floor where a file becomes hard to read end-to-end in one sitting; it is not derived from a study. Mitigation: this threshold is itself an operational constant under this ADR's rule, and its retune trigger is "any file declares a trigger and the trigger is consistently 'never'" — a sign the threshold is too lax. -- The `clarion-llm` extraction trigger commits a future split that may bring its own churn. Per `answer-solution-architect.md` §3, the LLM surface is at its minimum scope right now (post-ADR-030 narrowing of WP6); the split is cheap today and expensive later. The risk is that "ship the v1.0 tag" pressure interacts with "land the crate extraction" and either delays the tag or rushes the split. Mitigation: the trigger names the condition (any new LLM provider, any change to the network-stack transitive deps), so the split is reactive to a forcing function rather than scheduled. +- The `loomweave-llm` extraction trigger commits a future split that may bring its own churn. Per `answer-solution-architect.md` §3, the LLM surface is at its minimum scope right now (post-ADR-030 narrowing of WP6); the split is cheap today and expensive later. The risk is that "ship the v1.0 tag" pressure interacts with "land the crate extraction" and either delays the tag or rushes the split. Mitigation: the trigger names the condition (any new LLM provider, any change to the network-stack transitive deps), so the split is reactive to a forcing function rather than scheduled. ### Neutral -- This is a level-5 (rules) Meadows intervention. Leverage-point hierarchy: level 12 is "parameters" (the constants themselves), level 6 is "information flows" (the four-axis declaration is one), level 5 is "rules" (this ADR). A level-12 intervention — just adding `clarion.yaml` keys without the rule — would, per `answer-systems-thinker.md`, decay back to hardcoded constants by the next sprint. A level-6 intervention — emitting metrics on every constant's observable behaviour — would over-instrument before knowing which constants matter. The level-5 rule is the floor: it forces declaration without forcing exposure or instrumentation; the exposure and instrumentation become subsequent commits gated by declared retune triggers. +- This is a level-5 (rules) Meadows intervention. Leverage-point hierarchy: level 12 is "parameters" (the constants themselves), level 6 is "information flows" (the four-axis declaration is one), level 5 is "rules" (this ADR). A level-12 intervention — just adding `loomweave.yaml` keys without the rule — would, per `answer-systems-thinker.md`, decay back to hardcoded constants by the next sprint. A level-6 intervention — emitting metrics on every constant's observable behaviour — would over-instrument before knowing which constants matter. The level-5 rule is the floor: it forces declaration without forcing exposure or instrumentation; the exposure and instrumentation become subsequent commits gated by declared retune triggers. - The "internal, no override" state (state 1 in §7) is an allowed terminal state. A constant whose basis is "uniform security policy across deployments per ADR-035 §3 stance" and whose override surface is `recompile` is fully compliant with the rule. The discipline is *declaration*, not *exposure*; the security engineer's concern that exposing all 11 limits as operator surface creates a support burden (`answer-security-engineer.md` Q5) is preserved by allowing this state. - ADR-029's entity-association binding is a different mechanism for a different problem. ADR-029 binds Filigree issues to source entities; ADR-035 imposes a source-comment discipline on operational constants. The two mechanisms can be combined (an unresolved retune trigger could be filed as a filigree issue and bound to the constant's entity ID via ADR-029) but neither requires the other. This ADR is not a Filigree integration. - ADR-034 §3's wire-pinned batch cap of 256 queries is an instance of the rule, not an exception to it. The constant carries `Override = wire:contracts.md#batch-cap` and `Coupling = wire-paired-with:Filigree client splitting logic`. The override-surface enum's `wire:*` value exists exactly for this case. @@ -267,15 +267,15 @@ Graduating a constant from state 1 to state 2 is a normal commit; graduating fro ## Alternatives Considered -### Alternative 1: ship `clarion.yaml` with all eleven constants as keys and call it done +### Alternative 1: ship `loomweave.yaml` with all eleven constants as keys and call it done -**Pros**: most operationally tactile; an operator can `cat clarion.yaml` and see what is tunable. Implementation surface is bounded — eleven keys, eleven config-loader sites, eleven floor checks. Matches ADR-021 §4's existing four named keys and extends them naturally to the other seven. +**Pros**: most operationally tactile; an operator can `cat loomweave.yaml` and see what is tunable. Implementation surface is bounded — eleven keys, eleven config-loader sites, eleven floor checks. Matches ADR-021 §4's existing four named keys and extends them naturally to the other seven. **Cons**: this is a level-12 (parameters) intervention to a drift-to-low-performance loop. Per `answer-systems-thinker.md`: -> "A config file without the rule decays back to hardcoded constants on the next sprint — that is exactly how Clarion got here. The rule is what creates the surface; the surface alone is a parameter intervention (Level 12) and parameter interventions to a drift-to-low-performance loop reset the constant without changing the slope." +> "A config file without the rule decays back to hardcoded constants on the next sprint — that is exactly how Loomweave got here. The rule is what creates the surface; the surface alone is a parameter intervention (Level 12) and parameter interventions to a drift-to-low-performance loop reset the constant without changing the slope." -The eleven constants in `clarion-core` were not added all at once; they accreted over Sprints 1 and 2. Adding `clarion.yaml` without the rule that gates how the *next* constant graduates means the twelfth, thirteenth, fourteenth constants land hardcoded again, with `breaker.rs:7`-style "lands in WP6" comments, and the cycle repeats. The discipline must precede the surface, not follow it. +The eleven constants in `loomweave-core` were not added all at once; they accreted over Sprints 1 and 2. Adding `loomweave.yaml` without the rule that gates how the *next* constant graduates means the twelfth, thirteenth, fourteenth constants land hardcoded again, with `breaker.rs:7`-style "lands in WP6" comments, and the cycle repeats. The discipline must precede the surface, not follow it. **Why rejected**: the parameter intervention does not change the rate at which new constants accrete; only the rule does. @@ -299,7 +299,7 @@ A CI lint script is deterministic: the build either fails or it does not. The co **Why rejected**: code review's catch-rate is what this ADR is trying to compensate for, not augment. -### Alternative 4: extract `clarion-llm` immediately as the high-impact intervention; defer the broader rule +### Alternative 4: extract `loomweave-llm` immediately as the high-impact intervention; defer the broader rule **Pros**: closes the security-engineer's STRIDE-T/STRIDE-I argument fastest (the outbound-HTTP stack stops sharing a crate with the plugin supervisor); concrete deliverable; survives `cargo clippy`. @@ -325,7 +325,7 @@ The four-axis declaration is the floor — additional discipline can be layered ## Related Decisions -- [ADR-021](./ADR-021-plugin-authority-hybrid.md) — Plugin authority hybrid; §4 names four of the eleven operational constants (`plugin_limits.max_frame_bytes`, `plugin_limits.max_records_per_run`, `plugin_limits.max_rss_mib`, `expected_max_rss_mb`) as `clarion.yaml` config keys. ADR-035 generalises the discipline that ADR-021 §4 already implies and extends it to every operational constant. +- [ADR-021](./ADR-021-plugin-authority-hybrid.md) — Plugin authority hybrid; §4 names four of the eleven operational constants (`plugin_limits.max_frame_bytes`, `plugin_limits.max_records_per_run`, `plugin_limits.max_rss_mib`, `expected_max_rss_mb`) as `loomweave.yaml` config keys. ADR-035 generalises the discipline that ADR-021 §4 already implies and extends it to every operational constant. - [ADR-023](./ADR-023-tooling-baseline.md) — Tooling baseline; defines `clippy.toml`'s `too-many-lines-threshold = 120`. ADR-035 §4's 1,500-LOC budget operates above ADR-023's per-function threshold — the two thresholds apply to different units (function vs. file) and do not conflict. The `#[allow(clippy::too_many_lines)]` sites in `analyze.rs` are ADR-023 escapes; ADR-035 §6 requires either re-enabling them or pairing each with a declared split trigger. - [ADR-030](./ADR-030-on-demand-summary-scope.md) — Narrowed WP6 to the on-demand `summary(id)` MCP tool, leaving the operator-tunables work currently un-homed per `answer-solution-architect.md` §5. ADR-035 commits the governing rule; the implementation of the config surface lands in a post-1.0 work package (TBD) that operates on top of this ADR's framework. - [ADR-034](./ADR-034-federation-http-read-api-hardening.md) — Federation HTTP read API hardening; §3's wire-pinned 256-query batch cap is an instance of ADR-035's rule (`Override = wire:contracts.md#batch-cap`). The two ADRs do not conflict; ADR-034 specifies the wire contract, ADR-035 specifies the source-comment discipline. @@ -336,8 +336,8 @@ The four-axis declaration is the floor — additional discipline can be layered ### Originating analysis (2026-05-22 architecture review) - Final report `§8 Open Questions`: [`docs/arch-analysis-2026-05-22-1924/04-final-report.md`](../../arch-analysis-2026-05-22-1924/04-final-report.md#8-open-questions-for-the-next-phase) — the five questions this ADR's rule absorbs. -- Subsystem catalog `clarion-core` Concerns: [`02-subsystem-catalog.md`](../../arch-analysis-2026-05-22-1924/02-subsystem-catalog.md) §1 — the eleven-limit enumeration; the "every limit is a recompile" tell; the `mock.rs` 876-LOC sign of non-trivial host pipeline state. -- Subsystem catalog `clarion-storage` Concerns: §2 — the `application_id`/`user_version` gap; the writer-actor cadence constants (256 / 50) at `writer.rs:35,38`. +- Subsystem catalog `loomweave-core` Concerns: [`02-subsystem-catalog.md`](../../arch-analysis-2026-05-22-1924/02-subsystem-catalog.md) §1 — the eleven-limit enumeration; the "every limit is a recompile" tell; the `mock.rs` 876-LOC sign of non-trivial host pipeline state. +- Subsystem catalog `loomweave-storage` Concerns: §2 — the `application_id`/`user_version` gap; the writer-actor cadence constants (256 / 50) at `writer.rs:35,38`. ### SME roundtable (2026-05-23) @@ -345,23 +345,23 @@ The four-axis declaration is the floor — additional discipline can be layered - Systems thinker: [`answer-systems-thinker.md`](../../archive/working-notes/arch-analysis-2026-05-22-1924/answer-systems-thinker.md) — the level-5 (rules) leverage argument; the drift-to-low-performance archetype; the `analyze.rs:74` and `breaker.rs:7` smoking-gun tells (line numbers as recorded at analysis time; current `analyze.rs` `#[allow(clippy::too_many_lines)]` site has shifted to line 65 with two additional sites at 650 and 1190; the rule applies to all three). - Python engineer: [`answer-python-engineer.md`](../../archive/working-notes/arch-analysis-2026-05-22-1924/answer-python-engineer.md) — the wire-contract-pinned vs. operational-tunable Python constant classification; the `MAX_PYRIGHT_RESTARTS_PER_RUN` "per run" name vs. per-instance implementation interaction failure; the basis for the `Coupling` axis. - Quality engineer: [`answer-quality-engineer.md`](../../archive/working-notes/arch-analysis-2026-05-22-1924/answer-quality-engineer.md) — the per-constant test-coverage matrix; the `DEFAULT_MAX_RSS_MIB`/`DEFAULT_MAX_NOFILE`/`DEFAULT_MAX_NPROC` security-enforcement cluster as the highest-risk untested area. -- Security engineer: [`answer-security-engineer.md`](../../archive/working-notes/arch-analysis-2026-05-22-1924/answer-security-engineer.md) — the STRIDE-D/STRIDE-E framing of recompile-to-tune as a security-posture stance; the `clarion-llm` extraction as STRIDE-T/STRIDE-I defense-in-depth; the security-uniformity argument for keeping some constants `Override = recompile`. +- Security engineer: [`answer-security-engineer.md`](../../archive/working-notes/arch-analysis-2026-05-22-1924/answer-security-engineer.md) — the STRIDE-D/STRIDE-E framing of recompile-to-tune as a security-posture stance; the `loomweave-llm` extraction as STRIDE-T/STRIDE-I defense-in-depth; the security-uniformity argument for keeping some constants `Override = recompile`. ### Source-of-truth code locations -- `crates/clarion-core/src/plugin/limits.rs` — `ContentLengthCeiling::DEFAULT`, `EntityCountCap::DEFAULT_MAX`, `DEFAULT_MAX_RSS_MIB`, `DEFAULT_MAX_NOFILE`, `DEFAULT_MAX_NPROC`. -- `crates/clarion-core/src/plugin/host.rs` — `MAX_ENTITY_FIELD_BYTES`, `MAX_ENTITY_EXTRA_BYTES`, `STDERR_TAIL_BYTES`, `MAX_UNRESOLVED_CALLEE_EXPR_BYTES`, `PYRIGHT_MAX_NPROC`. -- `crates/clarion-core/src/plugin/breaker.rs:7` — the literal "lands in WP6" comment that this ADR retires; `CRASH_LOOP_THRESHOLD`, `CRASH_LOOP_WINDOW`. -- `crates/clarion-core/src/plugin/protocol.rs` — `MAX_PROTOCOL_ERROR_FIELD_BYTES`. -- `crates/clarion-core/src/plugin/transport.rs` — `MAX_HEADER_LINE_BYTES`. -- `crates/clarion-storage/src/writer.rs:35,38` — `DEFAULT_CHANNEL_CAPACITY = 256`, `DEFAULT_BATCH_SIZE = 50`. -- `crates/clarion-cli/src/analyze.rs:65,650,1190` — three `#[allow(clippy::too_many_lines)]` sites that ADR-035 §6 requires either re-enabling or pairing with a declared split trigger before the 1.1 release. -- `plugins/python/src/clarion_plugin_python/server.py:48,49` — `MAX_CONTENT_LENGTH = 8 MiB` (wire-paired-with Rust), `MAX_FILES_PER_PYRIGHT_SESSION = 25`. -- `plugins/python/src/clarion_plugin_python/pyright_session.py:43-49` — six Pyright-session operational constants enumerated by `answer-python-engineer.md`. +- `crates/loomweave-core/src/plugin/limits.rs` — `ContentLengthCeiling::DEFAULT`, `EntityCountCap::DEFAULT_MAX`, `DEFAULT_MAX_RSS_MIB`, `DEFAULT_MAX_NOFILE`, `DEFAULT_MAX_NPROC`. +- `crates/loomweave-core/src/plugin/host.rs` — `MAX_ENTITY_FIELD_BYTES`, `MAX_ENTITY_EXTRA_BYTES`, `STDERR_TAIL_BYTES`, `MAX_UNRESOLVED_CALLEE_EXPR_BYTES`, `PYRIGHT_MAX_NPROC`. +- `crates/loomweave-core/src/plugin/breaker.rs:7` — the literal "lands in WP6" comment that this ADR retires; `CRASH_LOOP_THRESHOLD`, `CRASH_LOOP_WINDOW`. +- `crates/loomweave-core/src/plugin/protocol.rs` — `MAX_PROTOCOL_ERROR_FIELD_BYTES`. +- `crates/loomweave-core/src/plugin/transport.rs` — `MAX_HEADER_LINE_BYTES`. +- `crates/loomweave-storage/src/writer.rs:35,38` — `DEFAULT_CHANNEL_CAPACITY = 256`, `DEFAULT_BATCH_SIZE = 50`. +- `crates/loomweave-cli/src/analyze.rs:65,650,1190` — three `#[allow(clippy::too_many_lines)]` sites that ADR-035 §6 requires either re-enabling or pairing with a declared split trigger before the 1.1 release. +- `plugins/python/src/loomweave_plugin_python/server.py:48,49` — `MAX_CONTENT_LENGTH = 8 MiB` (wire-paired-with Rust), `MAX_FILES_PER_PYRIGHT_SESSION = 25`. +- `plugins/python/src/loomweave_plugin_python/pyright_session.py:43-49` — six Pyright-session operational constants enumerated by `answer-python-engineer.md`. ### Doctrine the rule operationalises - Meadows, Donella H., *Thinking in Systems: A Primer*. Level-5 (rules) and level-6 (information flows) leverage points in the twelve-level hierarchy. -- The doctrine in [`docs/suite/loom.md`](../../suite/loom.md) §5 — the federation axiom's "enrich-only" rule applies by analogy to constants: a constant that gates externally observable behaviour without a declared basis is the same archetype as a sibling tool that adds a required dependency. +- The doctrine in [`docs/suite/weft.md`](../../suite/weft.md) §5 — the federation axiom's "enrich-only" rule applies by analogy to constants: a constant that gates externally observable behaviour without a declared basis is the same archetype as a sibling tool that adds a required dependency. — End of ADR-035 — diff --git a/docs/loomweave/adr/ADR-036-wardline-taint-fact-store.md b/docs/loomweave/adr/ADR-036-wardline-taint-fact-store.md new file mode 100644 index 00000000..4d9016e8 --- /dev/null +++ b/docs/loomweave/adr/ADR-036-wardline-taint-fact-store.md @@ -0,0 +1,128 @@ +# ADR-036: Loomweave as Wardline Taint-Fact Store — A Named, Read+Write HTTP Surface + +**Status**: Accepted +**Date**: 2026-05-31 +**Deciders**: qacona@gmail.com +**Context**: Wardline SP9 requested a persistent per-entity taint/provenance store held by Loomweave and keyed by Loomweave entity (`wardline/docs/integration/2026-05-30-wardline-loomweave-taint-store-requirements.md`). The design response that this ADR ratifies is `docs/superpowers/specs/2026-05-30-loomweave-wardline-taint-store-design.md`; the outward-facing confirmation to Wardline is `docs/federation/2026-05-30-loomweave-wardline-taint-store-response.md`. + +## Summary + +Wardline's `explain_taint` (and the later overlay-scan + N-hop chain work) re-runs taint analysis on every call. SP9 asks to turn those calls into cheap lookups against a persistent **per-entity taint-fact store that Loomweave holds**, keyed by Loomweave entity: Wardline computes the facts during `wardline scan`, writes them to Loomweave, and later reads become graph lookups. + +This ADR records the decision to build that store **inside Loomweave, scoped specifically to Wardline** — a dedicated `wardline_taint_facts` table and a set of `/api/wardline/*` routes — and to do so as Loomweave's **first read+write use of its HTTP API** (the API is read-only today per ADR-014 and ADR-034). The decision is recorded as an ADR, **not** as a `weft.md` §5 asterisk, because the integration **passes** the federation failure test rather than accepting a violation of it (see §Federation analysis below). + +The load-bearing guard, carried verbatim because it is the one sentence that keeps this decision from becoming a precedent: + +> This is not a precedent for a general-purpose cross-product blob store. The next sibling that wants per-entity persistence gets its own named, justified surface or it does not get one. + +## Context + +### What Wardline asked for + +Wardline's SP9 request (`wardline/docs/integration/2026-05-30-wardline-loomweave-taint-store-requirements.md`, referenced relative to the Wardline repo) wants a persistent per-entity taint/provenance store keyed by Loomweave entity, so that the SP8 stateless re-run becomes a layered optimization rather than the only path. The seven capabilities requested are: batch qualname→entity resolution, per-entity taint-fact upsert, per-entity fetch (single + batch), a freshness/staleness contract, entity-lifecycle handling, an HTTP transport, and per-project isolation. + +### Where Loomweave stands today + +- Loomweave's HTTP API is **read-only**. ADR-014 introduced the federation read API; ADR-034 hardened it (HMAC inbound auth, batch resolution, `BRIEFING_BLOCKED`, stable per-project `instance_id`). No write path is exposed over HTTP. +- Writes to the `.loomweave/` SQLite DB go through the ADR-011 writer-actor. `loomweave analyze` holds one for the duration of a run. `loomweave serve` opens the `ReaderPool` for queries and *additionally* spawns an optional MCP query-time writer-actor when an LLM summary provider is configured (`serve.rs`, for the summary/inferred-edge caches). No write path is exposed over HTTP today. +- The schema-reserved `wardline TEXT` column on `entities` is **orthogonal** to this work — it was reserved for the fingerprint/qualname reverse-map (`WardlineMeta`, `detailed-design.md` §7), a different and smaller dataset. It is not the taint store and is left as-is. Crucially, `loomweave analyze` rebuilds every `EntityRecord` with `wardline_json: None` and the `entities` UPSERT sets `wardline = excluded.wardline`, so any taint fact stored in that column would be silently wiped on the next re-analyze. A separate table is the only clobber-safe home. + +### Why this needs a decision, not just an implementation + +Two things make this load-bearing rather than routine. First, it flips Loomweave's HTTP API from read-only to read+write — a posture change that the security model (ADR-034) and the operator trust model (`docs/operator/loomweave-http-read-api.md`) must absorb. Second, the shape "a sibling writes opaque blobs keyed by Loomweave entity" is, generalised, exactly the **shared system-of-record** that `weft.md` §6 forbids. The decision is therefore about *boundaries* — what surface exists, what it is named, and what it must never be allowed to become — not merely about a table and four routes. + +## Decision + +### 1. A Wardline-specific, per-entity taint-fact store + +Loomweave builds a per-entity taint-fact store **named for and scoped to Wardline**: + +- A dedicated SQLite table, `wardline_taint_facts`, introduced by **migration `0003`** (`crates/loomweave-storage/migrations/0003_wardline_taint_facts.sql`; the design spec's "migration 0002" predates `0002_briefing_blocked.sql` and is superseded — `CURRENT_SCHEMA_VERSION` bumps `2 → 3`). The table is keyed by `entity_id` with `ON DELETE CASCADE` against `entities(id)`; it stores `wardline_json` (opaque, verbatim, Wardline-owned), and the queryable observability columns `scan_id`, `content_hash_at_compute`, and `updated_at`. +- A set of `/api/wardline/*` HTTP routes on `loomweave serve` (enumerated in Consequences), HMAC-gated per ADR-034 inbound auth. + +The surface is `wardline`-named at every layer — the table, the routes — exactly the naming discipline the ADR-018 asterisk used. There is no generic `sibling_json` column, no `/api/blob/*` route, no capability bus. This structural specificity is what makes the federation guard (§below) enforceable rather than aspirational. + +### 2. The first read+write use of the HTTP API + +This is Loomweave's first read+write HTTP surface. Writes go through an **optional** ADR-011 writer-actor that `loomweave serve` spawns **only when the write API is config-enabled** — the new config knob **`serve.http.wardline_taint_write`**, which **defaults off**. With the knob off, `serve` retains exactly today's read-only posture (the `ReaderPool` alone) and the write routes reject cleanly. The writer-actor is the same ADR-011 mechanism `loomweave analyze` uses; taint writes are query-time writes (the `query_time_write` actor path, like the summary-cache upsert), not analyze-run `BeginRun`/`CommitRun` writes. + +### 3. Resolution: exact-tier direct lookup; Wardline owns normalization + +Writes and reads are **qualname-keyed**. Wardline sends a **pre-composed** dotted qualname; Loomweave builds the candidate entity ID `python:function:` and resolves it by **direct existence lookup** against the local catalog. **Loomweave does no normalization at resolution time** — Wardline owns the normalization and pre-composes the qualname to byte-match Loomweave's `canonical_qualified_name` per `docs/federation/fixtures/wardline-qualname-normalization.json`. The five ADR-018 divergence traps (``, nested-class chains, non-`src` package roots such as `lib.foo`/`app.service`, the `a.src.b` pattern) are therefore **Wardline's** conformance burden against the fixture; on Loomweave's side they reduce to **verbatim-storage** correctness (Loomweave must not strip, rewrite, or re-canonicalise the composed string). + +Resolution is **exact-tier only for writes**: a write requires an `exact` match; `heuristic`/`none` results are returned in `unresolved_qualnames` and **never written** (a heuristic *write* would silently mis-attach a fact to the wrong entity). Reads may surface a `heuristic` match. The heuristic resolution tier and the conformance oracle over raw file+qualname (`scheme=wardline_qualname`) are **deferred** to Flow B B.2 (`clarion-ca2d26ffbe`), which extends — and must consume, not rebuild — this exact-tier resolver. + +### 4. Concurrency posture (ADR-011) + +In-process, a write-enabled `serve` may run **two** ADR-011 writer-actors against the same DB at once — the optional MCP summary writer (when an LLM provider is configured) and the taint-store writer — each on its own connection. This is a deliberate, bounded relaxation of ADR-011's single-writer-per-process expectation: the two write *streams* are independent (summary/inferred-edge caches vs. Wardline taint facts), and every writer opens its batch with `BEGIN IMMEDIATE` under the same `PRAGMA busy_timeout=5000` + `loomweave-storage::retry` capped-backoff layer, so they serialize at the SQLite write lock rather than corrupting. The same mechanism covers **cross-process** contention: a write-enabled `serve` and a concurrent `analyze` are **not expected** to write the same `.loomweave/` DB at the same time (an operational expectation, documented rather than enforced beyond the SQLite lock), but if they do, the busy-timeout + retry resolves it. A write that still cannot land after retry **fails as a retryable error**, and Wardline degrades to its SP8 stateless re-run. Per-entity replace is atomic at the row level, so Loomweave never corrupts or partially merges two scans for the same entity. + +### 5. The federation guard (load-bearing, verbatim) + +> This is not a precedent for a general-purpose cross-product blob store. The next sibling that wants per-entity persistence gets its own named, justified surface or it does not get one. + +The guard binds future decisions. A subsequent sibling (Shuttle, or a fourth-party tool) requesting per-entity persistence does not inherit this API by extension; it must pass the same `weft.md` §3–§5 analysis on its own terms and earn its own named, justified surface. There is no generic blob bus, and this ADR must not be cited as authority for building one. + +## Federation analysis (`weft.md` §3–§5) — passes; ADR, not asterisk + +The integration is **enrich-only and additive**, and passes the §5 failure test on all three modes: + +- **Solo-useful (both products).** Loomweave's briefings, queries, and catalog work with the taint store **empty** — the store is optional enrichment on Loomweave's *own* entities, never a precondition for Loomweave's semantics. Wardline guarantees (request §6) that its **SP8 stateless re-run is the permanent fallback**: Wardline boots and answers `explain_taint` with Loomweave absent, unreachable, write-disabled, or stale. Neither product requires the other to make sense of its own data. +- **Pairwise-composable.** `(Wardline, Loomweave)` composes directly — Loomweave stores Wardline's facts and serves them back cheaply. No third sibling mediates the pair (no pipeline coupling). +- **No semantic coupling.** `wardline_json` is stored **verbatim and opaque**. Loomweave never parses, validates, or depends on its contents; all taint semantics (including the single-successor chain walk) stay Wardline-side. Removing Wardline changes nothing about the meaning of Loomweave's own data — an empty or absent store reduces convenience, not coherence. +- **No initialization coupling.** `serve` boots and self-validates whether or not the write knob is set; with it off, the posture is identical to today's read-only `serve`. + +The one real risk is the **"no Weft store" rule** (`weft.md` §6): a *generic* "any sibling writes opaque blobs keyed by entity" API would turn Loomweave into the shared system-of-record the doctrine forbids. The guard in §5 of the Decision neutralises that risk by keeping the surface **structurally Wardline-specific** (a `wardline`-named table and `wardline`-scoped routes), not a general `sibling_json` bus. + +Because this integration **passes** the failure test — rather than accepting a violation with a written retirement condition — it is recorded as a **new ADR, not a new `weft.md` §5 asterisk**. Per `weft.md` §5, an asterisk is the instrument for an *accepted, temporarily-tolerated violation* of one named failure-test mode, carrying a retirement condition and an honest statement of which mode is violated. This decision violates no mode, has nothing to retire, and so is the wrong shape for an asterisk. It is a clean federation surface, recorded as a locked architectural decision. + +## Consequences + +### Positive + +- Wardline's `explain_taint` becomes a cheap per-entity lookup instead of a re-analysis, with the SP8 re-run preserved as a permanent standalone fallback. The optimization is layered, never load-bearing. +- The store is clobber-safe by construction: a dedicated `wardline_taint_facts` table is never touched by the `entities` UPSERT, so re-analyze does not wipe taint facts the way the schema-reserved `wardline` column would. +- `scan_id` and `content_hash_at_compute` are real columns (not parsed out of the opaque blob), giving observability and an optional future prune-by-scan without ever requiring Loomweave to read `wardline_json`. +- The federation boundary is structural, not merely documented: the `wardline`-named table and routes make "Wardline-specific, not a generic blob bus" a property of the schema, enforceable at review time against the §5 guard. + +### Negative / costs + +- Loomweave's HTTP API gains a write posture for the first time, widening the security surface that ADR-034's HMAC auth and the operator trust model must cover. Mitigation: the write path is **off by default** (`serve.http.wardline_taint_write = false`) and HMAC-gated; with the knob off, `serve` is byte-for-byte today's read-only posture. +- `serve` gains an optional ADR-011 writer-actor, adding both an **in-process** contention surface (it coexists with the optional MCP summary writer — two writer-actors on one DB, §4) and the **cross-process** surface (vs. a concurrent `analyze`). Mitigation: every writer uses `BEGIN IMMEDIATE` + `PRAGMA busy_timeout=5000` + the `loomweave-storage::retry` capped-backoff layer; a write that cannot land fails retryably and Wardline degrades to SP8 (enrich-only, never corruption). + +### What ships (artifact inventory) + +- **Migration `0003`** — `crates/loomweave-storage/migrations/0003_wardline_taint_facts.sql`; the `wardline_taint_facts` table; `CURRENT_SCHEMA_VERSION` bumps `2 → 3`. +- **Routes** (HMAC-gated, on `loomweave serve`): + - `POST /api/wardline/resolve` — batch qualname→entity resolution (exact-tier; pre-composed `python:function:` direct lookup). + - `POST /api/wardline/taint-facts` — batch upsert (per-entity replace), qualname-keyed, exact-only, returning `{written, unresolved_qualnames}`, `project`-guarded. + - `GET /api/wardline/taint-facts` — single fetch by qualname; returns blob + `current_content_hash` + `exists`. + - `POST /api/wardline/taint-facts:batch-get` — batch fetch; one round-trip; blob + `current_content_hash` + `exists` per entity. +- **Config knob** — `serve.http.wardline_taint_write` (boolean, default `false`); gates whether `serve` spawns the optional writer-actor and exposes the write/resolve routes. + +### What is deferred (not in this surface) + +- The **heuristic resolution tier** and the **conformance oracle** (`scheme=wardline_qualname` over raw file+qualname) remain deferred to **Flow B B.2** (`clarion-ca2d26ffbe`). The shipping resolve route here is exact-tier only; B.2 extends it and must consume this resolver rather than rebuild it. +- No general-purpose blob store, no Loomweave parsing of `wardline_json`, no replacement of the SP8 re-run, no mandatory lifecycle cascade/prune machinery beyond the freshness gate, and no ADR-029 issue↔entity bindings — see the design spec §9 for the full non-goal list. + +## Weft vocabulary verdict + +Per `docs/loomweave/adr/README.md` ("ADR acceptance criteria — Weft vocabulary discipline") and `weft.md` §8, this ADR introduces cross-product-visible field names that cross the Wardline↔Loomweave wire in the SP9 contract: `wardline_json`, `scan_id`, `content_hash_at_compute`, `current_content_hash`, and `unresolved_qualnames`. Verdict: **`no clash`** — each term is either Wardline-namespaced or local to this Loomweave surface, and none collides with an existing sibling term. `content_hash` semantics follow Loomweave's existing definition (whole-file `blake3`, hex; per the design spec §3), so the hash-related fields reuse rather than redefine vocabulary. These entries are **recorded in [`docs/suite/glossary.md`](../../suite/glossary.md) ("SP9 Wardline taint-store wire terms") as part of this ADR's acceptance evidence**, per the ADR-acceptance rule. The Loomweave-internal names `wardline_taint_facts` (table) and `serve.http.wardline_taint_write` (config) are deliberately excluded from the glossary — they never cross the wire to Wardline. + +## Related Decisions + +- [ADR-011](./ADR-011-writer-actor-concurrency.md) — Writer-actor concurrency. The taint store's optional `serve`-side writer is the same mechanism; the concurrency posture (§4) rests on ADR-011's `busy_timeout` + capped-backoff retry. +- [ADR-014](./ADR-014-filigree-registry-backend.md) — The federation HTTP read API this ADR extends from read-only to read+write. +- [ADR-018](./ADR-018-identity-reconciliation.md) — Identity reconciliation; Wardline owns its qualnames and pre-composes them to Loomweave's canonical form. The divergence traps are Wardline's conformance burden against the normalization fixture; Loomweave's side is verbatim storage. +- [ADR-029](./ADR-029-entity-associations-binding.md) — Entity-association binding; adjacent and explicitly out of scope (request §9). Not required by, and does not require, this surface. +- [ADR-034](./ADR-034-federation-http-read-api-hardening.md) — HTTP read-API hardening; the HMAC inbound auth and `project`/instance posture the `/api/wardline/*` routes inherit. + +## References + +- Design spec (federation verdict §2; seven decisions §3): [`docs/superpowers/specs/2026-05-30-loomweave-wardline-taint-store-design.md`](../../superpowers/specs/2026-05-30-loomweave-wardline-taint-store-design.md). +- Outward contract response: [`docs/federation/2026-05-30-loomweave-wardline-taint-store-response.md`](../../federation/2026-05-30-loomweave-wardline-taint-store-response.md). +- Implementation plan: [`docs/superpowers/plans/2026-05-31-loomweave-wardline-taint-store.md`](../../superpowers/plans/2026-05-31-loomweave-wardline-taint-store.md). +- Wardline request: `wardline/docs/integration/2026-05-30-wardline-loomweave-taint-store-requirements.md` (Wardline repo). +- Qualname parity fixture: [`docs/federation/fixtures/wardline-qualname-normalization.json`](../../federation/fixtures/wardline-qualname-normalization.json). +- Federation doctrine: [`docs/suite/weft.md`](../../suite/weft.md) §3–§6. + +— End of ADR-036 — diff --git a/docs/clarion/adr/ADR-037-shared-error-vocabulary.md b/docs/loomweave/adr/ADR-037-shared-error-vocabulary.md similarity index 76% rename from docs/clarion/adr/ADR-037-shared-error-vocabulary.md rename to docs/loomweave/adr/ADR-037-shared-error-vocabulary.md index 644de6d2..9b1ac3f6 100644 --- a/docs/clarion/adr/ADR-037-shared-error-vocabulary.md +++ b/docs/loomweave/adr/ADR-037-shared-error-vocabulary.md @@ -1,4 +1,4 @@ -# ADR-037: Shared Error Vocabulary (`clarion-core::errors`) +# ADR-037: Shared Error Vocabulary (`loomweave-core::errors`) **Status**: Accepted **Date**: 2026-05-31 @@ -8,25 +8,25 @@ ## Summary -Co-locate two **separate** typed enums — `HttpErrorCode` and `McpErrorCode` — in a new `clarion-core::errors` module as the single source of truth for Clarion's two structured wire error surfaces. Each enum preserves its established wire spelling (SCREAMING_SNAKE for HTTP, kebab-case for MCP). Wire bytes are unchanged on both surfaces. Drift tests pin every wire string at the definition site. +Co-locate two **separate** typed enums — `HttpErrorCode` and `McpErrorCode` — in a new `loomweave-core::errors` module as the single source of truth for Loomweave's two structured wire error surfaces. Each enum preserves its established wire spelling (SCREAMING_SNAKE for HTTP, kebab-case for MCP). Wire bytes are unchanged on both surfaces. Drift tests pin every wire string at the definition site. ## Context -Clarion emits machine-routable error codes on two independent wire surfaces: +Loomweave emits machine-routable error codes on two independent wire surfaces: 1. **Federation HTTP read API** — `HttpErrorCode`, SCREAMING_SNAKE on the wire (`INVALID_PATH`, `NOT_FOUND`, etc.). Contract frozen by ADR-034 and `docs/federation/contracts.md`; consumed by Filigree and Wardline clients that `switch on code`. The ten-variant set accrued across ADR-014 (initial error envelope), ADR-034 (added `BRIEFING_BLOCKED`, `UNAUTHENTICATED`, `BATCH_TOO_LARGE`), and ADR-036 (added `WRITE_DISABLED`, `PROJECT_MISMATCH` for the `/api/wardline/*` routes). -2. **MCP tool-error envelope** — consumed by consult-mode agents. Prior to this work: ~47 bare string literals scattered across `clarion-mcp`, no type, no exhaustiveness checking, no compiler protection against typos, and no pinning of the wire strings in tests. +2. **MCP tool-error envelope** — consumed by consult-mode agents. Prior to this work: ~47 bare string literals scattered across `loomweave-mcp`, no type, no exhaustiveness checking, no compiler protection against typos, and no pinning of the wire strings in tests. -The two surfaces serve different transports (HTTP vs. stdio/MCP), different consumers (Loom siblings vs. LLM agents), and different granularities (the MCP side carries 18 fine-grained codes while the HTTP side carries 10 coarser ones). Without a shared home, the vocabularies could drift independently and silently. +The two surfaces serve different transports (HTTP vs. stdio/MCP), different consumers (Weft siblings vs. LLM agents), and different granularities (the MCP side carries 18 fine-grained codes while the HTTP side carries 10 coarser ones). Without a shared home, the vocabularies could drift independently and silently. The finding was raised as V11-ARCH-01 in the v1.1 deep-dive architectural review and tracked as Filigree issue clarion-b57c6bc49f. ## Decision -Add a `clarion-core::errors` module containing two separate typed enums: +Add a `loomweave-core::errors` module containing two separate typed enums: -- **`HttpErrorCode`** — the closed SCREAMING_SNAKE HTTP error-code set. Serializes via `serde(rename_all = "SCREAMING_SNAKE_CASE")`. Wire spelling is unchanged (frozen ADR-034 contract). HTTP status is chosen per endpoint by the callers in `clarion-cli`; no total `code → status` mapping exists on the enum itself because the contract mandates that the same code can carry different HTTP statuses on different endpoints (e.g. `BATCH_TOO_LARGE` is 400 on `POST /api/v1/files/batch` per ADR-034 but 413 on the `/api/wardline/*` batch routes per ADR-036; see `docs/federation/contracts.md`). Drift tests assert that every `as_str()` return equals the corresponding `serde_json::to_string()` output. +- **`HttpErrorCode`** — the closed SCREAMING_SNAKE HTTP error-code set. Serializes via `serde(rename_all = "SCREAMING_SNAKE_CASE")`. Wire spelling is unchanged (frozen ADR-034 contract). HTTP status is chosen per endpoint by the callers in `loomweave-cli`; no total `code → status` mapping exists on the enum itself because the contract mandates that the same code can carry different HTTP statuses on different endpoints (e.g. `BATCH_TOO_LARGE` is 400 on `POST /api/v1/files/batch` per ADR-034 but 413 on the `/api/wardline/*` batch routes per ADR-036; see `docs/federation/contracts.md`). Drift tests assert that every `as_str()` return equals the corresponding `serde_json::to_string()` output. - **`McpErrorCode`** — the closed kebab-case MCP error-code set. Wire spelling is unchanged from the previous bare string literals. Drift tests pin all 18 wire strings at the definition site. @@ -50,7 +50,7 @@ A total mapping from `HttpErrorCode` to an HTTP status code would require choosi ### Alternative 3: Add a `to_http()` narrowing method on `McpErrorCode` -A method converting a fine-grained MCP code to a coarse HTTP code would encode the narrowing relationship as a callable API. Rejected as dead code: the two surfaces are disjoint in the Clarion codebase — no production code path converts an MCP error to an HTTP response or vice versa. Dead methods accumulate maintenance cost without benefit. The narrowing relationship is recorded as a rustdoc table in the module instead, where it is a maintainer reference that does not compile to anything. +A method converting a fine-grained MCP code to a coarse HTTP code would encode the narrowing relationship as a callable API. Rejected as dead code: the two surfaces are disjoint in the Loomweave codebase — no production code path converts an MCP error to an HTTP response or vice versa. Dead methods accumulate maintenance cost without benefit. The narrowing relationship is recorded as a rustdoc table in the module instead, where it is a maintainer reference that does not compile to anything. ### Alternative 4: Keep bare string literals; add a test-only string set for pinning @@ -60,14 +60,14 @@ Add a centralised list of expected strings in a test helper without creating enu ### Positive -- **Single typed home for both vocabularies.** All additions, renames, and retirements require a one-line change in `clarion-core::errors` plus a test assertion, not a grep through `clarion-mcp`'s tool handlers. +- **Single typed home for both vocabularies.** All additions, renames, and retirements require a one-line change in `loomweave-core::errors` plus a test assertion, not a grep through `loomweave-mcp`'s tool handlers. - **MCP codes are now compiler-checked.** Exhaustive matches on `McpErrorCode` mean the compiler detects a missing branch. Typos at call sites are now a type error, not a silent wire divergence. - **Wire bytes unchanged on both surfaces.** Existing HTTP tests (ADR-034 wire assertions) and the 6 pinned MCP wire tests pass unchanged. Neither Filigree, Wardline, nor any consult-mode agent needs updating. - **ADR-034 / contracts.md HTTP wire contract untouched.** The `HttpErrorCode` enum is the same closed set; it gains no new variants and emits identical SCREAMING_SNAKE strings. ### Negative -- **`clarion-cli` and `clarion-mcp` now depend on `clarion-core`** for their error code types. This is a crate-internal dependency increase, not a federation coupling; `clarion-core` is already the shared crate for entity-ID logic, plugin host, and manifest parsing. The coupling cost is minimal. +- **`loomweave-cli` and `loomweave-mcp` now depend on `loomweave-core`** for their error code types. This is a crate-internal dependency increase, not a federation coupling; `loomweave-core` is already the shared crate for entity-ID logic, plugin host, and manifest parsing. The coupling cost is minimal. ### Neutral @@ -83,9 +83,9 @@ Add a centralised list of expected strings in a test helper without creating enu ## References -- Module: [`crates/clarion-core/src/errors.rs`](../../../crates/clarion-core/src/errors.rs) +- Module: [`crates/loomweave-core/src/errors.rs`](../../../crates/loomweave-core/src/errors.rs) - Wire contract: [`docs/federation/contracts.md`](../../federation/contracts.md) (pointer back to `HttpErrorCode` added addend in same commit) - Implementing commits: - - `5afbbe0` feat(errors): add `clarion-core::errors` with `HttpErrorCode` + `McpErrorCode` + - `5afbbe0` feat(errors): add `loomweave-core::errors` with `HttpErrorCode` + `McpErrorCode` - `3701cc7` refactor(http_read): migrate to shared `HttpErrorCode` - `8f3e539` refactor(mcp): type MCP error codes off `McpErrorCode`; retire ~47 bare literals diff --git a/docs/clarion/adr/ADR-038-sei-token-and-signature.md b/docs/loomweave/adr/ADR-038-sei-token-and-signature.md similarity index 64% rename from docs/clarion/adr/ADR-038-sei-token-and-signature.md rename to docs/loomweave/adr/ADR-038-sei-token-and-signature.md index c694f1df..d2eb9cff 100644 --- a/docs/clarion/adr/ADR-038-sei-token-and-signature.md +++ b/docs/loomweave/adr/ADR-038-sei-token-and-signature.md @@ -3,48 +3,48 @@ **Status**: Accepted **Date**: 2026-06-02 **Deciders**: qacona@gmail.com (with Claude) -**Context**: The Loom suite's Stable Entity Identity (SEI) standard — `/home/john/wardline/docs/superpowers/specs/2026-06-01-loom-stable-entity-identity-conformance.md` — names Clarion as the identity **authority/implementer** and records two decisions as OPEN, owned by Clarion, in its §0.5 pre-lock intake: **REQ-C-01** (signature schema) and **REQ-C-02** (token scheme). These are the last items between "all four subsystems reported" and SEI lock. This ADR resolves both, plus the identity-persistence shape they imply, so the SEI standard can lock. +**Context**: The Weft suite's Stable Entity Identity (SEI) standard — `/home/john/wardline/docs/superpowers/specs/2026-06-01-weft-stable-entity-identity-conformance.md` — names Loomweave as the identity **authority/implementer** and records two decisions as OPEN, owned by Loomweave, in its §0.5 pre-lock intake: **REQ-C-01** (signature schema) and **REQ-C-02** (token scheme). These are the last items between "all four subsystems reported" and SEI lock. This ADR resolves both, plus the identity-persistence shape they imply, so the SEI standard can lock. **Relates to**: [ADR-003](./ADR-003-entity-id-scheme.md) (the `{plugin}:{kind}:{qualname}` id this ADR demotes from *identity* to *locator*), [ADR-018](./ADR-018-identity-reconciliation.md) (qualname reconciliation — subsumed as an *identity* mechanism by SEI per the SEI spec §0.2), [ADR-011](./ADR-011-writer-actor-concurrency.md) (writer-actor; the SEI writes route through it), [ADR-029](./ADR-029-entity-associations-binding.md) (entity associations — transport unchanged; the identity value carried becomes an SEI). -**Companion plan**: [`docs/superpowers/plans/2026-06-02-clarion-integrated-delivery-plan.md`](../../superpowers/plans/2026-06-02-clarion-integrated-delivery-plan.md) — the task-level implementation. +**Companion plan**: [`docs/superpowers/plans/2026-06-02-loomweave-integrated-delivery-plan.md`](../../superpowers/plans/2026-06-02-loomweave-integrated-delivery-plan.md) — the task-level implementation. ## Summary -The qualname id (`{plugin}:{kind}:{qualname}`) is a fine *address* and a poor *identity* — rename and move change it, orphaning every cross-tool binding keyed on it. The SEI standard introduces a durable opaque surrogate identity (SEI), demotes the qualname to a resolvable *locator*, and re-establishes identity via a deterministic matcher at each re-index. This ADR fixes the three Clarion-owned shape decisions that standard left open: +The qualname id (`{plugin}:{kind}:{qualname}`) is a fine *address* and a poor *identity* — rename and move change it, orphaning every cross-tool binding keyed on it. The SEI standard introduces a durable opaque surrogate identity (SEI), demotes the qualname to a resolvable *locator*, and re-establishes identity via a deterministic matcher at each re-index. This ADR fixes the three Loomweave-owned shape decisions that standard left open: -1. **Token (REQ-C-02):** `clarion:eid:`, where `mint_run_id` is the UUID of the run that *mints* the SEI. +1. **Token (REQ-C-02):** `loomweave:eid:`, where `mint_run_id` is the UUID of the run that *mints* the SEI. 2. **Signature (REQ-C-01):** a plugin-declared, versioned JSON object stored verbatim in a plain (non-unique) `entities.signature TEXT`, compared by string equality. 3. **Identity persistence:** identity lives in a dedicated `sei_bindings` table keyed by SEI — **not** as a column on `entities` — because `entities` is cumulative and never pruned, which a `UNIQUE` SEI column cannot survive. -It also reserves the `clarion:eid:` locator namespace so `resolve(locator)` can fail-closed-reject an SEI-shaped input. +It also reserves the `loomweave:eid:` locator namespace so `resolve(locator)` can fail-closed-reject an SEI-shaped input. ## Context -### What the SEI standard fixes, and what it left to Clarion +### What the SEI standard fixes, and what it left to Loomweave -Verified against `clarion`/`clarion-storage` source on 2026-06-01/02: +Verified against `loomweave`/`loomweave-storage` source on 2026-06-01/02: -- Clarion's entity id is derived from name + module path and upserted `ON CONFLICT(id)`. A rename/move changes the id; ADR-003 even named a deferred `EntityAlias` seam for exactly this. SEI is the product-grade form of that seam. -- The SEI standard settles the *track* (one canonical identity interface, it is SEI, fail-closed re-binding, deterministic v1 matcher, Clarion is the authority) and leaves the *shape* open until lock — including the two Clarion-owned items this ADR resolves. +- Loomweave's entity id is derived from name + module path and upserted `ON CONFLICT(id)`. A rename/move changes the id; ADR-003 even named a deferred `EntityAlias` seam for exactly this. SEI is the product-grade form of that seam. +- The SEI standard settles the *track* (one canonical identity interface, it is SEI, fail-closed re-binding, deterministic v1 matcher, Loomweave is the authority) and leaves the *shape* open until lock — including the two Loomweave-owned items this ADR resolves. ### Ground truth that shaped these decisions (peer review, 2026-06-02) An initial draft of the companion plan was reviewed against the actual code. Two intended decisions were found broken; this ADR records the corrected forms and the evidence, so a future reader does not regress them: -- **`first_seen_commit` is never populated.** `crates/clarion-cli/src/analyze.rs` writes `first_seen_commit: None` on every entity (the only non-`None` values in the tree are in unit tests). A token keyed on `first_seen_commit` therefore degenerates to `blake3(locator)` — the collision-on-reuse flaw the SEI priority brief (§3) explicitly warned against. The token must not depend on it. -- **`entities` is cumulative and never pruned.** `crates/clarion-storage/src/writer.rs` upserts `INSERT ... ON CONFLICT(id) DO UPDATE`; there is no `DELETE FROM entities` on re-index. Vanished and renamed entities' rows persist forever. A `sei TEXT UNIQUE` column on `entities` is therefore unworkable: carrying an SEI across a rename collides with the stale row that still holds it. +- **`first_seen_commit` is never populated.** `crates/loomweave-cli/src/analyze.rs` writes `first_seen_commit: None` on every entity (the only non-`None` values in the tree are in unit tests). A token keyed on `first_seen_commit` therefore degenerates to `blake3(locator)` — the collision-on-reuse flaw the SEI priority brief (§3) explicitly warned against. The token must not depend on it. +- **`entities` is cumulative and never pruned.** `crates/loomweave-storage/src/writer.rs` upserts `INSERT ... ON CONFLICT(id) DO UPDATE`; there is no `DELETE FROM entities` on re-index. Vanished and renamed entities' rows persist forever. A `sei TEXT UNIQUE` column on `entities` is therefore unworkable: carrying an SEI across a rename collides with the stale row that still holds it. ## Decision ### 1. SEI token (REQ-C-02) -**`clarion:eid:`** — 128 bits of identity space; `mint_run_id` is the minting run's UUID. +**`loomweave:eid:`** — 128 bits of identity space; `mint_run_id` is the minting run's UUID. The correct model is that **SEI allocation is stateful**: the matcher carries-or-mints by reading the persisted `sei_bindings` table. Reproducibility of the SEI *value* comes from that persisted binding, **not** from re-deriving the token as a pure function of the entity. Consequences: - **Collision-free under locator reuse.** A reused locator is only ever *minted* (never carried — the matcher mints precisely when it cannot confidently match), and minting happens in a later run with a different `mint_run_id` → a different token. - **Unique within a run.** Locators are unique per run, so two entities of the same run cannot collide. -- **No time/RNG component.** `run_id` is an already-allocated per-run UUID — no ad-hoc RNG (which Clarion's determinism posture forbids), and not time-ordered, so the SEI conformance oracle (SEI spec §8) need not assume a time-ordered token. -- **Determinism boundary (state this explicitly so it is not regressed).** Clarion's byte-identical-run guarantee (seeded RNG, `temperature: 0`, `RecordingProvider`) covers entity/edge/finding **state**. It does **not** extend to identity **values**: two from-scratch runs with different `run_id`s mint different SEIs for a brand-new entity. This is correct — in a real re-index the prior binding is *carried*, never re-minted. +- **No time/RNG component.** `run_id` is an already-allocated per-run UUID — no ad-hoc RNG (which Loomweave's determinism posture forbids), and not time-ordered, so the SEI conformance oracle (SEI spec §8) need not assume a time-ordered token. +- **Determinism boundary (state this explicitly so it is not regressed).** Loomweave's byte-identical-run guarantee (seeded RNG, `temperature: 0`, `RecordingProvider`) covers entity/edge/finding **state**. It does **not** extend to identity **values**: two from-scratch runs with different `run_id`s mint different SEIs for a brand-new entity. This is correct — in a real re-index the prior binding is *carried*, never re-minted. SEI is **opaque** on the wire and in storage; consumers MUST NOT parse it (the same discipline ADR-003 already applies to the entity id). @@ -65,18 +65,18 @@ Orphaning is a **`status` flip**, never a row deletion or collision. The read pa ### 4. Reserved locator namespace -The **`clarion:eid:` prefix is reserved** — no plugin locator may occupy it. This is what lets `resolve(locator)` reject an SEI-shaped input (SEI brief REQ-F-02): a colon-count check is insufficient because an SEI `clarion:eid:` has the same two colons a locator does, so the rejection MUST key on the reserved prefix. The fail-closed rejection is what makes the idempotent, resumable cutover backfill safe — an already-migrated SEI is rejected, never mis-resolved. +The **`loomweave:eid:` prefix is reserved** — no plugin locator may occupy it. This is what lets `resolve(locator)` reject an SEI-shaped input (SEI brief REQ-F-02): a colon-count check is insufficient because an SEI `loomweave:eid:` has the same two colons a locator does, so the rejection MUST key on the reserved prefix. The fail-closed rejection is what makes the idempotent, resumable cutover backfill safe — an already-migrated SEI is rejected, never mis-resolved. ## Consequences - **Migrations.** `0004_sei_prior_index.sql` adds the last-run snapshot side table (`locator → body_hash + signature`; **no SEI column** — shape-independent, shippable before lock). `0005_sei.sql` adds `sei_bindings` + `sei_lineage` and a plain `entities.signature` (no `entities.sei`). `CURRENT_SCHEMA_VERSION` advances 3 → 4 → 5; migrations stack (the in-place edit policy retired at the 1.0 publish). - **Plugin manifest.** Gains optional `signature_schemas` (per-kind) and `signature_schema_version`. Plugins that emit no signatures degrade to the no-signature move case. -- **HTTP read API.** Gains `resolve` / `resolve_sei` / `lineage` (and a batch resolve), and a `_capabilities` flag `sei: { supported: true, version: 1 }` so consumers degrade against a pre-SEI Clarion. +- **HTTP read API.** Gains `resolve` / `resolve_sei` / `lineage` (and a batch resolve), and a `_capabilities` flag `sei: { supported: true, version: 1 }` so consumers degrade against a pre-SEI Loomweave. - **MCP surface.** All tool responses returning an entity id also carry `sei` (via the binding join) — no "MCP locator exception" (SEI brief invariant §4). - **Determinism tests.** A new test asserts a back-to-back unchanged re-run **carries** (never re-mints) every SEI. The existing byte-identical-state guarantee is explicitly scoped to exclude identity values (documented in code). - **Supersession.** Per SEI spec §0.2, ADR-003's "the derived id *is* the identity" is demoted — that string is now the *locator*; and ADR-018's qualname heuristics are subsumed *as an identity mechanism* (their reconciliation role for Wardline qualnames is unchanged). Neither ADR file is rewritten (ADRs are immutable once Accepted); this ADR records the demotion. -- **Federation.** Passes `loom.md` §3–§5: SEI is enrich-only connective tissue, minted and resolved in one authority (Clarion), with no shared runtime/store/registry. The reserved-prefix and opacity rules keep consumers from coupling to the token's internal form. +- **Federation.** Passes `weft.md` §3–§5: SEI is enrich-only connective tissue, minted and resolved in one authority (Loomweave), with no shared runtime/store/registry. The reserved-prefix and opacity rules keep consumers from coupling to the token's internal form. -## Loom vocabulary verdict (per ADR index acceptance rule) +## Weft vocabulary verdict (per ADR index acceptance rule) -`SEI` and `locator` are **cross-product-visible** terms (Wardline, Filigree, and `legis` all consume SEI; `locator` is the demoted address). Verdict: **`no clash`** — both are new suite-wide terms introduced by the SEI standard with a single, identical meaning across all four subsystems; no sibling uses either word for a different concept. Both are registered in [`docs/suite/glossary.md`](../../suite/glossary.md) with this ADR as authority. SEI's authority is the SEI standard (Wardline specs tree) for the suite-wide definition; this ADR is Clarion's implementing authority for the token form, persistence, and reserved namespace. +`SEI` and `locator` are **cross-product-visible** terms (Wardline, Filigree, and `legis` all consume SEI; `locator` is the demoted address). Verdict: **`no clash`** — both are new suite-wide terms introduced by the SEI standard with a single, identical meaning across all four subsystems; no sibling uses either word for a different concept. Both are registered in [`docs/suite/glossary.md`](../../suite/glossary.md) with this ADR as authority. SEI's authority is the SEI standard (Wardline specs tree) for the suite-wide definition; this ADR is Loomweave's implementing authority for the token form, persistence, and reserved namespace. diff --git a/docs/clarion/adr/ADR-039-llm-provider-pivot-openrouter-cli.md b/docs/loomweave/adr/ADR-039-llm-provider-pivot-openrouter-cli.md similarity index 76% rename from docs/clarion/adr/ADR-039-llm-provider-pivot-openrouter-cli.md rename to docs/loomweave/adr/ADR-039-llm-provider-pivot-openrouter-cli.md index 8172d5de..16239bf1 100644 --- a/docs/clarion/adr/ADR-039-llm-provider-pivot-openrouter-cli.md +++ b/docs/loomweave/adr/ADR-039-llm-provider-pivot-openrouter-cli.md @@ -3,14 +3,14 @@ **Status**: Accepted **Date**: 2026-06-02 **Deciders**: qacona@gmail.com (with Claude) -**Context**: `CON-ANTHROPIC-01` (requirements.md) baselined Clarion's LLM provider as **Anthropic only** for v0.1, on the premise that Anthropic's explicit four-`cache_control`-breakpoint prompt caching was the mechanism that made elspeth-scale cost tractable, and that any other provider "either loses caching advantage or requires prompt-protocol refactoring (v0.3+)." The implementation diverged: `crates/clarion-core/src/llm_provider.rs` ships **four providers, none of which is a native Anthropic SDK provider**, and all of which declare `CachingModel::OpenAiChatCompletions`. This ADR records the pivot and supersedes `CON-ANTHROPIC-01`. +**Context**: `CON-ANTHROPIC-01` (requirements.md) baselined Loomweave's LLM provider as **Anthropic only** for v0.1, on the premise that Anthropic's explicit four-`cache_control`-breakpoint prompt caching was the mechanism that made elspeth-scale cost tractable, and that any other provider "either loses caching advantage or requires prompt-protocol refactoring (v0.3+)." The implementation diverged: `crates/loomweave-core/src/llm_provider.rs` ships **four providers, none of which is a native Anthropic SDK provider**, and all of which declare `CachingModel::OpenAiChatCompletions`. This ADR records the pivot and supersedes `CON-ANTHROPIC-01`. **Relates to**: [ADR-030](./ADR-030-on-demand-summary-scope.md) (on-demand summary scope; NFR-COST-01/03 deferral), [ADR-007](./ADR-007-summary-cache-key.md) (summary-cache 5-tuple — unchanged by this ADR). ## Summary Three decisions: -1. **Clarion's `LlmProvider` implementations are OpenRouter (live HTTP) + two CLI bridges + a recording provider — not a native Anthropic provider.** The shipped set in `llm_provider.rs` is: +1. **Loomweave's `LlmProvider` implementations are OpenRouter (live HTTP) + two CLI bridges + a recording provider — not a native Anthropic provider.** The shipped set in `llm_provider.rs` is: - `RecordingProvider` — replay/testing (record-and-replay for deterministic tests). - `OpenRouterProvider` — the primary live provider; OpenAI-compatible chat-completions HTTP against OpenRouter. Anthropic models remain reachable as routed model strings (e.g. `anthropic/claude-sonnet-4.6`), but via OpenRouter, not a native Anthropic SDK. - `CodexCliProvider` — OpenAI Codex CLI subprocess bridge. @@ -26,7 +26,7 @@ Supersede `CON-ANTHROPIC-01`. The provider posture of record is: an OpenAI-compa **Positive** - Provider portability is real, not aspirational: OpenRouter exposes many vendors (including Anthropic models) behind one OpenAI-compatible surface, so a model swap is configuration. -- CLI bridges let an operator drive Clarion through an already-authenticated local Codex/Claude CLI without managing a separate API key path. +- CLI bridges let an operator drive Loomweave through an already-authenticated local Codex/Claude CLI without managing a separate API key path. - Testability is unchanged: `RecordingProvider` still backs deterministic replay (REQ-ANALYZE-07). **Negative / tradeoff (the cost-caching point CON-ANTHROPIC-01 was protecting)** diff --git a/docs/clarion/adr/ADR-040-semantic-search-embeddings.md b/docs/loomweave/adr/ADR-040-semantic-search-embeddings.md similarity index 53% rename from docs/clarion/adr/ADR-040-semantic-search-embeddings.md rename to docs/loomweave/adr/ADR-040-semantic-search-embeddings.md index 204e24a7..30bf8d22 100644 --- a/docs/clarion/adr/ADR-040-semantic-search-embeddings.md +++ b/docs/loomweave/adr/ADR-040-semantic-search-embeddings.md @@ -4,18 +4,18 @@ **Date**: 2026-06-02 **Deciders**: john@pgpl.net (with Claude) **Context**: WS5b (advanced MCP queries) splits two capabilities out of the WS5 stateless catalogue that need infrastructure beyond a catalog query: `search_semantic` (this ADR) and `find_dead_code` (no ADR — pure graph query). `search_semantic` ranks entities by embedding similarity to a natural-language query, so it needs an embedding provider, vector storage, and a similarity scan. This ADR records the opt-in posture, the provider abstraction, the storage location, and the cost-governance hook. -**Relates to**: [ADR-005](./ADR-005-clarion-dir-tracking.md) (git-committable `.clarion/` — this ADR **extends** its gitignore list by its own authority; ADR-005 is unchanged), [ADR-007](./ADR-007-summary-cache-key.md) (content-hash cache invalidation — mirrored), [ADR-030](./ADR-030-on-demand-summary-scope.md) (on-demand / policy-engine cost posture), [ADR-039](./ADR-039-llm-provider-pivot-openrouter-cli.md) (the `LlmProvider` abstraction this mirrors). -**Glossary verdict**: **no clash** — `search_semantic`, `EmbeddingProvider`, and `entity_embeddings` are Clarion-local; no sibling product uses these terms. No `glossary.md` change required. +**Relates to**: [ADR-005](./ADR-005-loomweave-dir-tracking.md) (git-committable `.loomweave/` — this ADR **extends** its gitignore list by its own authority; ADR-005 is unchanged), [ADR-007](./ADR-007-summary-cache-key.md) (content-hash cache invalidation — mirrored), [ADR-030](./ADR-030-on-demand-summary-scope.md) (on-demand / policy-engine cost posture), [ADR-039](./ADR-039-llm-provider-pivot-openrouter-cli.md) (the `LlmProvider` abstraction this mirrors). +**Glossary verdict**: **no clash** — `search_semantic`, `EmbeddingProvider`, and `entity_embeddings` are Loomweave-local; no sibling product uses these terms. No `glossary.md` change required. ## Summary Five decisions. -1. **Opt-in, off by default (local-first doctrine).** Embeddings are disabled by default, exactly like `llm_policy`. Loom is local-first and single-binary; nothing here makes a hosted service *required*. When semantic search is off, `search_semantic` returns an honest `result_kind: "not_enabled"` with a missing-signal note — never a faked or empty-as-if-complete result. Config lives under `semantic_search:` (`enabled`, `allow_live_provider`, `provider`, `model_id`, `dimensions`, `endpoint_url`, `api_key_env`, `timeout_seconds`, `session_token_ceiling`). +1. **Opt-in, off by default (local-first doctrine).** Embeddings are disabled by default, exactly like `llm_policy`. Weft is local-first and single-binary; nothing here makes a hosted service *required*. When semantic search is off, `search_semantic` returns an honest `result_kind: "not_enabled"` with a missing-signal note — never a faked or empty-as-if-complete result. Config lives under `semantic_search:` (`enabled`, `allow_live_provider`, `provider`, `model_id`, `dimensions`, `endpoint_url`, `api_key_env`, `timeout_seconds`, `session_token_ceiling`). -2. **`EmbeddingProvider` trait — the trait, not the choice, is load-bearing (D-WS5b-1 resolved).** `clarion_core::embedding_provider::EmbeddingProvider` mirrors `LlmProvider`: `embed(&[String]) -> Vec>`, `dimensions()`, `model_id()`, `estimate_tokens()`. Two impls ship: `RecordingEmbeddingProvider` (deterministic tests, like `RecordingProvider`) and `ApiEmbeddingProvider` (OpenAI-compatible `/embeddings` endpoint — OpenAI / Voyage / Cohere-class — opt-in + key, honest degrade when key/network absent). A bundled local-model impl (`candle`/`ort`) is **deferred follow-on work behind the same trait**: the API-endpoint impl ships first so a network/key is opt-in, and the local impl is a second provider, not a re-architecture. +2. **`EmbeddingProvider` trait — the trait, not the choice, is load-bearing (D-WS5b-1 resolved).** `loomweave_core::embedding_provider::EmbeddingProvider` mirrors `LlmProvider`: `embed(&[String]) -> Vec>`, `dimensions()`, `model_id()`, `estimate_tokens()`. Two impls ship: `RecordingEmbeddingProvider` (deterministic tests, like `RecordingProvider`) and `ApiEmbeddingProvider` (OpenAI-compatible `/embeddings` endpoint — OpenAI / Voyage / Cohere-class — opt-in + key, honest degrade when key/network absent). A bundled local-model impl (`candle`/`ort`) is **deferred follow-on work behind the same trait**: the API-endpoint impl ships first so a network/key is opt-in, and the local impl is a second provider, not a re-architecture. -3. **Sidecar storage protects the committed DB (extends ADR-005).** Embeddings are large (≈ entities × dim × 4 bytes) and rebuildable, so they must **not** bloat the committed `.clarion/clarion.db`. They live in a separate, git-ignored `.clarion/embeddings.db` (`entity_embeddings(entity_id, content_hash, model_id, dim, vec BLOB, cost_usd, tokens_input, created_at, last_accessed_at)`, PK `(entity_id, content_hash, model_id)`). Because it is a private rebuildable cache, the sidecar carries its own self-contained `CREATE TABLE IF NOT EXISTS` schema (not a row in the committed-DB migration chain) and is exempt from the `application_id` foreign-DB guard. ADR-005's gitignore default list is extended with `embeddings.db` (WAL files already covered by `*.db-wal`/`*.db-shm`). **ADR-005 itself is not edited** — this ADR adds the entry by its own authority. +3. **Sidecar storage protects the committed DB (extends ADR-005).** Embeddings are large (≈ entities × dim × 4 bytes) and rebuildable, so they must **not** bloat the committed `.loomweave/loomweave.db`. They live in a separate, git-ignored `.loomweave/embeddings.db` (`entity_embeddings(entity_id, content_hash, model_id, dim, vec BLOB, cost_usd, tokens_input, created_at, last_accessed_at)`, PK `(entity_id, content_hash, model_id)`). Because it is a private rebuildable cache, the sidecar carries its own self-contained `CREATE TABLE IF NOT EXISTS` schema (not a row in the committed-DB migration chain) and is exempt from the `application_id` foreign-DB guard. ADR-005's gitignore default list is extended with `embeddings.db` (WAL files already covered by `*.db-wal`/`*.db-shm`). **ADR-005 itself is not edited** — this ADR adds the entry by its own authority. 4. **Bounded exact cosine scan (no ANN dependency at v1).** Stored f32 vectors are scanned with exact cosine over the candidate set (scope-pre-filtered), capped + paginated. Only embeddings whose `content_hash` matches the entity's *current* hash are considered, so stale vectors never surface (freshness, exactly like the summary cache). A scalable ANN backend (sqlite-vec / HNSW) is a **named follow-on, flagged not pre-built** — added only if the exact scan misses the latency bar (NFR-PERF-02). @@ -28,21 +28,21 @@ Ship `search_semantic` as an opt-in tool behind the `EmbeddingProvider` trait, w ## Consequences **Positive** -- Local-first preserved: off by default; honest degrade; no hosted service required for any other Clarion semantics (enrich-only on the product's own surface). -- Committed `.clarion/clarion.db` stays unbloated; embeddings are rebuildable and never committed. +- Local-first preserved: off by default; honest degrade; no hosted service required for any other Loomweave semantics (enrich-only on the product's own surface). +- Committed `.loomweave/loomweave.db` stays unbloated; embeddings are rebuildable and never committed. - Content-hash keying gives free invalidation: a changed entity's stale vector is ignored until re-embedded. - The trait isolates the provider decision: swapping API ↔ local model is a new impl, not a rewrite. **Negative / tradeoff** - Exact cosine scan is O(N·dim) per query; at very large N this may miss NFR-PERF-02 and require the ANN follow-on. Flagged, not pre-built. - The API-endpoint default requires an external embedding service + key when enabled; the local-model alternative (no network) is not yet shipped. -- Two storage files (`clarion.db` + `embeddings.db`) to manage operationally; the sidecar is rebuildable, so loss is non-fatal. +- Two storage files (`loomweave.db` + `embeddings.db`) to manage operationally; the sidecar is rebuildable, so loss is non-fatal. ## Status of delivery (2026-06-04) -Shipped and tested at acceptance: the `EmbeddingProvider` trait + `RecordingEmbeddingProvider` + `ApiEmbeddingProvider` (clarion-core), `semantic_search:` config (off by default), the `.clarion/embeddings.db` sidecar (`clarion-storage::embeddings`), the `search_semantic` MCP tool (honest-degrade + bounded cosine + content-hash freshness), `serve` provider construction (`build_embedding_provider` → `with_semantic_search`), the gitignore entry, and this ADR. The read + enable path is complete. +Shipped and tested at acceptance: the `EmbeddingProvider` trait + `RecordingEmbeddingProvider` + `ApiEmbeddingProvider` (loomweave-core), `semantic_search:` config (off by default), the `.loomweave/embeddings.db` sidecar (`loomweave-storage::embeddings`), the `search_semantic` MCP tool (honest-degrade + bounded cosine + content-hash freshness), `serve` provider construction (`build_embedding_provider` → `with_semantic_search`), the gitignore entry, and this ADR. The read + enable path is complete. -Delivery update: `clarion analyze` now runs an opt-in post-commit embedding population pass when `semantic_search.enabled` has a configured provider. It embeds content-hashed entities into `.clarion/embeddings.db`, skips fresh `(entity_id, content_hash, model_id)` rows, and enforces `semantic_search.session_token_ceiling`. +Delivery update: `loomweave analyze` now runs an opt-in post-commit embedding population pass when `semantic_search.enabled` has a configured provider. It embeds content-hashed entities into `.loomweave/embeddings.db`, skips fresh `(entity_id, content_hash, model_id)` rows, and enforces `semantic_search.session_token_ceiling`. ## Follow-up diff --git a/docs/clarion/adr/ADR-041-resume-is-idempotent-reemit.md b/docs/loomweave/adr/ADR-041-resume-is-idempotent-reemit.md similarity index 88% rename from docs/clarion/adr/ADR-041-resume-is-idempotent-reemit.md rename to docs/loomweave/adr/ADR-041-resume-is-idempotent-reemit.md index 52280b8a..26faf42d 100644 --- a/docs/clarion/adr/ADR-041-resume-is-idempotent-reemit.md +++ b/docs/loomweave/adr/ADR-041-resume-is-idempotent-reemit.md @@ -3,22 +3,22 @@ **Status**: Accepted **Date**: 2026-06-04 **Deciders**: qacona@gmail.com -**Context**: `clarion analyze --resume RUN_ID` shipped as a run-lifecycle and +**Context**: `loomweave analyze --resume RUN_ID` shipped as a run-lifecycle and finding-emission repair path, while older design text still promised phase/file checkpoint recovery. ## Summary -Clarion's v1.x `--resume RUN_ID` reopens the existing `runs` row, re-walks the +Loomweave's v1.x `--resume RUN_ID` reopens the existing `runs` row, re-walks the analysis idempotently, and emits Filigree findings with `mark_unseen=false`. It is not a durable phase/file checkpoint recovery mechanism. This ADR amends ADR-005 and ADR-011 where they describe `partial.json` or restart-at-first-uncommitted-file behavior. The SQLite WAL and writer-actor -contract remains: an unclean shutdown must not corrupt `.clarion/clarion.db`, +contract remains: an unclean shutdown must not corrupt `.loomweave/loomweave.db`, and committed rows survive. What changes is the resume promise: after a crash, the operator can safely re-run the same run id without Filigree seen/unseen -flapping, but Clarion does not guarantee it will skip already completed +flapping, but Loomweave does not guarantee it will skip already completed phases/files from that interrupted run. ## Decision @@ -54,7 +54,7 @@ scheduler subsystem. committed data and prevent corruption. - Resume may repeat structural extraction, plugin work, and provider calls unless existing content-hash caches independently avoid work. -- Operators must not rely on `.clarion/runs//partial.json` or +- Operators must not rely on `.loomweave/runs//partial.json` or `checkpoints.jsonl`; neither is part of the v1.x contract. - Future checkpoint recovery can be added as a separate capability without changing Filigree's `scan_run_id` semantics. diff --git a/docs/clarion/adr/ADR-042-hmac-freshness-and-replay-window.md b/docs/loomweave/adr/ADR-042-hmac-freshness-and-replay-window.md similarity index 87% rename from docs/clarion/adr/ADR-042-hmac-freshness-and-replay-window.md rename to docs/loomweave/adr/ADR-042-hmac-freshness-and-replay-window.md index 4337316a..17ae12e0 100644 --- a/docs/clarion/adr/ADR-042-hmac-freshness-and-replay-window.md +++ b/docs/loomweave/adr/ADR-042-hmac-freshness-and-replay-window.md @@ -11,19 +11,19 @@ identity message shape. ## Summary -Clarion's protected HTTP routes keep ADR-034's preferred Loom component HMAC -mode, but every signed request now carries `X-Loom-Timestamp` and -`X-Loom-Nonce`. The HMAC canonical message is: +Loomweave's protected HTTP routes keep ADR-034's preferred Weft component HMAC +mode, but every signed request now carries `X-Weft-Timestamp` and +`X-Weft-Nonce`. The HMAC canonical message is: ```text METHOD PATH_AND_QUERY SHA256_HEX_OF_REQUEST_BODY -X_LOOM_TIMESTAMP -X_LOOM_NONCE +X_WEFT_TIMESTAMP +X_WEFT_NONCE ``` -Clarion accepts timestamps inside a five-minute skew window and rejects reuse of +Loomweave accepts timestamps inside a five-minute skew window and rejects reuse of the same nonce inside the process-local replay cache for that window. Missing, malformed, stale, replayed, or wrongly signed requests all return the existing `401 UNAUTHENTICATED` envelope. @@ -32,7 +32,7 @@ malformed, stale, replayed, or wrongly signed requests all return the existing - Replace the local HMAC implementation with the `hmac` crate over `Sha256`. - Replace local byte-loop equality with `subtle::ConstantTimeEq`. -- Require `X-Loom-Timestamp` as Unix seconds and `X-Loom-Nonce` as a non-empty +- Require `X-Weft-Timestamp` as Unix seconds and `X-Weft-Nonce` as a non-empty opaque string up to 128 bytes whenever `identity_token_env` is active. - Include timestamp and nonce in the canonical HMAC message after the body hash. - Maintain an in-memory, process-local nonce cache with a five-minute freshness diff --git a/docs/clarion/adr/ADR-043-edge-reanalysis-replacement.md b/docs/loomweave/adr/ADR-043-edge-reanalysis-replacement.md similarity index 100% rename from docs/clarion/adr/ADR-043-edge-reanalysis-replacement.md rename to docs/loomweave/adr/ADR-043-edge-reanalysis-replacement.md diff --git a/docs/clarion/adr/README.md b/docs/loomweave/adr/README.md similarity index 82% rename from docs/clarion/adr/README.md rename to docs/loomweave/adr/README.md index 65d73792..3bafec9d 100644 --- a/docs/clarion/adr/README.md +++ b/docs/loomweave/adr/README.md @@ -1,6 +1,6 @@ -# Clarion ADR Index +# Loomweave ADR Index -This folder is the canonical home for authored Clarion architecture decision records. +This folder is the canonical home for authored Loomweave architecture decision records. ## Authored ADRs @@ -10,17 +10,17 @@ This folder is the canonical home for authored Clarion architecture decision rec | [ADR-002](./ADR-002-plugin-transport-json-rpc.md) | Plugin transport: Content-Length framed JSON-RPC subprocess | Accepted | | [ADR-003](./ADR-003-entity-id-scheme.md) | Entity ID scheme: symbolic canonical names | Accepted | | [ADR-004](./ADR-004-finding-exchange-format.md) | Finding-exchange format: Filigree-native intake | Accepted | -| [ADR-005](./ADR-005-clarion-dir-tracking.md) | `.clarion/` git-committable by default; DB included, run logs excluded | Accepted; amended by ADR-041 | +| [ADR-005](./ADR-005-loomweave-dir-tracking.md) | `.loomweave/` git-committable by default; DB included, run logs excluded | Accepted; amended by ADR-041 | | [ADR-006](./ADR-006-clustering-algorithm.md) | Clustering algorithm — Leiden on imports+calls subgraph; fallback amended by ADR-032 | Accepted; amended | | [ADR-007](./ADR-007-summary-cache-key.md) | Summary cache key — 5-part composite with TTL backstop and churn-eager invalidation | Accepted | | [ADR-011](./ADR-011-writer-actor-concurrency.md) | Writer-actor concurrency with per-N-files transactions; `--shadow-db` opt-in | Accepted; amended by ADR-041 | | [ADR-012](./ADR-012-http-auth-default.md) | HTTP read-API authentication — UDS default with token fallback | Superseded for ADR-014 registry-backend API | | [ADR-013](./ADR-013-pre-ingest-secret-scanner.md) | Pre-ingest secret scanner with LLM-dispatch block | Accepted | | [ADR-014](./ADR-014-filigree-registry-backend.md) | Filigree `registry_backend` flag and pluggable `RegistryProtocol` | Accepted; partially extended by ADR-034 | -| [ADR-015](./ADR-015-wardline-filigree-emission.md) | Wardline→Filigree emission ownership — Clarion-side SARIF translator (v0.1), native Wardline emitter (v0.2) | Accepted | +| [ADR-015](./ADR-015-wardline-filigree-emission.md) | Wardline→Filigree emission ownership — Loomweave-side SARIF translator (v0.1), native Wardline emitter (v0.2) | Accepted | | [ADR-016](./ADR-016-observation-transport.md) | Observation transport — MCP-spawn (v0.1), Filigree HTTP endpoint (v0.2) | Accepted | | [ADR-017](./ADR-017-severity-and-dedup.md) | Severity mapping, rule-ID round-trip, and dedup policy | Accepted | -| [ADR-018](./ADR-018-identity-reconciliation.md) | Identity reconciliation — Clarion translates; Wardline owns its qualnames; descriptor read (direct REGISTRY import retired, Rev 3) | Accepted | +| [ADR-018](./ADR-018-identity-reconciliation.md) | Identity reconciliation — Loomweave translates; Wardline owns its qualnames; descriptor read (direct REGISTRY import retired, Rev 3) | Accepted | | [ADR-021](./ADR-021-plugin-authority-hybrid.md) | Plugin authority model: hybrid (declared capabilities + core-enforced minimums) | Accepted | | [ADR-022](./ADR-022-core-plugin-ontology.md) | Core/plugin ontology ownership boundary | Accepted | | [ADR-023](./ADR-023-tooling-baseline.md) | Rust + Python tooling baseline (edition 2024, pedantic, cargo-deny, nextest, CI; ruff + mypy-strict + pre-commit) | Accepted | @@ -29,18 +29,18 @@ This folder is the canonical home for authored Clarion architecture decision rec | [ADR-026](./ADR-026-containment-wire-and-edge-identity.md) | Containment wire shape and edge identity (top-level `edges` field; drop `edges.id` column; per-kind `source_byte_start/end` contract) | Accepted; amended by ADR-043 | | [ADR-027](./ADR-027-ontology-version-semver.md) | Ontology version semver policy (MAJOR/MINOR/PATCH semantics for `[ontology].ontology_version`; clarifies ADR-022) | Accepted | | [ADR-028](./ADR-028-edge-confidence-tiers.md) | Edge confidence tiers (`resolved` / `ambiguous` / `inferred`); MCP queries default to `>= resolved`; inferred edges lazy-computed at query time | Accepted | -| [ADR-029](./ADR-029-entity-associations-binding.md) | Entity associations — Filigree-side `entity_associations` table; `add_entity_association` MCP tool on Filigree; `issues_for` MCP tool on Clarion; WP9 split into A (binding, v0.1) and B (findings emission) | Accepted | +| [ADR-029](./ADR-029-entity-associations-binding.md) | Entity associations — Filigree-side `entity_associations` table; `add_entity_association` MCP tool on Filigree; `issues_for` MCP tool on Loomweave; WP9 split into A (binding, v0.1) and B (findings emission) | Accepted | | [ADR-030](./ADR-030-on-demand-summary-scope.md) | On-demand summary scope — narrows WP6 to MCP-driven `summary(id)`; 5-tuple cache key unchanged; module/subsystem aggregation deferred to v0.2 | Accepted | | [ADR-031](./ADR-031-schema-validation-policy.md) | Schema-validation policy — CHECK on closed core-owned vocabularies (`findings.{kind,severity,status}`, `runs.status`); writer-actor + manifest are the only enforcement layer for plugin-extensible vocabularies (`entities.kind`, `edges.kind`) | Accepted | | [ADR-032](./ADR-032-weighted-components-clustering-fallback.md) | Weighted-components clustering fallback naming | Accepted | | [ADR-033](./ADR-033-v1.0-distribution.md) | v1.0 distribution via GitHub Releases (binary matrix + Python sdist; promote to crates.io/PyPI at v2.0) | Accepted | | [ADR-034](./ADR-034-federation-http-read-api-hardening.md) | Federation HTTP read API hardening — bearer auth, batch resolution, `BRIEFING_BLOCKED`, instance ID | Accepted; amended by ADR-042 | | [ADR-035](./ADR-035-operational-tuning-discipline.md) | Operational tuning discipline — declared basis / override surface / retune trigger / coupling per constant; file-LOC + crate-boundary budgets; CI lint gate | Accepted | -| [ADR-036](./ADR-036-wardline-taint-fact-store.md) | Clarion as Wardline taint-fact store — `wardline_taint_facts` table + `/api/wardline/*` routes; first read+write HTTP surface (optional writer-actor, default off); passes loom.md §3–§5 (ADR, not asterisk) | Accepted | -| [ADR-037](./ADR-037-shared-error-vocabulary.md) | Shared error vocabulary (`clarion-core::errors`) — two typed enums (`HttpErrorCode`, `McpErrorCode`) as single source of truth; wire spelling unchanged on both surfaces; relates to ADR-034 | Accepted | -| [ADR-038](./ADR-038-sei-token-and-signature.md) | SEI token scheme (`clarion:eid:`), signature schema (plugin-declared versioned JSON), and identity persistence (`sei_bindings` table, not an `entities` column); reserves the `clarion:eid:` locator namespace; resolves SEI-standard REQ-C-01/REQ-C-02; demotes ADR-003 id to *locator* | Accepted | +| [ADR-036](./ADR-036-wardline-taint-fact-store.md) | Loomweave as Wardline taint-fact store — `wardline_taint_facts` table + `/api/wardline/*` routes; first read+write HTTP surface (optional writer-actor, default off); passes weft.md §3–§5 (ADR, not asterisk) | Accepted | +| [ADR-037](./ADR-037-shared-error-vocabulary.md) | Shared error vocabulary (`loomweave-core::errors`) — two typed enums (`HttpErrorCode`, `McpErrorCode`) as single source of truth; wire spelling unchanged on both surfaces; relates to ADR-034 | Accepted | +| [ADR-038](./ADR-038-sei-token-and-signature.md) | SEI token scheme (`loomweave:eid:`), signature schema (plugin-declared versioned JSON), and identity persistence (`sei_bindings` table, not an `entities` column); reserves the `loomweave:eid:` locator namespace; resolves SEI-standard REQ-C-01/REQ-C-02; demotes ADR-003 id to *locator* | Accepted | | [ADR-039](./ADR-039-llm-provider-pivot-openrouter-cli.md) | LLM provider pivot — OpenRouter (live HTTP) + Codex/Claude CLI bridges + recording provider; `CachingModel::OpenAiChatCompletions` (not Anthropic four-`cache_control`-breakpoint); supersedes CON-ANTHROPIC-01 | Accepted | -| [ADR-040](./ADR-040-semantic-search-embeddings.md) | Semantic search (`search_semantic`) — opt-in `EmbeddingProvider` trait (recording + API-endpoint impls), git-ignored `.clarion/embeddings.db` sidecar keyed `(entity_id, content_hash, model_id)` (extends ADR-005's gitignore list), bounded exact cosine scan, policy-engine cost governance | Accepted | +| [ADR-040](./ADR-040-semantic-search-embeddings.md) | Semantic search (`search_semantic`) — opt-in `EmbeddingProvider` trait (recording + API-endpoint impls), git-ignored `.loomweave/embeddings.db` sidecar keyed `(entity_id, content_hash, model_id)` (extends ADR-005's gitignore list), bounded exact cosine scan, policy-engine cost governance | Accepted | | [ADR-041](./ADR-041-resume-is-idempotent-reemit.md) | Analyze resume is idempotent re-emit, not checkpoint recovery; amends ADR-005/ADR-011 resume language | Accepted | | [ADR-042](./ADR-042-hmac-freshness-and-replay-window.md) | HMAC freshness and replay window — timestamp + nonce headers, crate-backed HMAC, process-local replay cache | Accepted | | [ADR-043](./ADR-043-edge-reanalysis-replacement.md) | Edge reanalysis replacement — per-source-file anchored-edge replacement and edge metadata upsert; amends ADR-026 | Accepted | @@ -61,7 +61,7 @@ The following decisions are still backlog items rather than authored ADR files. The priorities and scope implied by these ADRs are committed in [../../implementation/v0.1-scope-plans/v0.1-scope-commitments.md](../../implementation/v0.1-scope-plans/v0.1-scope-commitments.md). The ADR authoring sprint is staged against that memo. -## ADR acceptance criteria — Loom vocabulary discipline +## ADR acceptance criteria — Weft vocabulary discipline ADRs introducing cross-product-visible field names must update [`docs/suite/glossary.md`](../../suite/glossary.md) before moving from Proposed to Accepted, with one of three explicit verdicts: diff --git a/docs/operator/README.md b/docs/operator/README.md index ae0714d1..9ddbd310 100644 --- a/docs/operator/README.md +++ b/docs/operator/README.md @@ -1,6 +1,6 @@ # Operator Notes -Practical notes for configuring and running Clarion. +Practical notes for configuring and running Loomweave. - [Getting started](./getting-started.md) — single-flow walkthrough: install, analyse a small repo, connect an MCP client, ask three questions, verify @@ -9,15 +9,15 @@ Practical notes for configuring and running Clarion. headers, and token-ceiling configuration. - [Coding-agent LLM providers](./coding-agent-llm-providers.md) — Codex CLI and Claude CLI as local-login alternatives to API-key provider wiring. -- [Runtime topology](./runtime-topology.md) — supported `clarion serve` and - `clarion analyze` concurrency against one `.clarion/clarion.db`. +- [Runtime topology](./runtime-topology.md) — supported `loomweave serve` and + `loomweave analyze` concurrency against one `.loomweave/loomweave.db`. - [Secret scanning](./secret-scanning.md) — pre-ingest scanner behavior, baseline false-positive workflow, override confirmation, and audit queries. -- [Guidance](./guidance.md) — authoring guidance sheets with the `clarion +- [Guidance](./guidance.md) — authoring guidance sheets with the `loomweave guidance` CLI, `--match`/`--scope-level`/`--expires` semantics, staleness findings, and the export/import team-sharing workflow. - [Release handoff](./release-handoff.md) — retired - Clarion-owned GitHub ruleset enforcement and current standalone release + Loomweave-owned GitHub ruleset enforcement and current standalone release sequence. - [Federation contracts](../federation/contracts.md) — read-side HTTP contracts consumed by sibling products such as Filigree. diff --git a/docs/operator/clustering.md b/docs/operator/clustering.md index a63d03bf..1ce06146 100644 --- a/docs/operator/clustering.md +++ b/docs/operator/clustering.md @@ -1,12 +1,12 @@ # Clustering Operator Notes -Clarion Phase 3 runs after plugin entity and edge extraction. It reads the +Loomweave Phase 3 runs after plugin entity and edge extraction. It reads the persisted module dependency graph, clusters modules, and writes subsystem -entities plus `in_subsystem` edges back into `.clarion/clarion.db`. +entities plus `in_subsystem` edges back into `.loomweave/loomweave.db`. ## Configuration -`clarion analyze` snapshots the resolved config into `runs.config`. +`loomweave analyze` snapshots the resolved config into `runs.config`. ```yaml analysis: @@ -28,7 +28,7 @@ weight is at least the graph's average positive edge weight; it is deterministic and does not perform Louvain modularity optimisation. `edge_types` may include `imports`, `calls`, or both. `weight_by` is currently `reference_count`. -When `algorithm: leiden` produces zero or one community, Clarion computes the +When `algorithm: leiden` produces zero or one community, Loomweave computes the local `weighted_components` fallback and uses it only if it produces more communities than Leiden. Stored subsystem properties and `runs.stats.clustering.algorithm` record the algorithm actually used, while @@ -74,8 +74,8 @@ not call the LLM provider in v0.1. ## Weak Modularity -Clarion emits a fact finding with rule -`CLA-FACT-CLUSTERING-WEAK-MODULARITY` when clustering succeeds but the +Loomweave emits a fact finding with rule +`LMWV-FACT-CLUSTERING-WEAK-MODULARITY` when clustering succeeds but the modularity score is below `analysis.clustering.weak_modularity_threshold` (default `0.3`; set `0.0` to disable the finding). This means the graph did not separate cleanly into strong communities. Treat it as operator guidance, not a @@ -84,7 +84,7 @@ different config, graph pruning, or an ADR amendment. ## Empty Inputs -If no module dependency edges exist, Clarion emits no subsystems and records: +If no module dependency edges exist, Loomweave emits no subsystems and records: ```json { diff --git a/docs/operator/coding-agent-llm-providers.md b/docs/operator/coding-agent-llm-providers.md index 7d1536aa..8cdec06d 100644 --- a/docs/operator/coding-agent-llm-providers.md +++ b/docs/operator/coding-agent-llm-providers.md @@ -1,6 +1,6 @@ # Coding-Agent LLM Providers -Clarion can run live summary and inferred-edge LLM calls through local coding +Loomweave can run live summary and inferred-edge LLM calls through local coding agent CLIs instead of an HTTP API key. These routes still send prompt content to the agent vendor, so they require the same explicit live-provider opt-in as OpenRouter. @@ -31,14 +31,14 @@ llm_policy: cache_max_age_days: 180 ``` -Run `codex login status` before `clarion serve`. Clarion passes prompts on +Run `codex login status` before `loomweave serve`. Loomweave passes prompts on stdin, uses `--sandbox read-only`, `approval_policy="never"`, `--json`, `--output-last-message`, and `--output-schema`. Token usage is read from Codex -JSONL events when available; otherwise Clarion falls back to local estimates. +JSONL events when available; otherwise Loomweave falls back to local estimates. Set `codex_cli.model` only when you want to force a specific Codex model. When it is `null`, the Codex CLI profile/default model is used. `model_id` is the -Clarion cache-key label for this route, so change it deliberately when changing +Loomweave cache-key label for this route, so change it deliberately when changing the effective model or prompt-routing behavior. ## Claude CLI @@ -63,11 +63,11 @@ llm_policy: cache_max_age_days: 180 ``` -Run `claude auth status` before `clarion serve`. Clarion uses print mode +Run `claude auth status` before `loomweave serve`. Loomweave uses print mode (`-p`), `--output-format json`, `--json-schema`, and stdin prompts. The default -tool list is empty because Clarion already supplies the source excerpt and +tool list is empty because Loomweave already supplies the source excerpt and candidate graph context; add read-only tools only when you intentionally want -Claude Code to inspect the workspace. Clarion also passes an explicit empty MCP +Claude Code to inspect the workspace. Loomweave also passes an explicit empty MCP config, `--strict-mcp-config`, and `--disable-slash-commands` so project/user MCP servers and skills do not inflate this bounded provider call. @@ -79,19 +79,19 @@ internal tool turn and then emits the final JSON result. ## Live Opt-In `llm_policy.enabled: true` is not enough for any live provider. Set -`allow_live_provider: true` in `clarion.yaml`, or launch with: +`allow_live_provider: true` in `loomweave.yaml`, or launch with: ```sh -CLARION_LLM_LIVE=1 clarion serve --path . +LOOMWEAVE_LLM_LIVE=1 loomweave serve --path . ``` Unlike OpenRouter, the CLI routes do not require `OPENROUTER_API_KEY` or an -OpenAI/Anthropic API key in Clarion config. They rely on the local CLI's own +OpenAI/Anthropic API key in Loomweave config. They rely on the local CLI's own login state. ## Prompt Caching And Advanced Interfaces -Clarion already has its own semantic result cache for summaries and inferred +Loomweave already has its own semantic result cache for summaries and inferred edges. Provider prompt caching is separate: it depends on the vendor surface, stable prompt prefixes, model choice, and usage telemetry. @@ -104,18 +104,18 @@ app-server protocol for rich product integrations. ## Shared Agent Prompt Contract -Both CLI providers wrap Clarion's leaf-summary and inferred-edge prompts in the +Both CLI providers wrap Loomweave's leaf-summary and inferred-edge prompts in the same stable provider prompt before invoking the local agent. Future SDK or app-server providers should use `build_coding_agent_provider_prompt()` from -`clarion_core` so all coding-agent surfaces share the same behavior: +`loomweave_core` so all coding-agent surfaces share the same behavior: ```text -Prompt contract: clarion-agent-provider-v1 -You are Clarion's coding-agent LLM provider for repository graph enrichment. -Clarion has already selected the source excerpt, entity metadata, unresolved +Prompt contract: loomweave-agent-provider-v1 +You are Loomweave's coding-agent LLM provider for repository graph enrichment. +Loomweave has already selected the source excerpt, entity metadata, unresolved call sites, and candidate graph context needed for this task. Follow these rules exactly: -1. Use only the evidence inside . Do not inspect additional +1. Use only the evidence inside . Do not inspect additional files, browse, run commands, edit files, or ask follow-up questions. 2. Return exactly one JSON object matching the structured-output schema supplied by the caller. Do not wrap it in Markdown or prose. @@ -124,24 +124,24 @@ Follow these rules exactly: relationships. 4. When evidence is absent, prefer empty strings for optional prose fields and empty arrays for collection fields instead of guessing. -5. Keep stable field names and JSON types; downstream Clarion storage parses the +5. Keep stable field names and JSON types; downstream Loomweave storage parses the response mechanically. ``` SDK providers should pass the same prompt as the user/task content and attach -Clarion's purpose-specific JSON Schema through the SDK's structured-output +Loomweave's purpose-specific JSON Schema through the SDK's structured-output mechanism. Do not replace this with an agentic "go inspect the repo" prompt: -Clarion's MCP path is a bounded graph-enrichment call, not an autonomous code +Loomweave's MCP path is a bounded graph-enrichment call, not an autonomous code review session. Prompt surfaces by provider: - Codex CLI: pass the shared prompt to `codex exec -` on stdin and attach - Clarion's JSON Schema with `--output-schema`. + Loomweave's JSON Schema with `--output-schema`. - Claude CLI: use the short print-mode bootstrap prompt, pass the shared prompt - on stdin, and attach Clarion's JSON Schema with `--json-schema`. + on stdin, and attach Loomweave's JSON Schema with `--json-schema`. - Codex SDK or app-server: put the same shared prompt in the task/user content, - keep the developer/system instruction to "Clarion graph enrichment, JSON only, + keep the developer/system instruction to "Loomweave graph enrichment, JSON only, no repository inspection", and attach the same purpose-specific schema through structured output. - Claude Agent SDK: use the same task prompt and schema, disable tools by @@ -150,8 +150,8 @@ Prompt surfaces by provider: The stable prefix is intentional. It keeps provider prompt-caching viable while the volatile source excerpt, unresolved call sites, and candidates stay inside -the `` block. +the `` block. -Do not use either CLI route as the pre-ingest secret scanner. Clarion's local +Do not use either CLI route as the pre-ingest secret scanner. Loomweave's local secret scanner must continue to run before any prompt content is sent to a live provider. diff --git a/docs/operator/getting-started.md b/docs/operator/getting-started.md index 9fc0422d..d63b15ad 100644 --- a/docs/operator/getting-started.md +++ b/docs/operator/getting-started.md @@ -1,4 +1,4 @@ -# Getting started with Clarion +# Getting started with Loomweave A single-flow walkthrough that takes you from an empty machine to a working consult-mode agent asking real questions about a real codebase. Target time: @@ -6,9 +6,9 @@ consult-mode agent asking real questions about a real codebase. Target time: You will: -1. [Install Clarion + the Python plugin.](#1-install) -2. [Run `clarion analyze` against a small public Python project.](#2-analyze) -3. [Start `clarion serve` and connect an MCP client.](#3-serve) +1. [Install Loomweave + the Python plugin.](#1-install) +2. [Run `loomweave analyze` against a small public Python project.](#2-analyze) +3. [Start `loomweave serve` and connect an MCP client.](#3-serve) 4. [Ask three questions through the MCP tools.](#4-ask) 5. [Verify the secret-scanner block fires on a planted secret.](#5-secret-block) @@ -36,7 +36,7 @@ For step 4's `summary` question you need an OpenRouter API key: export OPENROUTER_API_KEY=sk-or-v1-... ``` -`clarion analyze` (step 2) and the structural MCP tools work without any LLM +`loomweave analyze` (step 2) and the structural MCP tools work without any LLM credentials. The key is only consulted when an MCP client calls `summary(id)` against an entity that does not yet have a cached summary. @@ -44,35 +44,35 @@ against an entity that does not yet have a cached summary. Tagged releases ship a platform archive for the Rust binary and a Python sdist for the language plugin via GitHub Releases (per -[ADR-033](../clarion/adr/ADR-033-v1.0-distribution.md)). Use the source-install +[ADR-033](../loomweave/adr/ADR-033-v1.0-distribution.md)). Use the source-install fallback below only when testing unreleased commits. ```bash -TAG=v1.3.0 -curl -L -o clarion-x86_64-unknown-linux-gnu.tar.gz \ - "https://github.com/tachyon-beep/clarion/releases/download/${TAG}/clarion-x86_64-unknown-linux-gnu.tar.gz" -tar xzf clarion-x86_64-unknown-linux-gnu.tar.gz -install clarion-x86_64-unknown-linux-gnu/clarion ~/.local/bin/ +TAG=v1.0.0 +curl -L -o loomweave-x86_64-unknown-linux-gnu.tar.gz \ + "https://github.com/foundryside-dev/loomweave/releases/download/${TAG}/loomweave-x86_64-unknown-linux-gnu.tar.gz" +tar xzf loomweave-x86_64-unknown-linux-gnu.tar.gz +install loomweave-x86_64-unknown-linux-gnu/loomweave ~/.local/bin/ pipx install \ - "https://github.com/tachyon-beep/clarion/releases/download/${TAG}/clarion-plugin-python-1.3.0.tar.gz" + "https://github.com/foundryside-dev/loomweave/releases/download/${TAG}/loomweave-plugin-python-1.0.0.tar.gz" ``` Source-install fallback: ```bash # Rust core -cargo install --git https://github.com/tachyon-beep/clarion clarion-cli +cargo install --git https://github.com/foundryside-dev/loomweave loomweave-cli -# Python plugin (provides clarion-plugin-python on $PATH) -pipx install git+https://github.com/tachyon-beep/clarion#subdirectory=plugins/python +# Python plugin (provides loomweave-plugin-python on $PATH) +pipx install git+https://github.com/foundryside-dev/loomweave#subdirectory=plugins/python ``` Verify the discovery surface: ```bash -which clarion # e.g. ~/.cargo/bin/clarion -which clarion-plugin-python # e.g. ~/.local/bin/clarion-plugin-python +which loomweave # e.g. ~/.cargo/bin/loomweave +which loomweave-plugin-python # e.g. ~/.local/bin/loomweave-plugin-python ``` ### Verifying release artifacts @@ -81,29 +81,29 @@ Tagged releases publish platform archives, SHA256 files, keyless cosign signatures/certificates, and SLSA provenance. For a downloaded archive: ```bash -sha256sum -c clarion-x86_64-unknown-linux-gnu.tar.gz.sha256 +sha256sum -c loomweave-x86_64-unknown-linux-gnu.tar.gz.sha256 cosign verify-blob \ - --certificate clarion-x86_64-unknown-linux-gnu.tar.gz.pem \ - --signature clarion-x86_64-unknown-linux-gnu.tar.gz.sig \ + --certificate loomweave-x86_64-unknown-linux-gnu.tar.gz.pem \ + --signature loomweave-x86_64-unknown-linux-gnu.tar.gz.sig \ --certificate-identity-regexp 'https://github.com/.+/.github/workflows/release.yml@refs/tags/v.*' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ - clarion-x86_64-unknown-linux-gnu.tar.gz + loomweave-x86_64-unknown-linux-gnu.tar.gz slsa-verifier verify-artifact \ - --provenance-path clarion-rust-binaries.intoto.jsonl \ - --source-uri github.com/tachyon-beep/clarion \ + --provenance-path loomweave-rust-binaries.intoto.jsonl \ + --source-uri github.com/foundryside-dev/loomweave \ --source-tag "$TAG" \ - clarion-x86_64-unknown-linux-gnu.tar.gz + loomweave-x86_64-unknown-linux-gnu.tar.gz ``` The current 1.x release line deliberately does not publish to PyPI or crates.io. GitHub Release assets are the source of truth until public registries are introduced by a later ADR. -**`$PATH` discipline matters.** Clarion's plugin host (per -[ADR-002](../clarion/adr/ADR-002-plugin-transport-json-rpc.md)) discovers -plugins by walking `$PATH` for executables matching `clarion-plugin-*`. If +**`$PATH` discipline matters.** Loomweave's plugin host (per +[ADR-002](../loomweave/adr/ADR-002-plugin-transport-json-rpc.md)) discovers +plugins by walking `$PATH` for executables matching `loomweave-plugin-*`. If `pipx`'s install directory (`~/.local/bin/` on Linux, `~/Library/...` on -macOS) is not on your shell's `$PATH`, `clarion analyze` will exit +macOS) is not on your shell's `$PATH`, `loomweave analyze` will exit **successfully** with status `skipped_no_plugins` and emit a `WARN no plugins discovered` line — the analyse pass produces nothing. See [Troubleshooting → "analyze runs but emits no entities"](#analyze-runs-but-emits-no-entities) @@ -121,47 +121,47 @@ tar xzf requests-2.32.4.tar.gz cd requests-2.32.4 ``` -Initialise Clarion's project-local state, then run the analyser: +Initialise Loomweave's project-local state, then run the analyser: ```bash -clarion install -clarion analyze +loomweave install +loomweave analyze ``` -A bare `clarion install` does everything: it initialises `.clarion/`, installs +A bare `loomweave install` does everything: it initialises `.loomweave/`, installs the agent-orientation assets, writes Claude Code MCP config, and upserts the Codex MCP config (see [§3](#agent-orientation-installed-by-default)). If -`.clarion/` already exists, init is skipped and the other components are applied +`.loomweave/` already exists, init is skipped and the other components are applied idempotently; pass `--force` to wipe and reinitialise the index. Expected output (abridged): ``` applying migration version=1 name="0001_initial_schema" -clarion install complete clarion_dir=/tmp/requests-2.32.4/.clarion -Initialised /tmp/requests-2.32.4/.clarion -Installed clarion-workflow skill into ... +loomweave install complete loomweave_dir=/tmp/requests-2.32.4/.loomweave +Initialised /tmp/requests-2.32.4/.loomweave +Installed loomweave-workflow skill into ... Installed Claude Code MCP config at .../.mcp.json Installed Codex MCP config at ~/.codex/config.toml -Added clarion SessionStart hook to .../.claude/settings.json +Added loomweave SessionStart hook to .../.claude/settings.json ... analyze complete: run ok (entities=NNN, edges=MMM) ``` The first run on a tree of this size completes in well under a minute on -typical hardware. The result lives at `.clarion/clarion.db` (a single SQLite +typical hardware. The result lives at `.loomweave/loomweave.db` (a single SQLite file) and is safe to commit to git — see -[ADR-005](../clarion/adr/ADR-005-clarion-dir-tracking.md). +[ADR-005](../loomweave/adr/ADR-005-loomweave-dir-tracking.md). ## 3. Serve Start the MCP stdio server in one shell: ```bash -clarion serve --path /tmp/requests-2.32.4 +loomweave serve --path /tmp/requests-2.32.4 ``` -`clarion serve` speaks the MCP protocol over stdio. Any MCP client works; +`loomweave serve` speaks the MCP protocol over stdio. Any MCP client works; documented options: - **Claude Desktop.** Add to your `claude_desktop_config.json`: @@ -169,8 +169,8 @@ documented options: ```json { "mcpServers": { - "clarion-requests": { - "command": "/path/to/clarion", + "loomweave-requests": { + "command": "/path/to/loomweave", "args": ["serve", "--path", "/tmp/requests-2.32.4"], "env": { "OPENROUTER_API_KEY": "sk-or-v1-..." @@ -184,50 +184,50 @@ documented options: ad-hoc tool-level exploration without an agent in the loop: ```bash - npx @modelcontextprotocol/inspector clarion serve --path /tmp/requests-2.32.4 + npx @modelcontextprotocol/inspector loomweave serve --path /tmp/requests-2.32.4 ``` Pick whichever you have; the questions in step 4 are client-agnostic. ### Agent orientation (installed by default) -A bare `clarion install` already bundles these for consult-mode agents. The +A bare `loomweave install` already bundles these for consult-mode agents. The component flags exist for explicit partial installs (e.g. adding the skill to a -project whose `.clarion/` you do not want re-touched): +project whose `.loomweave/` you do not want re-touched): ```bash -clarion install --claude-code --path /tmp/requests-2.32.4 # Claude Code MCP only -clarion install --codex --path /tmp/requests-2.32.4 # Codex MCP only -clarion install --skills --path /tmp/requests-2.32.4 # Claude skill only -clarion install --codex-skills --path /tmp/requests-2.32.4 # Codex skill only -clarion install --hooks --path /tmp/requests-2.32.4 # hook only -clarion install --all --path /tmp/requests-2.32.4 # same as bare install +loomweave install --claude-code --path /tmp/requests-2.32.4 # Claude Code MCP only +loomweave install --codex --path /tmp/requests-2.32.4 # Codex MCP only +loomweave install --skills --path /tmp/requests-2.32.4 # Claude skill only +loomweave install --codex-skills --path /tmp/requests-2.32.4 # Codex skill only +loomweave install --hooks --path /tmp/requests-2.32.4 # hook only +loomweave install --all --path /tmp/requests-2.32.4 # same as bare install ``` -`--skills` writes `.claude/skills/clarion-workflow/`; `--codex-skills` writes -`.agents/skills/clarion-workflow/`. `--claude-code` writes `.mcp.json` with a -stdio `clarion serve` entry. `--codex` upserts `[mcp_servers.clarion]` in +`--skills` writes `.claude/skills/loomweave-workflow/`; `--codex-skills` writes +`.agents/skills/loomweave-workflow/`. `--claude-code` writes `.mcp.json` with a +stdio `loomweave serve` entry. `--codex` upserts `[mcp_servers.loomweave]` in `~/.codex/config.toml`. Both MCP configs rely on the client working directory for project discovery instead of pinning `--path`. `--hooks` merges a SessionStart entry into `.claude/settings.json` (existing -hooks are preserved) that runs `clarion hook session-start` — a fail-soft +hooks are preserved) that runs `loomweave hook session-start` — a fail-soft command printing live entity/subsystem/finding counts and index freshness. -To verify (and repair) these surfaces later, run `clarion doctor`: +To verify (and repair) these surfaces later, run `loomweave doctor`: ```bash -clarion doctor --path /tmp/requests-2.32.4 # report only; exits non-zero if anything is off -clarion doctor --fix --path /tmp/requests-2.32.4 # repair the skill pack, hook, and .mcp.json entry in place +loomweave doctor --path /tmp/requests-2.32.4 # report only; exits non-zero if anything is off +loomweave doctor --fix --path /tmp/requests-2.32.4 # repair the skill pack, hook, and .mcp.json entry in place ``` -`doctor` also checks the `clarion` entry in `.mcp.json` — which `install` does +`doctor` also checks the `loomweave` entry in `.mcp.json` — which `install` does not register automatically — and `--fix` adds it (preserving any sibling MCP servers and a customised `command`). The non-zero exit on remaining problems makes it usable as a CI / pre-commit gate. Over MCP, the same orientation is available without install: the `initialize` -result carries an `instructions` field, the `clarion://context` resource returns -the live snapshot, and the `clarion-workflow` prompt returns the skill text. +result carries an `instructions` field, the `loomweave://context` resource returns +the live snapshot, and the `loomweave-workflow` prompt returns the skill text. ## 4. Ask @@ -235,7 +235,7 @@ the live snapshot, and the `clarion-workflow` prompt returns the skill text. The structural MCP tools work out of the box, but `summary(id)` (question 3 below) needs the live OpenRouter path explicitly opted into. Edit -`/tmp/requests-2.32.4/clarion.yaml` and set both: +`/tmp/requests-2.32.4/loomweave.yaml` and set both: ```yaml llm_policy: @@ -244,7 +244,7 @@ llm_policy: ``` `OPENROUTER_API_KEY` must also be exported in the environment that -`clarion serve` (or your MCP client wrapper) inherits — see the +`loomweave serve` (or your MCP client wrapper) inherits — see the prerequisites section above. Skip this block if you don't have a key; the other seventeen tools still work, only `summary` will return an "LLM disabled" envelope. @@ -279,7 +279,7 @@ graph query, not free-text grep. | `source_for_entity(id, context_lines)` | `source_for_entity(id="python:function:requests.sessions.Session.send", context_lines=10)` — the entity's exact indexed source span plus bounded line-numbered context, each line flagged `in_entity`. Reports `source_status` (`ok`/`missing`/`drifted`/…) instead of a stale snippet. No LLM. | | `call_sites(id, role)` | `call_sites(id="python:function:requests.sessions.Session.send", role="caller")` — the actual source line(s) behind calls/references edges: file, line, line text, edge kind, confidence, and resolved/ambiguous/unresolved classification. `role="callee"` shows incoming sites. No LLM. | | `orientation_pack(entity \| file, line)` | `orientation_pack(file="requests/sessions.py", line=480)` — one deterministic packet for a location: primary entity, `entity_context` evidence, source-span summary, one-hop neighbors, compact execution paths, related Filigree issues, index/Filigree/LLM health, and suggested next reads. Resolve by `entity` id or by `file`+`line`. No LLM. | -| `analyze_start()` | `analyze_start()` — launch a background `clarion analyze` re-index and return its `run_id` immediately. One run per project (cross-process lock). No arguments, no LLM. | +| `analyze_start()` | `analyze_start()` — launch a background `loomweave analyze` re-index and return its `run_id` immediately. One run per project (cross-process lock). No arguments, no LLM. | | `analyze_status(run_id)` | `analyze_status(run_id="…")` — live status of a run: `queued`/`running`/`completed`/`failed`/`cancelled`/`skipped_no_plugins`, phase, processed/total files, heartbeat, and recorded stats on a terminal status. No LLM. | | `analyze_cancel(run_id)` | `analyze_cancel(run_id="…")` — SIGKILL a running analyze's process group (plugin + Pyright) and record its terminal state. No LLM. | | `index_diff()` | `index_diff()` — freshness / drift report: latest completed run, indexed-file drift (mtime vs. index), and git working-tree changes correlated against indexed paths. No arguments, no LLM. | @@ -303,7 +303,7 @@ that your client actually called the tools. Re-run analyse for idempotency: ```bash -clarion analyze +loomweave analyze # entity/edge counts on the second run should match the first ``` @@ -316,16 +316,16 @@ cat > .env <<'EOF' AWS_ACCESS_KEY_ID=AKIA0123456789ABCDEF EOF -clarion analyze +loomweave analyze ``` Expected behaviour: -- `clarion analyze` exits **0** with run status `completed`. -- A `CLA-SEC-SECRET-DETECTED` finding lands in `findings` with the message +- `loomweave analyze` exits **0** with run status `completed`. +- A `LMWV-SEC-SECRET-DETECTED` finding lands in `findings` with the message `AwsAccessKeyId detected in /tmp/requests-2.32.4/.env:1`. Inspect with - `sqlite3 .clarion/clarion.db "SELECT rule_id, message FROM findings - WHERE rule_id LIKE 'CLA-SEC%';"`. + `sqlite3 .loomweave/loomweave.db "SELECT rule_id, message FROM findings + WHERE rule_id LIKE 'LMWV-SEC%';"`. - The `.env` file itself has no language entities (it's not Python), so the finding is anchored to the core-minted file entity rather than a language-plugin entity. Source files in the project that the scanner @@ -343,53 +343,53 @@ Full mechanics — baseline format, override flags, audit queries — in ### `analyze` runs but emits no entities Look for `WARN no plugins discovered` and `skipped_no_plugins` in the -analyse output. The plugin host walks `$PATH` for `clarion-plugin-*` +analyse output. The plugin host walks `$PATH` for `loomweave-plugin-*` executables; if your shell's `$PATH` does not include `pipx`'s install directory the plugin is invisible. Confirm and fix: ```bash -which clarion-plugin-python || echo "not on PATH" +which loomweave-plugin-python || echo "not on PATH" echo $PATH # is pipx's bin dir in here? # If pipx is installed but its bin dir is missing: pipx ensurepath # writes the PATH update; restart shell ``` -Note: `clarion analyze` deliberately exits **0** even when no plugins are +Note: `loomweave analyze` deliberately exits **0** even when no plugins are discovered, so the run can be re-attempted without manual cleanup. The `WARN` line and the `skipped_no_plugins` run status are the operator-facing -signals. A `clarion doctor` subcommand that surfaces discovery state at exit +signals. A `loomweave doctor` subcommand that surfaces discovery state at exit is on the v2.0 roadmap; for v1.0 the diagnostic is the WARN line plus the -`which clarion-plugin-*` check above. +`which loomweave-plugin-*` check above. ### "secret_present" block fires on a real file -Add the file to `.clarion/secrets-baseline.yaml` with a written justification +Add the file to `.loomweave/secrets-baseline.yaml` with a written justification (the schema requires it). Full procedure: [secret-scanning.md](./secret-scanning.md). ### `summary` returns an error citing budget or LLM provider -Check `OPENROUTER_API_KEY` is set in the environment that `clarion serve` +Check `OPENROUTER_API_KEY` is set in the environment that `loomweave serve` inherits (for Claude Desktop that means the `env` block in the MCP-server config). Live LLM calls are also gated by `llm_policy.enabled: true` and -`llm_policy.allow_live_provider: true` in `clarion.yaml` — see +`llm_policy.allow_live_provider: true` in `loomweave.yaml` — see [openrouter.md](./openrouter.md). ### `issues_for` returns an `unavailable` envelope Expected when Filigree is not reachable. Filigree integration is -*enrich-only* per the Loom federation axiom — Clarion's structural answers +*enrich-only* per the Weft federation axiom — Loomweave's structural answers are unaffected. See -[CON-FILIGREE-02](../clarion/1.0/requirements.md#con-filigree-02--file-registry-displacement-is-deferred-to-v02) +[CON-FILIGREE-02](../loomweave/1.0/requirements.md#con-filigree-02--file-registry-displacement-is-deferred-to-v02) for the v1.0 → v2.0 trajectory. ## Where to go next - [Operator notes index](./README.md) — OpenRouter, runtime topology, secret scanning, federation contracts, coding-agent LLM providers. -- [Design ladder](../clarion/1.0/README.md) — requirements → system-design → +- [Design ladder](../loomweave/1.0/README.md) — requirements → system-design → detailed-design. -- [ADR index](../clarion/adr/README.md) — accepted architecture decisions. +- [ADR index](../loomweave/adr/README.md) — accepted architecture decisions. - [CLAUDE.md](../../CLAUDE.md) — repository conventions. diff --git a/docs/operator/guidance.md b/docs/operator/guidance.md index e9dcb23d..bb1bf7ce 100644 --- a/docs/operator/guidance.md +++ b/docs/operator/guidance.md @@ -2,19 +2,19 @@ A **guidance sheet** is institutional knowledge attached to code: a short note ("refresh tokens are single-use", "this module owns the retry budget") that -Clarion serves to consult-mode agents alongside the entities it applies to. A +Loomweave serves to consult-mode agents alongside the entities it applies to. A sheet is a first-class entity of `kind: guidance` (`id` form `core:guidance:`); it carries the note text plus the rules that decide which entities it covers, a scope level, and optional pinning / expiry (`REQ-GUIDANCE-01`, ADR-024). -Guidance is authored by operators via the `clarion guidance` CLI (this guide) +Guidance is authored by operators via the `loomweave guidance` CLI (this guide) or proposed by agents through MCP and promoted by an operator. Authored and promoted sheets reach consult agents through the `guidance_for` MCP read tool and are also composed into auto-generated `summary` prompts with a real `guidance_fingerprint` cache key. -All subcommands operate on `.clarion/clarion.db`, so **run `clarion analyze` +All subcommands operate on `.loomweave/loomweave.db`, so **run `loomweave analyze` first** — the CLI errors if the database is absent. ## Authoring workflow (`REQ-GUIDANCE-03`) @@ -22,7 +22,7 @@ first** — the CLI errors if the database is absent. ### `create` ```bash -clarion guidance create \ +loomweave guidance create \ --match path:src/auth/** \ --match tag:auth \ --scope-level module \ @@ -51,9 +51,9 @@ preserved. ### `show ` / `list` ```bash -clarion guidance show core:guidance:auth-tokens -clarion guidance list -clarion guidance list --for-entity python:function:auth.tokens.refresh +loomweave guidance show core:guidance:auth-tokens +loomweave guidance list +loomweave guidance list --for-entity python:function:auth.tokens.refresh ``` `list` is ordered by `scope_rank` (project → function). `--for-entity` filters @@ -68,12 +68,12 @@ Removes the sheet. Its matched entities' cached summaries are invalidated (see ### `promote ` ```bash -clarion guidance promote clarion-obs-abc123 +loomweave guidance promote loomweave-obs-abc123 ``` Promotes a reviewed Filigree observation produced by MCP `propose_guidance` into a local guidance sheet (`provenance: filigree_promotion`). Arbitrary -observations are rejected: the observation detail must contain Clarion's +observations are rejected: the observation detail must contain Loomweave's guidance-proposal payload. This is the anti-poisoning boundary (`NFR-SEC-02`): an agent proposal is inert until an operator promotes it. @@ -123,7 +123,7 @@ them as a finding (below). ## Wardline-derived guidance (`REQ-GUIDANCE-04`) -When `wardline.yaml` is present, `clarion analyze` generates deterministic, +When `wardline.yaml` is present, `loomweave analyze` generates deterministic, pinned guidance sheets from the Wardline bundle: - `core:guidance:wardline-tier-` @@ -143,37 +143,37 @@ artifact hash/count metadata, and a generated-signature guard. If an operator edits a generated sheet, the next analyze preserves the edit and marks the sheet `provenance: wardline_derived_overridden`. If the Wardline bundle changes while an override remains in place, analyze emits -`CLA-FACT-GUIDANCE-STALE` so the override can be reviewed instead of silently +`LMWV-FACT-GUIDANCE-STALE` so the override can be reviewed instead of silently overwritten. ## Staleness Two independent staleness signals exist, and they are **not** the same thing: -1. **Age / review cadence** — `clarion guidance list --stale [--days N]` shows +1. **Age / review cadence** — `loomweave guidance list --stale [--days N]` shows sheets not touched (the later of `reviewed_at` / `authored_at`) within `N` days (default 90). This is a review-cadence prompt, computed at list time. -2. **Churn-based finding** — `clarion analyze` emits - `CLA-FACT-GUIDANCE-CHURN-STALE` when the code under a sheet has churned (see +2. **Churn-based finding** — `loomweave analyze` emits + `LMWV-FACT-GUIDANCE-CHURN-STALE` when the code under a sheet has churned (see the findings table). This is a separate heuristic, not the `--days` age signal. `--stale` and `--expired` are independent filters that compose by intersection -(AND): `clarion guidance list --stale --expired` shows sheets that are both. +(AND): `loomweave guidance list --stale --expired` shows sheets that are both. ### Staleness findings (`REQ-GUIDANCE-05`) -`clarion analyze` persists these findings over the committed graph (anchored to +`loomweave analyze` persists these findings over the committed graph (anchored to the guidance sheet). See `detailed-design.md` §5 for the canonical catalogue. | Rule | Severity | When | |---|---|---| -| `CLA-FACT-GUIDANCE-ORPHAN` | WARN | The sheet's `guides` edge **or** a `match_rules` `entity:` rule points at an entity deleted between runs. The sheet's guidance is stranded. | -| `CLA-FACT-GUIDANCE-EXPIRED` | INFO | The sheet's `expires` instant is in the past. The read path already excludes it from composition; this surfaces the state operatively (the sheet is not deleted). | -| `CLA-FACT-GUIDANCE-STALE` | WARN | A Wardline-derived override carries an older `wardline_manifest_hash` than the current Wardline bundle. | -| `CLA-FACT-GUIDANCE-CHURN-STALE` | WARN (confidence 0.7) | The aggregate `git_churn_count` over the sheet's matched entities meets the staleness threshold (50; 20 for `pinned: true` sheets). | +| `LMWV-FACT-GUIDANCE-ORPHAN` | WARN | The sheet's `guides` edge **or** a `match_rules` `entity:` rule points at an entity deleted between runs. The sheet's guidance is stranded. | +| `LMWV-FACT-GUIDANCE-EXPIRED` | INFO | The sheet's `expires` instant is in the past. The read path already excludes it from composition; this surfaces the state operatively (the sheet is not deleted). | +| `LMWV-FACT-GUIDANCE-STALE` | WARN | A Wardline-derived override carries an older `wardline_manifest_hash` than the current Wardline bundle. | +| `LMWV-FACT-GUIDANCE-CHURN-STALE` | WARN (confidence 0.7) | The aggregate `git_churn_count` over the sheet's matched entities meets the staleness threshold (50; 20 for `pinned: true` sheets). | -> **`CLA-FACT-GUIDANCE-CHURN-STALE` is currently inert.** It is emitted only +> **`LMWV-FACT-GUIDANCE-CHURN-STALE` is currently inert.** It is emitted only > when churn data is available, and the analyze pipeline does not yet populate > `git_churn_count`. In production today it never fires. The other guidance > findings are live. @@ -185,8 +185,8 @@ sorted-key JSON file per sheet (byte-stable across runs on identical DB state, diff-friendly): ```bash -clarion guidance export --to ./shared/guidance # --to takes a flag -clarion guidance import ./shared/guidance # dir is positional +loomweave guidance export --to ./shared/guidance # --to takes a flag +loomweave guidance import ./shared/guidance # dir is positional ``` Import is **additive and idempotent**: each sheet is upserted by id, ids are diff --git a/docs/operator/clarion-http-read-api.md b/docs/operator/loomweave-http-read-api.md similarity index 65% rename from docs/operator/clarion-http-read-api.md rename to docs/operator/loomweave-http-read-api.md index 725209ca..39e7d7fe 100644 --- a/docs/operator/clarion-http-read-api.md +++ b/docs/operator/loomweave-http-read-api.md @@ -1,53 +1,53 @@ -# Clarion HTTP Read API +# Loomweave HTTP Read API -Clarion can expose a read-only HTTP API for local sibling integrations such as -Filigree's `registry_backend: clarion` mode. The wire contract is documented in +Loomweave can expose a read-only HTTP API for local sibling integrations such as +Filigree's `registry_backend: loomweave` mode. The wire contract is documented in the [federation contracts](../federation/contracts.md). ## Trust Model -By default, `clarion serve` binds the HTTP read API only to loopback addresses so +By default, `loomweave serve` binds the HTTP read API only to loopback addresses so it is reachable from local processes on the same host, not from the network. A loopback-only API may run without authentication for local sidecar workflows. For authenticated mode, set `serve.http.identity_token_env` to the name of an -environment variable that contains the shared Loom component secret: +environment variable that contains the shared Weft component secret: ```yaml serve: http: enabled: true bind: 127.0.0.1:9111 - identity_token_env: CLARION_LOOM_IDENTITY_SECRET + identity_token_env: WEFT_IDENTITY_SECRET ``` -When `identity_token_env` is configured, Clarion refuses to start unless the env +When `identity_token_env` is configured, Loomweave refuses to start unless the env var is present and non-empty. Protected `/api/v1/files` routes then require -`X-Loom-Component: clarion:`, `X-Loom-Timestamp: `, and -`X-Loom-Nonce: `. The HMAC is lowercase hex HMAC-SHA256 over: +`X-Weft-Component: loomweave:`, `X-Weft-Timestamp: `, and +`X-Weft-Nonce: `. The HMAC is lowercase hex HMAC-SHA256 over: ```text - - + + ``` For example, a GET of `/api/v1/files?path=demo.py&language=python` signs the method `GET`, that exact path-and-query string, and the SHA-256 hash of an empty -body, followed by the timestamp and nonce header values. Clarion accepts a +body, followed by the timestamp and nonce header values. Loomweave accepts a five-minute timestamp skew and rejects reuse of the same nonce inside that process-local window. `GET /api/v1/_capabilities` stays unauthenticated so siblings can probe the API surface before sending protected reads. -Clarion still accepts the older `serve.http.token_env` bearer-token path for +Loomweave still accepts the older `serve.http.token_env` bearer-token path for compatibility. Prefer `identity_token_env` for new deployments. -Clarion refuses non-loopback binds unless `serve.http.allow_non_loopback: true` +Loomweave refuses non-loopback binds unless `serve.http.allow_non_loopback: true` is set. Non-loopback deployments must also configure authentication; otherwise -Clarion refuses to bind. Treat the endpoint as source-code metadata exposure: -anyone who can reach it can read Clarion's catalog responses for the project. +Loomweave refuses to bind. Treat the endpoint as source-code metadata exposure: +anyone who can reach it can read Loomweave's catalog responses for the project. ## Contract Summary @@ -55,7 +55,7 @@ anyone who can reach it can read Clarion's catalog responses for the project. `instance_id`, and booleans indicating whether registry-backend file resolution is available. -`GET /api/v1/files?path=&language=` resolves an existing Clarion file-kind row +`GET /api/v1/files?path=&language=` resolves an existing Loomweave file-kind row to the entity ID and project-relative canonical path Filigree should store. It fails closed when the path is invalid, outside the project, missing from the catalog, or unavailable because of storage errors. @@ -64,11 +64,11 @@ catalog, or unavailable because of storage errors. When both `serve.http.token_env` (legacy bearer) and `serve.http.identity_token_env` (HMAC, preferred per -[ADR-034](../clarion/adr/ADR-034-federation-http-read-api-hardening.md)) are unset and the +[ADR-034](../loomweave/adr/ADR-034-federation-http-read-api-hardening.md)) are unset and the bind is loopback (default: `127.0.0.1:9111`), the HTTP read API serves unauthenticated. This is the intended single-user developer-workstation trust model — the loopback socket is reachable only from processes on the -same host, and Clarion's catalogue is no more sensitive than the project +same host, and Loomweave's catalogue is no more sensitive than the project source those processes can already read. **On a multi-tenant developer host or shared CI runner this trust model @@ -80,11 +80,11 @@ defect, but operators on multi-tenant hosts must configure authentication before binding. Multi-tenant operators MUST set `identity_token_env` (HMAC, preferred) or -`token_env` (bearer, legacy) before running `clarion serve`. The HMAC +`token_env` (bearer, legacy) before running `loomweave serve`. The HMAC configuration shape is documented in the [Trust Model](#trust-model) section above. -The Clarion `serve` startup banner emits a `[TRUST]` line warning when +The Loomweave `serve` startup banner emits a `[TRUST]` line warning when loopback-no-token mode is active: `HTTP API serving on loopback without authentication; any local process on this host can read the catalogue.` The warning is logged at `WARN` level at startup whenever both auth knobs diff --git a/docs/operator/openrouter.md b/docs/operator/openrouter.md index 08251eed..dbcbb9b8 100644 --- a/docs/operator/openrouter.md +++ b/docs/operator/openrouter.md @@ -1,16 +1,16 @@ # OpenRouter LLM Provider -Clarion uses OpenRouter as its canonical HTTP LLM provider. The provider +Loomweave uses OpenRouter as its canonical HTTP LLM provider. The provider speaks OpenAI-compatible Chat Completions HTTP through the existing `LlmProvider` trait, so tests can continue to use `RecordingProvider` and coding-agent CLI providers can be added without changing MCP tool call sites. -For local-login alternatives that avoid API keys in Clarion config, see +For local-login alternatives that avoid API keys in Loomweave config, see [Coding-agent LLM providers](./coding-agent-llm-providers.md). -## Configure Clarion +## Configure Loomweave -`clarion install` writes a default `clarion.yaml` with LLMs disabled. To enable +`loomweave install` writes a default `loomweave.yaml` with LLMs disabled. To enable live OpenRouter calls, set a concrete model ID and opt in explicitly: ```yaml @@ -23,8 +23,8 @@ llm_policy: api_key_env: OPENROUTER_API_KEY timeout_seconds: 300 # per-request HTTP timeout; must be > 0 (default 300) attribution: - referer: https://github.com/tachyon-beep/clarion - title: Clarion + referer: https://github.com/foundryside-dev/loomweave + title: Loomweave model_id: anthropic/claude-sonnet-4.6 session_token_ceiling: 1000000 max_inferred_edges_per_caller: 8 @@ -43,8 +43,8 @@ environment variable named by `llm_policy.openrouter.api_key_env`: export OPENROUTER_API_KEY=... ``` -`clarion serve` does not construct a live provider unless LLMs are enabled and -`allow_live_provider: true` is set, or `CLARION_LLM_LIVE=1` is present. Missing +`loomweave serve` does not construct a live provider unless LLMs are enabled and +`allow_live_provider: true` is set, or `LOOMWEAVE_LLM_LIVE=1` is present. Missing keys are reported as configuration errors instead of panicking at startup. ## Model IDs @@ -55,31 +55,31 @@ OpenRouter model IDs are concrete strings such as: - `openai/gpt-4o-mini` - `meta-llama/llama-3.1-8b-instruct` -Clarion stores this exact string in the summary cache key's `model_tier` +Loomweave stores this exact string in the summary cache key's `model_tier` component. There is no tier-name resolver in v1.0; operators choose the concrete -model in `clarion.yaml`. Refresh the chosen ID against OpenRouter's Models API +model in `loomweave.yaml`. Refresh the chosen ID against OpenRouter's Models API before release or production use. ## Token Ceiling -Clarion enforces a per-`clarion serve` session token ceiling, not a dollar +Loomweave enforces a per-`loomweave serve` session token ceiling, not a dollar ceiling. Live responses debit OpenRouter's reported `usage.total_tokens`; cache hits do not spend tokens. When the ceiling is reached, MCP responses use: - error code: `token-ceiling-exceeded` -- diagnostic: `CLA-LLM-TOKEN-CEILING-EXCEEDED` +- diagnostic: `LMWV-LLM-TOKEN-CEILING-EXCEEDED` - stat: `token_ceiling_exceeded_total` -The ceiling is scoped to the running `clarion serve` process. Once a live LLM -call attempts to exceed `llm_policy.session_token_ceiling`, Clarion blocks new +The ceiling is scoped to the running `loomweave serve` process. Once a live LLM +call attempts to exceed `llm_policy.session_token_ceiling`, Loomweave blocks new cold LLM dispatches for the rest of that process lifetime. Cache hits can still be returned while the budget is blocked, because they do not spend additional tokens. -To clear a blocked LLM budget, stop and restart `clarion serve`. To change the -future ceiling, edit `llm_policy.session_token_ceiling` in `clarion.yaml` before -restarting. Clarion v1.0 intentionally has no MCP tool that resets the in-memory +To clear a blocked LLM budget, stop and restart `loomweave serve`. To change the +future ceiling, edit `llm_policy.session_token_ceiling` in `loomweave.yaml` before +restarting. Loomweave v1.0 intentionally has no MCP tool that resets the in-memory budget ledger. Dollar budgeting remains an operator concern in OpenRouter billing controls. diff --git a/docs/operator/release-handoff.md b/docs/operator/release-handoff.md index c5d49368..715dad3d 100644 --- a/docs/operator/release-handoff.md +++ b/docs/operator/release-handoff.md @@ -1,30 +1,30 @@ # Release Handoff -Clarion no longer owns live GitHub branch/ruleset release-control enforcement. +Loomweave no longer owns live GitHub branch/ruleset release-control enforcement. -The old v1.0 guard required Clarion's release workflow to inspect repository +The old v1.0 guard required Loomweave's release workflow to inspect repository branch protection, tag rulesets, Actions policy, and a dedicated maintainer -secret before build jobs could start. That requirement is retired for Clarion. +secret before build jobs could start. That requirement is retired for Loomweave. Governance enforcement is moving to Legis, which is delivered alongside the -next Clarion release. +next Loomweave release. -Clarion's standalone release semantics are now: +Loomweave's standalone release semantics are now: - the release workflow verifies the source tree, builds artifacts, and publishes GitHub Releases for `v*` tags; -- Clarion does not require live repository ruleset inspection to build or +- Loomweave does not require live repository ruleset inspection to build or publish its own artifacts; - any Legis governance signal is external enrichment and must not become a - prerequisite for Clarion to remain useful alone. + prerequisite for Loomweave to remain useful alone. -Keep future Clarion release controls local to Clarion's own artifact integrity +Keep future Loomweave release controls local to Loomweave's own artifact integrity unless a new accepted ADR says otherwise. If Legis publishes an attestation for the same release train, link it from release notes or operator handoff material -without adding a mandatory sibling dependency to Clarion's release workflow. +without adding a mandatory sibling dependency to Loomweave's release workflow. ## Current Release Sequence -1. Confirm the release branch or PR is green under Clarion's CI floor. +1. Confirm the release branch or PR is green under Loomweave's CI floor. 2. Run `.github/workflows/release.yml` with `workflow_dispatch` from the release commit to validate the build and artifact path. 3. Push the `v*` tag from the reviewed release commit. diff --git a/docs/operator/runtime-topology.md b/docs/operator/runtime-topology.md index 1302d617..8a5590b3 100644 --- a/docs/operator/runtime-topology.md +++ b/docs/operator/runtime-topology.md @@ -1,13 +1,13 @@ # Runtime Topology -Clarion stores project state in `.clarion/clarion.db`. The current v0.1 CLI +Loomweave stores project state in `.loomweave/loomweave.db`. The current v0.1 CLI uses SQLite WAL mode with a 5 second `busy_timeout` on writer and reader -connections. `clarion analyze` opens one writer actor for ingest. `clarion +connections. `loomweave analyze` opens one writer actor for ingest. `loomweave serve` always opens a reader pool, and opens its own writer actor only when LLM -summary or inferred-edge writes are enabled by `clarion.yaml`. +summary or inferred-edge writes are enabled by `loomweave.yaml`. These storage settings are implementation constants today, not configurable -`clarion.yaml` keys: +`loomweave.yaml` keys: - write connections set `journal_mode=WAL`, `synchronous=NORMAL`, `busy_timeout=5000`, `wal_autocheckpoint=1000`, and `foreign_keys=ON` @@ -17,8 +17,8 @@ These storage settings are implementation constants today, not configurable ## Supported -One `clarion analyze` process and one `clarion serve` process may run against -the same `.clarion/clarion.db`. `serve` reads use committed SQLite snapshots: +One `loomweave analyze` process and one `loomweave serve` process may run against +the same `.loomweave/loomweave.db`. `serve` reads use committed SQLite snapshots: in-flight analyze writes are invisible until their transaction commits and a later read checks out a connection. If LLM-backed `serve` writes race with analyze ingest, SQLite serialises the writers and waits up to 5 seconds before @@ -27,8 +27,8 @@ returning a lock error. This topology is the default local workflow: ```sh -clarion analyze . -clarion serve --path . +loomweave analyze . +loomweave serve --path . ``` Long analyze runs can make `serve` responses stale relative to the source tree @@ -38,17 +38,17 @@ snapshot for a review session. ## Unsupported -Do not run multiple `clarion analyze` processes against the same -`.clarion/clarion.db`. Clarion has one writer actor per process, not one global +Do not run multiple `loomweave analyze` processes against the same +`.loomweave/loomweave.db`. Loomweave has one writer actor per process, not one global writer across processes, so two analyze runs can contend at SQLite's single writer boundary and produce interleaved run state. -Do not run `clarion install --force` while either `clarion analyze` or -`clarion serve` is using the same project. `--force` replaces `.clarion/`, so it +Do not run `loomweave install --force` while either `loomweave analyze` or +`loomweave serve` is using the same project. `--force` replaces `.loomweave/`, so it is an offline maintenance operation. -Do not delete SQLite sidecar files, copy `.clarion/clarion.db` without its WAL -sidecars, or edit `.clarion/` files while Clarion is running. Stop the processes +Do not delete SQLite sidecar files, copy `.loomweave/loomweave.db` without its WAL +sidecars, or edit `.loomweave/` files while Loomweave is running. Stop the processes first, then copy or repair the store. ## Not Yet Shipped diff --git a/docs/operator/secret-scanning.md b/docs/operator/secret-scanning.md index b424a4a0..d0f2e81d 100644 --- a/docs/operator/secret-scanning.md +++ b/docs/operator/secret-scanning.md @@ -1,16 +1,16 @@ # Secret Scanning -Clarion scans source files before any file content can be used for LLM summaries. A detected credential creates a finding and marks entities from that file with `briefing_blocked: secret_present`. Structural analysis still runs, but summaries for that file do not. +Loomweave scans source files before any file content can be used for LLM summaries. A detected credential creates a finding and marks entities from that file with `briefing_blocked: secret_present`. Structural analysis still runs, but summaries for that file do not. ## What Gets Blocked Blocking is file-level. If `src/config.py` contains a detected key, entities from that file remain queryable through structural tools, but the `summary` tool returns a policy envelope instead of calling the LLM provider or writing `summary_cache`. -Plugin source files and `.env` sidecars are scanned. If a plugin reports an entity for some other in-project path that was not covered by the scanner, Clarion marks that entity `briefing_blocked: unscanned_source` so source bytes cannot reach the LLM provider without a prior scan. +Plugin source files and `.env` sidecars are scanned. If a plugin reports an entity for some other in-project path that was not covered by the scanner, Loomweave marks that entity `briefing_blocked: unscanned_source` so source bytes cannot reach the LLM provider without a prior scan. ## Whitelist A False Positive -Add `.clarion/secrets-baseline.yaml` and commit it with the source change: +Add `.loomweave/secrets-baseline.yaml` and commit it with the source change: ```yaml version: "1.0" @@ -23,9 +23,9 @@ results: justification: "AWS documentation example key used in a test fixture." ``` -The hash is SHA-1 over the matched literal bytes, matching `detect-secrets` v1.x baseline conventions. `justification` is required; entries without it are ignored and produce `CLA-INFRA-SECRET-BASELINE-NO-JUSTIFICATION`. +The hash is SHA-1 over the matched literal bytes, matching `detect-secrets` v1.x baseline conventions. `justification` is required; entries without it are ignored and produce `LMWV-INFRA-SECRET-BASELINE-NO-JUSTIFICATION`. -A matching baseline entry suppresses the block and records `CLA-INFRA-SECRET-BASELINE-MATCH` for audit. +A matching baseline entry suppresses the block and records `LMWV-INFRA-SECRET-BASELINE-MATCH` for audit. ## Override Flag @@ -40,11 +40,11 @@ yes-i-understand Non-TTY runs must pass both flags: ```bash -clarion analyze --allow-unredacted-secrets \ +loomweave analyze --allow-unredacted-secrets \ --confirm-allow-unredacted-secrets=yes-i-understand . ``` -Confirmed overrides do not set `briefing_blocked`; they emit `CLA-SEC-UNREDACTED-SECRETS-ALLOWED` and add `secret_override_used` plus `secret_override_files_affected` to `runs.stats`. Passing `--allow-unredacted-secrets` on a clean repo is a no-op. +Confirmed overrides do not set `briefing_blocked`; they emit `LMWV-SEC-UNREDACTED-SECRETS-ALLOWED` and add `secret_override_used` plus `secret_override_files_affected` to `runs.stats`. Passing `--allow-unredacted-secrets` on a clean repo is a no-op. ## Exit Codes @@ -61,8 +61,8 @@ Local SQLite: ```sql select rule_id, severity, message, evidence from findings -where rule_id like 'CLA-SEC-%' - or rule_id like 'CLA-INFRA-SECRET-%'; +where rule_id like 'LMWV-SEC-%' + or rule_id like 'LMWV-INFRA-SECRET-%'; ``` Currently blocked entities: @@ -78,11 +78,11 @@ Filigree integration for scanner findings (WP9-B finding emission) is deferred t ## Limitations -The scanner is pattern-based. It can miss novel internal key formats and it can flag high-entropy test data. Use a justified baseline for reviewed false positives, and disable LLM dispatch entirely for repos where any source disclosure would be unacceptable — set `llm.allow_live_provider: false` in `clarion.yaml` (or leave `CLARION_LLM_LIVE` unset) so the recording provider is the only path Clarion will take. +The scanner is pattern-based. It can miss novel internal key formats and it can flag high-entropy test data. Use a justified baseline for reviewed false positives, and disable LLM dispatch entirely for repos where any source disclosure would be unacceptable — set `llm.allow_live_provider: false` in `loomweave.yaml` (or leave `LOOMWEAVE_LLM_LIVE` unset) so the recording provider is the only path Loomweave will take. Contextual credential suppression currently recognises shell/Python `#` comments only. It does not recognise `//` or `/* */` comments; use a justified baseline entry for reviewed non-Python test fixtures. -See [ADR-013](../clarion/adr/ADR-013-pre-ingest-secret-scanner.md) for design rationale. +See [ADR-013](../loomweave/adr/ADR-013-pre-ingest-secret-scanner.md) for design rationale. ## Trust assumption: loopback-no-token mode @@ -92,11 +92,11 @@ callers. The v1.0 HTTP API has one mode where it serves any local caller without authentication: **loopback bind with no token configured.** When both `serve.http.token_env` (legacy bearer) and `serve.http.identity_token_env` -(HMAC, preferred per [ADR-034](../clarion/adr/ADR-034-federation-http-read-api-hardening.md)) +(HMAC, preferred per [ADR-034](../loomweave/adr/ADR-034-federation-http-read-api-hardening.md)) are unset and the bind is loopback (default: `127.0.0.1:9111`), the HTTP read API serves unauthenticated. On a single-user developer workstation this is the intended trust model: the loopback socket is reachable only from -processes on that host, and Clarion's catalogue is no more sensitive than +processes on that host, and Loomweave's catalogue is no more sensitive than the project source those processes can already read. **On a multi-tenant developer host or shared CI runner the trust model is @@ -107,11 +107,11 @@ is the documented v1.0 trust matrix; it is not a defect, but it is a constraint operators must understand. Multi-tenant operators MUST set `identity_token_env` (HMAC, preferred) or -`token_env` (bearer, legacy) before running `clarion serve`. See -[`clarion-http-read-api.md`](./clarion-http-read-api.md) for the +`token_env` (bearer, legacy) before running `loomweave serve`. See +[`loomweave-http-read-api.md`](./loomweave-http-read-api.md) for the configuration shape. -The Clarion `serve` startup banner emits a `[TRUST]` line warning when +The Loomweave `serve` startup banner emits a `[TRUST]` line warning when loopback-no-token mode is active: `HTTP API serving on loopback without authentication; any local process on this host can read the catalogue.` This warning is logged at `WARN` level at startup whenever both auth knobs @@ -124,8 +124,8 @@ are marked by writing `briefing_blocked: ` into the file entity's `properties` JSON column. v1.1 will promote `briefing_blocked` to a typed column on `entities`; v1.0 carries it as a JSON property. -**A v1.0 binary opening a `.clarion/clarion.db` produced by a pre-WP5 -Clarion binary will find no `briefing_blocked` properties on any row.** +**A v1.0 binary opening a `.loomweave/loomweave.db` produced by a pre-WP5 +Loomweave binary will find no `briefing_blocked` properties on any row.** Pre-WP5 binaries never ran the scanner and never wrote the property; the 1.0 binary cannot retroactively discover which files contained secrets at that earlier scan time. The HTTP read API will serve the entire catalogue @@ -134,11 +134,11 @@ absent. **Required upgrade procedure:** after installing the v1.0 binary against a project root that was previously analyzed by a pre-WP5 binary, run -`clarion analyze` (with the secret scanner active, which is the default) +`loomweave analyze` (with the secret scanner active, which is the default) against the project root **before** exposing the HTTP read API or calling the `summary` MCP tool. The re-analyze produces a fresh briefing-blocked annotation pass over all current file entities. -This applies only to upgrades from a Clarion binary built before WP5 +This applies only to upgrades from a Loomweave binary built before WP5 landed. A v1.0 installation that has never been opened by a pre-WP5 binary is unaffected. diff --git a/docs/operator/v1.0-release-governance.md b/docs/operator/v1.0-release-governance.md index fa9c23f9..183ebcd2 100644 --- a/docs/operator/v1.0-release-governance.md +++ b/docs/operator/v1.0-release-governance.md @@ -2,5 +2,5 @@ This operator page is retired. -Clarion's current release handoff and tag-cut sequence live in +Loomweave's current release handoff and tag-cut sequence live in [`release-handoff.md`](./release-handoff.md). diff --git a/docs/operator/v1.0-release-rollback.md b/docs/operator/v1.0-release-rollback.md index 1d34955f..c4f8b2e8 100644 --- a/docs/operator/v1.0-release-rollback.md +++ b/docs/operator/v1.0-release-rollback.md @@ -40,8 +40,8 @@ be withdrawn).** A broken download URL is materially worse than a stale file: - Operator docs and the Sprint 1 walkthrough pin specific release-asset URLs by tag. A `curl -L` against a deleted asset returns 404; a stale-but-known binary at least produces a reproducible failure mode. -- Filigree CI and any downstream consumers that pin Clarion plugin URLs (e.g. - `pipx install https://.../clarion-plugin-python-1.0.0.tar.gz`) break loudly +- Filigree CI and any downstream consumers that pin Loomweave plugin URLs (e.g. + `pipx install https://.../loomweave-plugin-python-1.0.0.tar.gz`) break loudly rather than silently rolling forward. - The cosign signatures and SLSA provenance attestations are bound to the exact asset bytes; deleting and re-uploading does not restore the original @@ -77,7 +77,7 @@ not assume the Rekor entry can be removed — it cannot. ## 4. v1.0.1 publication — the supersession mechanism -The standard Clarion tag-cut procedure applies, with one addition: the v1.0.1 +The standard Loomweave tag-cut procedure applies, with one addition: the v1.0.1 release body **must** carry a supersession note. Suggested body opening: @@ -91,14 +91,14 @@ upgrade to v1.0.1; the v1.0.0 cosign signatures and Rekor entries remain verifiable but should not be used to gate new installs. ``` -Run the full Clarion CI and release dry run before tagging v1.0.1. Legis-owned -governance may publish companion evidence for the release train, but Clarion no +Run the full Loomweave CI and release dry run before tagging v1.0.1. Legis-owned +governance may publish companion evidence for the release train, but Loomweave no longer requires a live GitHub governance guard to publish its own artifacts. ## 5. Downstream notification Any known Filigree integrators and other downstream consumers (e.g. -operators running `pipx install` of the Clarion plugin against a pinned +operators running `pipx install` of the Loomweave plugin against a pinned v1.0.0 URL) must be informed out-of-band that v1.0.0 has been yanked. **Current notification path (informal):** the maintainer notifies known diff --git a/docs/suite/README.md b/docs/suite/README.md index 1a638446..ba51ed28 100644 --- a/docs/suite/README.md +++ b/docs/suite/README.md @@ -1,19 +1,19 @@ # Suite Docs -This folder holds the Loom-wide documents. +This folder holds the Weft-wide documents. -> **Authoritative federation hub:** `~/loom` is the single authoritative source for federation-wide interoperability — the doctrine (`~/loom/doctrine.md`), the cross-product glossary (`~/loom/glossary.md`), the SEI standard, the federation map, and the contract index. The `loom.md` and `glossary.md` files in this folder are now pointer stubs to the hub (promoted 2026-06-05). Clarion-owned docs (ADRs, federation contracts) remain authoritative in this repo. +> **Authoritative federation hub:** `~/loom` is the single authoritative source for federation-wide interoperability — the doctrine (`~/loom/doctrine.md`), the cross-product glossary (`~/loom/glossary.md`), the SEI standard, the federation map, and the contract index. The `weft.md` and `glossary.md` files in this folder are now pointer stubs to the hub (promoted 2026-06-05). Loomweave-owned docs (ADRs, federation contracts) remain authoritative in this repo. ## Canonical docs -- [loom.md](./loom.md) — the suite doctrine: bounded authority, federation, and the composition law. +- [weft.md](./weft.md) — the suite doctrine: bounded authority, federation, and the composition law. - [briefing.md](./briefing.md) — the 5-minute introduction for new readers. ## Read this folder in order 1. [briefing.md](./briefing.md) -2. [loom.md](./loom.md) +2. [weft.md](./weft.md) ## Product docs -- [Clarion](../clarion/README.md) +- [Loomweave](../loomweave/README.md) diff --git a/docs/suite/briefing.md b/docs/suite/briefing.md index 18980f82..55af3e45 100644 --- a/docs/suite/briefing.md +++ b/docs/suite/briefing.md @@ -1,6 +1,6 @@ -# The Loom Suite — A Briefing +# The Weft Suite — A Briefing -**Audience**: engineers, reviewers, or stakeholders new to the Loom suite +**Audience**: engineers, reviewers, or stakeholders new to the Weft suite **Purpose**: explain what each tool does, how they fit together, and what state the suite is in today **Reading time**: ~5 minutes @@ -8,23 +8,23 @@ ## The one-paragraph version -**Loom** is a suite for enterprise-grade code governance on small teams. Its v0.1 products — **Clarion**, **Filigree**, and **Wardline** — are three independent tools that enrich one another through narrow additive protocols. Each is fully authoritative in its domain and fully usable on its own. Clarion builds a trustworthy catalog of a codebase and answers structural questions. Filigree tracks the issues, findings, and observations that arise from examining that codebase. Wardline declares and enforces the trust topology that constrains how code is allowed to behave. Together they deliver rigor that normally requires enterprise-scale platform teams — without the operational weight, and without any shared runtime, store, or orchestrator. A fourth product, **Shuttle**, is proposed for transactional scoped change execution; see [loom.md](./loom.md) for the suite's founding doctrine, the enrichment-not-load-bearing principle, and the go/no-go test that governs future products. +**Weft** is a suite for enterprise-grade code governance on small teams. Its v0.1 products — **Loomweave**, **Filigree**, and **Wardline** — are three independent tools that enrich one another through narrow additive protocols. Each is fully authoritative in its domain and fully usable on its own. Loomweave builds a trustworthy catalog of a codebase and answers structural questions. Filigree tracks the issues, findings, and observations that arise from examining that codebase. Wardline declares and enforces the trust topology that constrains how code is allowed to behave. Together they deliver rigor that normally requires enterprise-scale platform teams — without the operational weight, and without any shared runtime, store, or orchestrator. A fourth product, **Shuttle**, is proposed for transactional scoped change execution; see [weft.md](./weft.md) for the suite's founding doctrine, the enrichment-not-load-bearing principle, and the go/no-go test that governs future products. -> **Authoritative source.** The Loom federation axiom, roster, and composition law are now authoritative at the federation hub: `~/loom/doctrine.md` (as of 2026-06-05). The hub's canonical roster is **5 realized members** — Clarion, Filigree, Wardline, **Legis**, and **Charter** — plus **Shuttle as a roadmap thought-bubble**; the three-member v0.1 framing in this briefing predates Legis and Charter and is kept as Clarion's local intro. This briefing remains Clarion's own introduction to the suite as Clarion sees it. +> **Authoritative source.** The Weft federation axiom, roster, and composition law are now authoritative at the federation hub: `~/loom/doctrine.md` (as of 2026-06-05). The hub's canonical roster is **5 realized members** — Loomweave, Filigree, Wardline, **Legis**, and **Charter** — plus **Shuttle as a roadmap thought-bubble**; the three-member v0.1 framing in this briefing predates Legis and Charter and is kept as Loomweave's local intro. This briefing remains Loomweave's own introduction to the suite as Loomweave sees it. --- -## The Loom products +## The Weft products -### Clarion — the code-archaeology catalog +### Loomweave — the code-archaeology catalog **Role**: indexes the source tree and answers structural questions. -Clarion ingests a codebase, extracts entities (functions, classes, modules, packages), clusters them into subsystems, and produces structured briefings that summarise each entity's purpose, maturity, and relationships. Consult-mode LLM agents query Clarion through MCP tools so they never need to spawn an explore-agent to answer "what are the entry points?" or "what calls this function?" — Clarion answered that during its batch analysis and caches the result. +Loomweave ingests a codebase, extracts entities (functions, classes, modules, packages), clusters them into subsystems, and produces structured briefings that summarise each entity's purpose, maturity, and relationships. Consult-mode LLM agents query Loomweave through MCP tools so they never need to spawn an explore-agent to answer "what are the entry points?" or "what calls this function?" — Loomweave answered that during its batch analysis and caches the result. **Authoritative for**: the entity catalog, the code graph, guidance sheets (institutional knowledge attached to entities), and structural / factual findings. -**Typical invocation**: `clarion analyze ` for batch indexing; `clarion serve` for MCP + HTTP consult. +**Typical invocation**: `loomweave analyze ` for batch indexing; `loomweave serve` for MCP + HTTP consult. **Status**: walking skeleton merged (Sprint 1, tagged `v0.1-sprint-1`); v0.1 build in flight against the published design. Target first customer is `elspeth` (~425k LOC Python). @@ -56,13 +56,13 @@ Wardline understands "which code is allowed to do what." Modules declare their t **Role**: executes an already-scoped change plan against the working tree with ordered edits, gated checks, rollback, and telemetry. -Shuttle is the Loom suite's change-execution layer. It receives a scoped change intent, binds it to concrete files or entities, orders the edits, applies them incrementally with pre- and post-change checks, rolls back on failure, and lints / commits / emits telemetry on success. It does **not** plan changes (Filigree tracks work), reason about correctness (Wardline and tests do), or understand code structure (Clarion does). +Shuttle is the Weft suite's change-execution layer. It receives a scoped change intent, binds it to concrete files or entities, orders the edits, applies them incrementally with pre- and post-change checks, rolls back on failure, and lints / commits / emits telemetry on success. It does **not** plan changes (Filigree tracks work), reason about correctness (Wardline and tests do), or understand code structure (Loomweave does). **Authoritative for**: the transactional execution record of a code change. **Typical invocation**: none yet; design not started. -**Status**: proposed. No design document. [loom.md](./loom.md) §7 describes the go/no-go test that gates new Loom products. +**Status**: proposed. No design document. [weft.md](./weft.md) §7 describes the go/no-go test that gates new Weft products. --- @@ -85,7 +85,7 @@ The suite is composed via two narrow protocols and a shared identity scheme. scan-results) │ │ ▼ │ ┌──────────────┐ ┌──────────────┐ - │ Clarion ├────►│ scan import │ + │ Loomweave ├────►│ scan import │ │ catalog + │ │ + observations│ │ briefings │◄────┤ │ └──────▲───────┘ └──────────────┘ @@ -106,30 +106,30 @@ The suite is composed via two narrow protocols and a shared identity scheme. | Flow | From | To | Mechanism | |---|---|---|---| -| Declared topology | Wardline manifest / fingerprint files | Clarion catalog | File read at `clarion analyze` | -| Annotation vocabulary | `wardline.core.registry.REGISTRY` | Clarion's Python plugin | Direct import at plugin startup | -| Findings | Clarion | Filigree | `POST /api/v1/scan-results` (Clarion-native schema) | -| Findings (Wardline-sourced) *(v0.2)* | Wardline SARIF → Clarion translator | Filigree | `POST /api/v1/scan-results` via `clarion sarif import`; deferred in v0.1 per Clarion ADR-015 — retires when Wardline emits natively to Filigree | -| Observations | Clarion consult mode | Filigree | MCP tool call (or HTTP once the endpoint ships) | -| Entity state | Clarion | Wardline (v0.2+) | Clarion HTTP read API; Wardline currently re-scans | -| Issue cross-references | Filigree | Clarion consult surface | Filigree read API | +| Declared topology | Wardline manifest / fingerprint files | Loomweave catalog | File read at `loomweave analyze` | +| Annotation vocabulary | `wardline.core.registry.REGISTRY` | Loomweave's Python plugin | Direct import at plugin startup | +| Findings | Loomweave | Filigree | `POST /api/v1/scan-results` (Loomweave-native schema) | +| Findings (Wardline-sourced) *(v0.2)* | Wardline SARIF → Loomweave translator | Filigree | `POST /api/v1/scan-results` via `loomweave sarif import`; deferred in v0.1 per Loomweave ADR-015 — retires when Wardline emits natively to Filigree | +| Observations | Loomweave consult mode | Filigree | MCP tool call (or HTTP once the endpoint ships) | +| Entity state | Loomweave | Wardline (v0.2+) | Loomweave HTTP read API; Wardline currently re-scans | +| Issue cross-references | Filigree | Loomweave consult surface | Filigree read API | ### Identity and the shared vocabulary -The glue between tools is the **entity ID**. Clarion owns the entity catalog and mints stable symbolic identifiers (`python:class:auth.tokens::TokenManager`). Filigree issues reference entities by Clarion ID. Wardline findings carry qualnames that Clarion reconciles to entity IDs at ingest. The suite has three concurrent identity schemes (Clarion EntityId, Wardline qualname, Wardline exception-register location string) — Clarion maintains the translation layer; neither sibling tool takes on that responsibility. +The glue between tools is the **entity ID**. Loomweave owns the entity catalog and mints stable symbolic identifiers (`python:class:auth.tokens::TokenManager`). Filigree issues reference entities by Loomweave ID. Wardline findings carry qualnames that Loomweave reconciles to entity IDs at ingest. The suite has three concurrent identity schemes (Loomweave EntityId, Wardline qualname, Wardline exception-register location string) — Loomweave maintains the translation layer; neither sibling tool takes on that responsibility. -Findings are the other glue: every tool emits findings into Filigree's `POST /api/v1/scan-results` with a distinct `scan_source` (`clarion`, `wardline`, and so on). Filigree preserves the `metadata` dict verbatim, so Clarion's richer fields (`kind`, `confidence`, `related_entities`) and Wardline's SARIF property-bag extensions survive ingest under namespaced keys (`metadata.clarion.*`, `metadata.wardline_properties.*`). +Findings are the other glue: every tool emits findings into Filigree's `POST /api/v1/scan-results` with a distinct `scan_source` (`loomweave`, `wardline`, and so on). Filigree preserves the `metadata` dict verbatim, so Loomweave's richer fields (`kind`, `confidence`, `related_entities`) and Wardline's SARIF property-bag extensions survive ingest under namespaced keys (`metadata.loomweave.*`, `metadata.wardline_properties.*`). --- ## Principles that shape the suite -Four commitments keep the Loom products from drifting into overlap (see [loom.md](./loom.md) for the suite's full doctrine, including the federation axiom and the composition law): +Four commitments keep the Weft products from drifting into overlap (see [weft.md](./weft.md) for the suite's full doctrine, including the federation axiom and the composition law): -1. **Clarion observes, Wardline enforces.** Clarion detects that an annotation is present; Wardline determines whether the annotated code satisfies the semantic it declares. Clarion never re-implements Wardline analyses; Wardline never re-implements Clarion's graph. -2. **Findings are facts, not just errors.** A unified `Finding` record type carries defects, structural observations, classifications, metrics, and suggestions across all Loom products. -3. **Each tool is independently useful.** Clarion works without Filigree (writes findings to local JSONL). Wardline works without Clarion (has since day one). Filigree works without either. -4. **Local-first, single-binary, git-committable state.** No hosted service is required; `.clarion/`, `.filigree/`, and Wardline's JSON state files are all meant to be committed and shared. +1. **Loomweave observes, Wardline enforces.** Loomweave detects that an annotation is present; Wardline determines whether the annotated code satisfies the semantic it declares. Loomweave never re-implements Wardline analyses; Wardline never re-implements Loomweave's graph. +2. **Findings are facts, not just errors.** A unified `Finding` record type carries defects, structural observations, classifications, metrics, and suggestions across all Weft products. +3. **Each tool is independently useful.** Loomweave works without Filigree (writes findings to local JSONL). Wardline works without Loomweave (has since day one). Filigree works without either. +4. **Local-first, single-binary, git-committable state.** No hosted service is required; `.loomweave/`, `.filigree/`, and Wardline's JSON state files are all meant to be committed and shared. --- @@ -139,10 +139,10 @@ Four commitments keep the Loom products from drifting into overlap (see [loom.md |---|---|---|---| | Filigree | Yes | Yes — active development | `filigree` itself; this project | | Wardline | Yes | Yes — commit-cadence scanner | Wardline's own codebase | -| Clarion | Partial — Sprint 1 walking skeleton tagged `v0.1-sprint-1`; v0.1 build in flight | Not yet — pre-v0.1 release | `elspeth` (~425k LOC Python) targeted for v0.1 validation | +| Loomweave | Partial — Sprint 1 walking skeleton tagged `v0.1-sprint-1`; v0.1 build in flight | Not yet — pre-v0.1 release | `elspeth` (~425k LOC Python) targeted for v0.1 validation | | Shuttle | No — proposed; no design yet | Not yet | None — not yet scoped | -### What Clarion v0.1 ships +### What Loomweave v0.1 ships A single-binary Rust core plus a Python language plugin. The core handles storage, LLM orchestration, clustering, and MCP read-only consult; the plugin handles Python parsing, import resolution, and entity extraction. @@ -152,24 +152,24 @@ v0.1 is scoped as **minimal-core plus the Filigree registry handover**: - Python-plugin parsing and entity extraction. - Local `findings.jsonl` writer. - MCP read-only consult surface. -- Filigree `registry_backend: clarion` integration so Clarion owns the file registry end-to-end. Filigree-side work lands alongside Clarion's own release. +- Filigree `registry_backend: loomweave` integration so Loomweave owns the file registry end-to-end. Filigree-side work lands alongside Loomweave's own release. Deferred to v0.2 with written retirement conditions: -- **Wardline→Filigree SARIF bridge.** Wardline findings flow to Filigree only when Wardline ships its own native Filigree emitter (Clarion ADR-015). Until then, the (Wardline, Filigree) pair composes outside Clarion, via Wardline's existing SARIF-to-GitHub-Security path. `loom.md` §5 names this as a v0.1 asterisk. -- **Observation HTTP transport.** Clarion emits observations via MCP tool calls in v0.1; a dedicated Filigree HTTP endpoint lands in v0.2. -- **Clarion HTTP write API and summary cache beyond in-memory.** Read-only consult in v0.1; write surface deferred. +- **Wardline→Filigree SARIF bridge.** Wardline findings flow to Filigree only when Wardline ships its own native Filigree emitter (Loomweave ADR-015). Until then, the (Wardline, Filigree) pair composes outside Loomweave, via Wardline's existing SARIF-to-GitHub-Security path. `weft.md` §5 names this as a v0.1 asterisk. +- **Observation HTTP transport.** Loomweave emits observations via MCP tool calls in v0.1; a dedicated Filigree HTTP endpoint lands in v0.2. +- **Loomweave HTTP write API and summary cache beyond in-memory.** Read-only consult in v0.1; write surface deferred. -### What the suite needs from Filigree and Wardline for Clarion to ship +### What the suite needs from Filigree and Wardline for Loomweave to ship -Several changes land in the sibling tools as Clarion-v0.1 prerequisites. All three products are maintained together, so these are within-scope work items rather than external dependencies: +Several changes land in the sibling tools as Loomweave-v0.1 prerequisites. All three products are maintained together, so these are within-scope work items rather than external dependencies: -- **Filigree (v0.1)**: a pluggable `registry_backend` (authored jointly with Clarion ADR-014) so Clarion can own the file registry; a published schema-compatibility contract (`NFR-COMPAT-01`). +- **Filigree (v0.1)**: a pluggable `registry_backend` (authored jointly with Loomweave ADR-014) so Loomweave can own the file registry; a published schema-compatibility contract (`NFR-COMPAT-01`). - **Filigree (v0.2)**: an HTTP endpoint for observation creation. -- **Wardline (v0.1)**: a stable `REGISTRY_VERSION` that Clarion's plugin pins against; a commitment to maintain legacy-decorator aliases. -- **Wardline (v0.2)**: a native emitter to Filigree so Clarion's SARIF translator can be retired per ADR-015. +- **Wardline (v0.1)**: a stable `REGISTRY_VERSION` that Loomweave's plugin pins against; a commitment to maintain legacy-decorator aliases. +- **Wardline (v0.2)**: a native emitter to Filigree so Loomweave's SARIF translator can be retired per ADR-015. -Clarion's v0.1 design set spells these asks out in [system-design.md](../clarion/v0.1/system-design.md) and [detailed-design.md](../clarion/v0.1/detailed-design.md). Clarion ships with degraded-mode fallbacks (`--no-filigree`, `--no-wardline`) so operators using only part of the suite still get a coherent product. +Loomweave's v0.1 design set spells these asks out in [system-design.md](../loomweave/v0.1/system-design.md) and [detailed-design.md](../loomweave/v0.1/detailed-design.md). Loomweave ships with degraded-mode fallbacks (`--no-filigree`, `--no-wardline`) so operators using only part of the suite still get a coherent product. --- @@ -177,12 +177,12 @@ Clarion's v0.1 design set spells these asks out in [system-design.md](../clarion | If you want to… | Read | |---|---| -| Read Loom's founding doctrine — federation axiom, composition law, go/no-go test | [loom.md](./loom.md) | -| Enter the Clarion v0.1 docset in reading order | [../clarion/v0.1/README.md](../clarion/v0.1/README.md) | -| Read Clarion's requirements | [../clarion/v0.1/requirements.md](../clarion/v0.1/requirements.md) | -| Read Clarion's system design | [../clarion/v0.1/system-design.md](../clarion/v0.1/system-design.md) | -| Read Clarion's detailed design reference | [../clarion/v0.1/detailed-design.md](../clarion/v0.1/detailed-design.md) | -| Read accepted architecture decisions | [../clarion/adr/README.md](../clarion/adr/README.md) | +| Read Weft's founding doctrine — federation axiom, composition law, go/no-go test | [weft.md](./weft.md) | +| Enter the Loomweave v0.1 docset in reading order | [../loomweave/v0.1/README.md](../loomweave/v0.1/README.md) | +| Read Loomweave's requirements | [../loomweave/v0.1/requirements.md](../loomweave/v0.1/requirements.md) | +| Read Loomweave's system design | [../loomweave/v0.1/system-design.md](../loomweave/v0.1/system-design.md) | +| Read Loomweave's detailed design reference | [../loomweave/v0.1/detailed-design.md](../loomweave/v0.1/detailed-design.md) | +| Read accepted architecture decisions | [../loomweave/adr/README.md](../loomweave/adr/README.md) | | Review the planning and review archive | [../implementation/README.md](../implementation/README.md) | | Work with Filigree today | Check out the Filigree repository; start with its `CLAUDE.md` and `filigree --help`. | | Work with Wardline today | Check out the Wardline repository; start with `docs/spec/`. | diff --git a/docs/suite/glossary.md b/docs/suite/glossary.md index d10507ea..2405b8e9 100644 --- a/docs/suite/glossary.md +++ b/docs/suite/glossary.md @@ -1,6 +1,6 @@ -# Loom suite glossary +# Weft suite glossary -> **This glossary has been promoted to the Loom federation hub.** +> **This glossary has been promoted to the Weft federation hub.** > The canonical, authoritative cross-product vocabulary catalogue — every term > whose meaning crosses product boundaries, with its managed/renamed/no-clash > verdict and authority — now lives at @@ -19,6 +19,6 @@ there. The federation axiom this glossary defends is the cross-product field-name rule in the hub doctrine: `~/loom/doctrine.md` §8. -Clarion's ADRs (e.g. ADR-004, ADR-017, ADR-022, ADR-024, ADR-036, ADR-038) -remain Clarion-owned and authoritative for Clarion's own field shapes; the hub +Loomweave's ADRs (e.g. ADR-004, ADR-017, ADR-022, ADR-024, ADR-036, ADR-038) +remain Loomweave-owned and authoritative for Loomweave's own field shapes; the hub glossary points to them, not the reverse. diff --git a/docs/suite/loom.md b/docs/suite/weft.md similarity index 73% rename from docs/suite/loom.md rename to docs/suite/weft.md index 3cc600db..de18eb60 100644 --- a/docs/suite/loom.md +++ b/docs/suite/weft.md @@ -1,13 +1,13 @@ -# Loom +# Weft -> **This founding doctrine has been promoted to the Loom federation hub.** -> The canonical, authoritative Loom federation doctrine — federation axiom, +> **This founding doctrine has been promoted to the Weft federation hub.** +> The canonical, authoritative Weft federation doctrine — federation axiom, > roster, composition law, enrichment-not-load-bearing test, and the go/no-go > gate for new products — now lives at **`~/loom/doctrine.md`** (authoritative as of 2026-06-05). > This file is retained as a **pointer stub** so existing references resolve. > > The doctrine was promoted faithfully and **preserves the original section -> numbers**, so any reference of the form `loom.md §N` (e.g. `loom.md §5` = +> numbers**, so any reference of the form `weft.md §N` (e.g. `weft.md §5` = > enrichment-not-load-bearing, `§7` = go/no-go, `§8` = naming) resolves to the > same content at `~/loom/doctrine.md §N`. @@ -21,9 +21,9 @@ go/no-go test, and naming) is now authoritative at `~/loom/doctrine.md`. Read it there. One substantive update the hub makes over the old body of this file: the -**federation roster is now 5 realized members** — Clarion, Filigree, Wardline, +**federation roster is now 5 realized members** — Loomweave, Filigree, Wardline, **Legis**, and **Charter** — plus **Shuttle as a roadmap thought-bubble** (not a -committed member). The old §1/§9 three-member framing (Clarion + Filigree + +committed member). The old §1/§9 three-member framing (Loomweave + Filigree + Wardline, with Shuttle "proposed") is **superseded** by the hub: Legis and Charter shipped/were-designed after this file was last written, and the hub is the body that declares the roster. See the doctrine's §1 roster note and @@ -38,11 +38,11 @@ the body that declares the roster. See the doctrine's §1 roster note and - `~/loom/contracts-index.md` — the cross-product contract index - `~/loom/asterisk-register.md` — the live/retired federation asterisks -## Clarion-local notes +## Loomweave-local notes -The only Clarion-specific detail in the old body was the operator-trust pointer -for Clarion's HTTP read API (its unauthenticated, loopback-only default and the +The only Loomweave-specific detail in the old body was the operator-trust pointer +for Loomweave's HTTP read API (its unauthenticated, loopback-only default and the reverse-proxy requirement before any non-loopback bind). That remains documented -in Clarion's own -[`docs/operator/clarion-http-read-api.md`](../operator/clarion-http-read-api.md); +in Loomweave's own +[`docs/operator/loomweave-http-read-api.md`](../operator/loomweave-http-read-api.md); the federation-pattern framing around it lives in the hub doctrine. diff --git a/docs/superpowers/plans/2026-05-31-clarion-wardline-taint-store.md b/docs/superpowers/plans/2026-05-31-loomweave-wardline-taint-store.md similarity index 80% rename from docs/superpowers/plans/2026-05-31-clarion-wardline-taint-store.md rename to docs/superpowers/plans/2026-05-31-loomweave-wardline-taint-store.md index be028e17..05db98b8 100644 --- a/docs/superpowers/plans/2026-05-31-clarion-wardline-taint-store.md +++ b/docs/superpowers/plans/2026-05-31-loomweave-wardline-taint-store.md @@ -1,10 +1,10 @@ -# Clarion as Wardline taint-fact store (SP9) — Implementation Plan +# Loomweave as Wardline taint-fact store (SP9) — Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. -**Goal:** Give Clarion a Wardline-specific, per-entity taint-fact store with an HTTP read+write surface on `clarion serve`, so Wardline's `explain_taint` becomes a cheap query instead of a re-analysis. +**Goal:** Give Loomweave a Wardline-specific, per-entity taint-fact store with an HTTP read+write surface on `loomweave serve`, so Wardline's `explain_taint` becomes a cheap query instead of a re-analysis. -**Architecture:** A dedicated `wardline_taint_facts` SQLite table (migration `0003`), written through an *optional* ADR-011 writer-actor that `clarion serve` spawns only when the write API is config-enabled (default off). Resolution of Wardline's **pre-composed** dotted qualname to a Clarion `EntityId` is a direct existence lookup (`python:function:`) — Wardline owns the normalization (it pre-composes to byte-match Clarion's canonical form per `fixtures/wardline-qualname-normalization.json`), so Clarion does no normalization at resolution time. New HMAC-gated routes under `/api/wardline/*` and `/api/v1/entities/resolve` expose write, read, batch-get, and resolve. `wardline_json` is stored verbatim and opaque. +**Architecture:** A dedicated `wardline_taint_facts` SQLite table (migration `0003`), written through an *optional* ADR-011 writer-actor that `loomweave serve` spawns only when the write API is config-enabled (default off). Resolution of Wardline's **pre-composed** dotted qualname to a Loomweave `EntityId` is a direct existence lookup (`python:function:`) — Wardline owns the normalization (it pre-composes to byte-match Loomweave's canonical form per `fixtures/wardline-qualname-normalization.json`), so Loomweave does no normalization at resolution time. New HMAC-gated routes under `/api/wardline/*` and `/api/v1/entities/resolve` expose write, read, batch-get, and resolve. `wardline_json` is stored verbatim and opaque. **Tech Stack:** Rust (axum, rusqlite, tokio mpsc writer-actor), SQLite, HMAC inbound auth (ADR-034). Docs: ADR + federation contract. @@ -15,26 +15,26 @@ These were settled during brainstorming + plan grounding. They are the load-bearing facts the tasks below assume: 1. **Migration number is `0003`** — `0002_briefing_blocked.sql` already exists. The spec's "migration 0002" is superseded by this plan. `CURRENT_SCHEMA_VERSION` bumps `2 → 3`; a compile-time assert in `schema.rs` enforces the bump. -2. **Resolution is a direct lookup, not normalization.** Wardline sends the **pre-composed** dotted `module.qualified_name` (e.g. `auth.tokens.TokenManager.verify`). Clarion builds the candidate `python:function:` and checks existence via `existing_entity_ids`. No `module_dotted_name` port to Rust; no `&file=` disambiguator needed for this scheme. The 5 ADR-018 divergence traps (``, nested-class chains, `lib.foo`/`app.service` non-src roots, `a.src.b`) are *Wardline's* conformance burden against the fixture; on Clarion's side they become **verbatim-storage** tests (we must not strip or rewrite the composed string). This is the **exact tier**; Flow B's B.2 (`clarion-ca2d26ffbe`) extends it with the heuristic tier and must consume this resolver, not rebuild it. +2. **Resolution is a direct lookup, not normalization.** Wardline sends the **pre-composed** dotted `module.qualified_name` (e.g. `auth.tokens.TokenManager.verify`). Loomweave builds the candidate `python:function:` and checks existence via `existing_entity_ids`. No `module_dotted_name` port to Rust; no `&file=` disambiguator needed for this scheme. The 5 ADR-018 divergence traps (``, nested-class chains, `lib.foo`/`app.service` non-src roots, `a.src.b`) are *Wardline's* conformance burden against the fixture; on Loomweave's side they become **verbatim-storage** tests (we must not strip or rewrite the composed string). This is the **exact tier**; Flow B's B.2 (`clarion-ca2d26ffbe`) extends it with the heuristic tier and must consume this resolver, not rebuild it. 3. **Methods are `python:function:`** (not a distinct method kind), and the entity-id `canonical_qualified_name` uses all-dot separators. The `::` form in detailed-design §7 (`python:class:auth.tokens::TokenManager`) is **stale**; the normative fixture uses dots. Taint facts are function/method-scoped (request §3), so function-only resolution is sufficient — recorded as a deliberate scope line. 4. **Taint writes are query-time writes, not analyze-run writes** — they use the `query_time_write` actor path (like `UpsertSummaryCache`), not `BeginRun`/`CommitRun`. 5. **Body limit.** The existing HMAC middleware and `RequestBodyLimitLayer` cap bodies at `HTTP_BODY_LIMIT_BYTES = 16 KiB`. Batched taint writes exceed that, so the `/api/wardline/*` routes get a larger cap (`WARDLINE_BODY_LIMIT_BYTES`) and a per-request fact-count cap (`WARDLINE_TAINT_BATCH_MAX`); Wardline chunks client-side, exactly as Filigree splits against `BATCH_MAX_QUERIES`. -6. **`wardline_json` is opaque.** Clarion stores and returns the blob verbatim; it never parses it. `scan_id` and `content_hash_at_compute` are accepted as *separate top-level fields* on each fact (not parsed out of the blob) so they are queryable columns. +6. **`wardline_json` is opaque.** Loomweave stores and returns the blob verbatim; it never parses it. `scan_id` and `content_hash_at_compute` are accepted as *separate top-level fields* on each fact (not parsed out of the blob) so they are queryable columns. ## File structure | File | Responsibility | Tasks | |---|---|---| -| `docs/clarion/adr/ADR-036-wardline-taint-fact-store.md` | The federation decision + read→read+write shift + not-a-blob-store guard | T0 | -| `crates/clarion-storage/migrations/0003_wardline_taint_facts.sql` | The table DDL | T1 | -| `crates/clarion-storage/src/schema.rs` | Register migration `0003`, bump `CURRENT_SCHEMA_VERSION` | T1 | -| `crates/clarion-storage/src/wardline_taint.rs` | Records + `upsert_taint_fact` + `get_taint_facts` + `resolve_wardline_qualname` | T2, T4 | -| `crates/clarion-storage/src/lib.rs` | Re-export the new module's public items | T2 | -| `crates/clarion-storage/src/commands.rs` | `WriterCmd::UpsertWardlineTaintFact` variant + `WardlineTaintFactRecord` | T3 | -| `crates/clarion-storage/src/writer.rs` | Actor-loop arm dispatching the new command via `query_time_write` | T3 | -| `crates/clarion-mcp/src/config.rs` | `HttpReadConfig.wardline_taint_write: bool` (default false) | T5 | -| `crates/clarion-cli/src/http_read.rs` | New routes, handlers, AppState fields, larger body limit, optional writer plumbing | T5–T8 | -| `crates/clarion-cli/src/serve.rs` | Pass the write-enable knob into `http_read::spawn` | T5 | +| `docs/loomweave/adr/ADR-036-wardline-taint-fact-store.md` | The federation decision + read→read+write shift + not-a-blob-store guard | T0 | +| `crates/loomweave-storage/migrations/0003_wardline_taint_facts.sql` | The table DDL | T1 | +| `crates/loomweave-storage/src/schema.rs` | Register migration `0003`, bump `CURRENT_SCHEMA_VERSION` | T1 | +| `crates/loomweave-storage/src/wardline_taint.rs` | Records + `upsert_taint_fact` + `get_taint_facts` + `resolve_wardline_qualname` | T2, T4 | +| `crates/loomweave-storage/src/lib.rs` | Re-export the new module's public items | T2 | +| `crates/loomweave-storage/src/commands.rs` | `WriterCmd::UpsertWardlineTaintFact` variant + `WardlineTaintFactRecord` | T3 | +| `crates/loomweave-storage/src/writer.rs` | Actor-loop arm dispatching the new command via `query_time_write` | T3 | +| `crates/loomweave-mcp/src/config.rs` | `HttpReadConfig.wardline_taint_write: bool` (default false) | T5 | +| `crates/loomweave-cli/src/http_read.rs` | New routes, handlers, AppState fields, larger body limit, optional writer plumbing | T5–T8 | +| `crates/loomweave-cli/src/serve.rs` | Pass the write-enable knob into `http_read::spawn` | T5 | | `docs/federation/contracts.md` | Pin the new routes + freshness contract | T9 | --- @@ -44,29 +44,29 @@ These were settled during brainstorming + plan grounding. They are the load-bear **Doc task, no TDD.** ADR files are immutable once Accepted; write it complete and correct the first time. **Files:** -- Create: `docs/clarion/adr/ADR-036-wardline-taint-fact-store.md` +- Create: `docs/loomweave/adr/ADR-036-wardline-taint-fact-store.md` - [ ] **Step 1: Write the ADR** following the repo's ADR format (see `ADR-035` header shape: `# ADR-036: …`, then `**Status**`, `**Date**`, `**Deciders**`, then Context / Decision / Consequences). It MUST carry, verbatim in spirit: - **Status:** Accepted. **Date:** 2026-05-31. - - **Context:** Wardline SP9 (`wardline/docs/integration/2026-05-30-wardline-clarion-taint-store-requirements.md`) needs a persistent per-entity taint store keyed by Clarion entity. Clarion's HTTP API is read-only today (ADR-014/ADR-034). - - **Decision:** Clarion builds a **Wardline-specific** per-entity taint-fact store: a dedicated `wardline_taint_facts` table and `/api/wardline/*` routes. This is the **first read+write** use of Clarion's HTTP API. Writes go through an **optional** ADR-011 writer-actor spawned by `clarion serve` only when config-enabled (default off). Resolution is exact-tier direct lookup of Wardline's pre-composed qualname. + - **Context:** Wardline SP9 (`wardline/docs/integration/2026-05-30-wardline-loomweave-taint-store-requirements.md`) needs a persistent per-entity taint store keyed by Loomweave entity. Loomweave's HTTP API is read-only today (ADR-014/ADR-034). + - **Decision:** Loomweave builds a **Wardline-specific** per-entity taint-fact store: a dedicated `wardline_taint_facts` table and `/api/wardline/*` routes. This is the **first read+write** use of Loomweave's HTTP API. Writes go through an **optional** ADR-011 writer-actor spawned by `loomweave serve` only when config-enabled (default off). Resolution is exact-tier direct lookup of Wardline's pre-composed qualname. - **The load-bearing guard (quote it):** *"This is not a precedent for a general-purpose cross-product blob store. The next sibling that wants per-entity persistence gets its own named, justified surface or it does not get one."* - - **Federation analysis:** passes `loom.md` §3–§5 (enrich-only, both solo-useful, no semantic coupling — `wardline_json` is opaque). Recorded as an ADR, **not** a §5 asterisk, because it *passes* the failure test rather than accepting a violation. - - **Concurrency posture:** cite ADR-011; a write-enabled `serve` and a concurrent `analyze` are not expected to write the same DB simultaneously; cross-process contention is handled by `PRAGMA busy_timeout=5000` + the `clarion-storage::retry` capped-backoff layer; a write that still cannot land fails retryably and Wardline degrades to SP8. + - **Federation analysis:** passes `weft.md` §3–§5 (enrich-only, both solo-useful, no semantic coupling — `wardline_json` is opaque). Recorded as an ADR, **not** a §5 asterisk, because it *passes* the failure test rather than accepting a violation. + - **Concurrency posture:** cite ADR-011; a write-enabled `serve` and a concurrent `analyze` are not expected to write the same DB simultaneously; cross-process contention is handled by `PRAGMA busy_timeout=5000` + the `loomweave-storage::retry` capped-backoff layer; a write that still cannot land fails retryably and Wardline degrades to SP8. - **Consequences:** lists migration `0003`, the new routes, the `serve.http.wardline_taint_write` config knob, and that the heuristic resolution tier + the conformance oracle (`scheme=wardline_qualname` over raw file+qualname) remain deferred (Flow B B.2). - - Reference the design spec `docs/superpowers/specs/2026-05-30-clarion-wardline-taint-store-design.md`. + - Reference the design spec `docs/superpowers/specs/2026-05-30-loomweave-wardline-taint-store-design.md`. -- [ ] **Step 2: Register it in the ADR index.** Add the ADR-036 row to `docs/clarion/adr/README.md` matching the existing table/list format. +- [ ] **Step 2: Register it in the ADR index.** Add the ADR-036 row to `docs/loomweave/adr/README.md` matching the existing table/list format. - [ ] **Step 3: Update CLAUDE.md precedence count.** `CLAUDE.md` says "28 are Accepted at 1.0 (ADR-001…ADR-007, ADR-011, ADR-013…ADR-018, ADR-021…ADR-034)". This is a 1.0-scope statement; do **not** rewrite the 1.0 count. Instead confirm no edit is needed (ADR-036 is a 1.1 addition, outside the quoted 1.0 enumeration). If a maintainer wants a 1.1 ADR list later, that is separate. **No code change in this step — just verify and note in the commit body.** - [ ] **Step 4: Commit** ```bash -git add docs/clarion/adr/ADR-036-wardline-taint-fact-store.md docs/clarion/adr/README.md -git commit -m "docs(adr): ADR-036 — Clarion as Wardline taint-fact store (read+write HTTP) +git add docs/loomweave/adr/ADR-036-wardline-taint-fact-store.md docs/loomweave/adr/README.md +git commit -m "docs(adr): ADR-036 — Loomweave as Wardline taint-fact store (read+write HTTP) -W.0 (clarion-e1a5971e42). Records the federation verdict (passes loom.md +W.0 (clarion-e1a5971e42). Records the federation verdict (passes weft.md §3-§5, ADR not asterisk), the read-only->read+write shift, the optional writer-actor concurrency posture, and the not-a-general-blob-store guard. @@ -78,19 +78,19 @@ Co-Authored-By: Claude Opus 4.8 (1M context) " ## Task 1 (W.1a — `clarion-6cc50d7a1d`): Migration `0003` + schema-version bump **Files:** -- Create: `crates/clarion-storage/migrations/0003_wardline_taint_facts.sql` -- Modify: `crates/clarion-storage/src/schema.rs:17-41` (MIGRATIONS array + `CURRENT_SCHEMA_VERSION`) +- Create: `crates/loomweave-storage/migrations/0003_wardline_taint_facts.sql` +- Modify: `crates/loomweave-storage/src/schema.rs:17-41` (MIGRATIONS array + `CURRENT_SCHEMA_VERSION`) - Test: existing `schema.rs` migration tests + a new round-trip test in `wardline_taint.rs` (T2) - [ ] **Step 1: Write the migration SQL** -Create `crates/clarion-storage/migrations/0003_wardline_taint_facts.sql`: +Create `crates/loomweave-storage/migrations/0003_wardline_taint_facts.sql`: ```sql -- Migration 0003: Wardline taint-fact store (SP9, ADR-036). -- Dedicated, Wardline-owned per-entity table. NOT the schema-reserved -- `entities.wardline` column (which `analyze` clobbers with NULL on every --- re-index). `wardline_json` is opaque to Clarion — stored and returned +-- re-index). `wardline_json` is opaque to Loomweave — stored and returned -- verbatim. `scan_id` and `content_hash_at_compute` are queryable columns -- supplied by the caller, not parsed out of the blob. CREATE TABLE wardline_taint_facts ( @@ -103,11 +103,11 @@ CREATE TABLE wardline_taint_facts ( ) STRICT; ``` -> Note: `0001_initial_schema.sql` uses `STRICT` tables — match that. If grepping `0001` shows it does **not** use `STRICT`, drop the `STRICT` keyword to match the house style. Verify with `grep -c STRICT crates/clarion-storage/migrations/0001_initial_schema.sql` before committing. +> Note: `0001_initial_schema.sql` uses `STRICT` tables — match that. If grepping `0001` shows it does **not** use `STRICT`, drop the `STRICT` keyword to match the house style. Verify with `grep -c STRICT crates/loomweave-storage/migrations/0001_initial_schema.sql` before committing. - [ ] **Step 2: Register the migration and bump the version** -In `crates/clarion-storage/src/schema.rs`, append to the `MIGRATIONS` array (after the `0002_briefing_blocked` entry): +In `crates/loomweave-storage/src/schema.rs`, append to the `MIGRATIONS` array (after the `0002_briefing_blocked` entry): ```rust Migration { @@ -127,18 +127,18 @@ pub const CURRENT_SCHEMA_VERSION: u32 = 3; - [ ] **Step 3: Build to verify the compile-time assert passes** -Run: `cargo build -p clarion-storage` +Run: `cargo build -p loomweave-storage` Expected: compiles clean (the const assert is satisfied: highest migration version `3` == `CURRENT_SCHEMA_VERSION`). - [ ] **Step 4: Run the existing schema/migration tests** -Run: `cargo nextest run -p clarion-storage schema` +Run: `cargo nextest run -p loomweave-storage schema` Expected: PASS — existing migration-apply tests now apply `0003` too and write `user_version = 3`. - [ ] **Step 5: Commit** ```bash -git add crates/clarion-storage/migrations/0003_wardline_taint_facts.sql crates/clarion-storage/src/schema.rs +git add crates/loomweave-storage/migrations/0003_wardline_taint_facts.sql crates/loomweave-storage/src/schema.rs git commit -m "feat(storage): migration 0003 — wardline_taint_facts table (W.1, ADR-036) Co-Authored-By: Claude Opus 4.8 (1M context) " @@ -149,21 +149,21 @@ Co-Authored-By: Claude Opus 4.8 (1M context) " ## Task 2 (W.1b — `clarion-6cc50d7a1d`): Storage module — records, upsert, fetch, resolve **Files:** -- Create: `crates/clarion-storage/src/wardline_taint.rs` -- Modify: `crates/clarion-storage/src/lib.rs` (add `mod wardline_taint;` + re-exports) +- Create: `crates/loomweave-storage/src/wardline_taint.rs` +- Modify: `crates/loomweave-storage/src/lib.rs` (add `mod wardline_taint;` + re-exports) - Test: inline `#[cfg(test)]` module in `wardline_taint.rs` The resolver and the fetch are read-pool operations; the upsert helper is invoked by the writer-actor (T3). All three live here so the SQL is in one place. - [ ] **Step 1: Write the failing test for `resolve_wardline_qualname` using the fixture vectors** -Create `crates/clarion-storage/src/wardline_taint.rs` with the test module first. The test seeds entities matching the fixture's `expected_entity_id`s and asserts resolution. Use the **exact** strings from `docs/federation/fixtures/wardline-qualname-normalization.json` `qualified_name_vectors`: +Create `crates/loomweave-storage/src/wardline_taint.rs` with the test module first. The test seeds entities matching the fixture's `expected_entity_id`s and asserts resolution. Use the **exact** strings from `docs/federation/fixtures/wardline-qualname-normalization.json` `qualified_name_vectors`: ```rust //! Wardline taint-fact store (SP9, ADR-036). Dedicated per-entity table; //! `wardline_json` is opaque (stored/returned verbatim). Resolution is the //! exact tier: Wardline pre-composes its dotted qualname to byte-match -//! Clarion's canonical_qualified_name, so resolution is a direct existence +//! Loomweave's canonical_qualified_name, so resolution is a direct existence //! lookup of `python:function:`. Heuristic tier is Flow B B.2. use std::collections::HashSet; @@ -192,12 +192,12 @@ pub struct Resolution { /// Build the candidate entity id for a Wardline pre-composed qualname. /// Taint facts are function/method-scoped (request §3); methods are -/// `python:function:` in Clarion's ontology (ADR-022, fixture-confirmed). +/// `python:function:` in Loomweave's ontology (ADR-022, fixture-confirmed). fn function_candidate(qualname: &str) -> String { format!("python:function:{qualname}") } -/// Resolve one pre-composed Wardline qualname to a Clarion entity id (exact +/// Resolve one pre-composed Wardline qualname to a Loomweave entity id (exact /// tier). Returns `Exact` with the id when the entity exists, else `None`. pub fn resolve_wardline_qualname(conn: &Connection, qualname: &str) -> Result { let resolved = resolve_wardline_qualnames(conn, std::slice::from_ref(&qualname.to_owned()))?; @@ -314,7 +314,7 @@ mod tests { - [ ] **Step 2: Wire the module into the crate** -In `crates/clarion-storage/src/lib.rs`, add `mod wardline_taint;` alongside the other `mod` declarations, and re-export the public items next to the existing `pub use` lines: +In `crates/loomweave-storage/src/lib.rs`, add `mod wardline_taint;` alongside the other `mod` declarations, and re-export the public items next to the existing `pub use` lines: ```rust pub use wardline_taint::{ @@ -327,7 +327,7 @@ pub use wardline_taint::{ - [ ] **Step 3: Run the resolver tests (they should fail to compile first, then pass)** -Run: `cargo nextest run -p clarion-storage wardline_taint` +Run: `cargo nextest run -p loomweave-storage wardline_taint` Expected: first FAIL to compile (missing `TaintFact` etc. in the re-export). Temporarily comment the not-yet-defined names out of the `pub use` to run *just* the resolver tests, confirm they PASS, then restore the re-export and proceed to Step 4. (Or write Steps 4–5 first if working top-down; the resolver tests are independent of upsert/fetch.) - [ ] **Step 4: Add the upsert helper (writer-actor calls this)** @@ -335,7 +335,7 @@ Expected: first FAIL to compile (missing `TaintFact` etc. in the re-export). Tem Append to `wardline_taint.rs` (before the test module): ```rust -/// A single taint fact to persist. `wardline_json` is opaque to Clarion. +/// A single taint fact to persist. `wardline_json` is opaque to Loomweave. #[derive(Debug, Clone, PartialEq, Eq)] pub struct TaintFact { pub entity_id: String, @@ -427,7 +427,7 @@ pub fn get_taint_facts(conn: &Connection, entity_ids: &[String]) -> Result **Freshness caveat to verify:** `entities.content_hash` may be `NULL` because Clarion derives the hash lazily at read time (`query.rs:209-211`, via `file_content_hash`), not at write time. If the join returns `NULL` for `content_hash`, the read handler (T7) must derive it from the entity's source file the same way `resolve_file` does, so Wardline gets a real hash. **Check whether `entities.content_hash` is populated post-analyze** with `grep -n "content_hash" crates/clarion-storage/src/writer.rs`. If it is NOT persisted, T7's handler derives it via the existing `resolve_file`/`file_content_hash` path instead of relying on the join column, and `get_taint_facts` returns the stored fact while the handler supplies the live hash. Resolve this before writing T7's handler — it is the freshness contract's correctness point. +> **Freshness caveat to verify:** `entities.content_hash` may be `NULL` because Loomweave derives the hash lazily at read time (`query.rs:209-211`, via `file_content_hash`), not at write time. If the join returns `NULL` for `content_hash`, the read handler (T7) must derive it from the entity's source file the same way `resolve_file` does, so Wardline gets a real hash. **Check whether `entities.content_hash` is populated post-analyze** with `grep -n "content_hash" crates/loomweave-storage/src/writer.rs`. If it is NOT persisted, T7's handler derives it via the existing `resolve_file`/`file_content_hash` path instead of relying on the join column, and `get_taint_facts` returns the stored fact while the handler supplies the live hash. Resolve this before writing T7's handler — it is the freshness contract's correctness point. - [ ] **Step 6: Add upsert + fetch tests** @@ -517,15 +517,15 @@ Append to the test module: - [ ] **Step 7: Run all storage tests** -Run: `cargo nextest run -p clarion-storage wardline_taint` +Run: `cargo nextest run -p loomweave-storage wardline_taint` Expected: PASS (resolve + upsert + fetch). - [ ] **Step 8: Lint + commit** ```bash cargo fmt --all -- --check -cargo clippy -p clarion-storage --all-targets --all-features -- -D warnings -git add crates/clarion-storage/src/wardline_taint.rs crates/clarion-storage/src/lib.rs +cargo clippy -p loomweave-storage --all-targets --all-features -- -D warnings +git add crates/loomweave-storage/src/wardline_taint.rs crates/loomweave-storage/src/lib.rs git commit -m "feat(storage): wardline_taint module — resolve/upsert/fetch (W.1) Exact-tier qualname resolution via direct python:function: lookup. @@ -540,13 +540,13 @@ Co-Authored-By: Claude Opus 4.8 (1M context) " ## Task 3 (W.1c — `clarion-6cc50d7a1d`): Writer-actor command for taint upsert **Files:** -- Modify: `crates/clarion-storage/src/commands.rs` (new `WriterCmd` variant) -- Modify: `crates/clarion-storage/src/writer.rs` (actor-loop arm) -- Test: a writer integration test (follow the pattern of the existing `UpsertSummaryCache` tests; `grep -n "UpsertSummaryCache" crates/clarion-storage/src/writer.rs` and its test module) +- Modify: `crates/loomweave-storage/src/commands.rs` (new `WriterCmd` variant) +- Modify: `crates/loomweave-storage/src/writer.rs` (actor-loop arm) +- Test: a writer integration test (follow the pattern of the existing `UpsertSummaryCache` tests; `grep -n "UpsertSummaryCache" crates/loomweave-storage/src/writer.rs` and its test module) - [ ] **Step 1: Add the command variant** -In `crates/clarion-storage/src/commands.rs`, add to the `use` block: +In `crates/loomweave-storage/src/commands.rs`, add to the `use` block: ```rust use crate::wardline_taint::TaintFact; @@ -567,7 +567,7 @@ and append a variant to `enum WriterCmd` (after `TouchSummaryCache`): - [ ] **Step 2: Dispatch it in the actor loop** -In `crates/clarion-storage/src/writer.rs`, add an arm to the `match cmd` in `run_actor` (after the `TouchSummaryCache` arm), using the `query_time_write` path (taint writes are not run-scoped): +In `crates/loomweave-storage/src/writer.rs`, add an arm to the `match cmd` in `run_actor` (after the `TouchSummaryCache` arm), using the `query_time_write` path (taint writes are not run-scoped): ```rust WriterCmd::UpsertWardlineTaintFact { fact, ack } => { @@ -586,7 +586,7 @@ Add to the `writer.rs` test module (mirror the existing summary-cache writer tes #[tokio::test] async fn upsert_wardline_taint_fact_persists() { let dir = tempfile::tempdir().unwrap(); - let db_path = dir.path().join("clarion.db"); + let db_path = dir.path().join("loomweave.db"); // Apply migrations + seed one entity so the FK is satisfied. { let mut conn = rusqlite::Connection::open(&db_path).unwrap(); @@ -628,19 +628,19 @@ Add to the `writer.rs` test module (mirror the existing summary-cache writer tes } ``` -> Verify the exact `entities` INSERT column list against `0001_initial_schema.sql` (`grep -n "CREATE TABLE entities" -A40 crates/clarion-storage/migrations/0001_initial_schema.sql`) and adjust the seed INSERT to satisfy NOT NULL columns. Reuse the existing writer test's entity-seed helper if one exists. +> Verify the exact `entities` INSERT column list against `0001_initial_schema.sql` (`grep -n "CREATE TABLE entities" -A40 crates/loomweave-storage/migrations/0001_initial_schema.sql`) and adjust the seed INSERT to satisfy NOT NULL columns. Reuse the existing writer test's entity-seed helper if one exists. - [ ] **Step 4: Run it** -Run: `cargo nextest run -p clarion-storage upsert_wardline_taint_fact_persists` +Run: `cargo nextest run -p loomweave-storage upsert_wardline_taint_fact_persists` Expected: PASS. - [ ] **Step 5: Full storage gate + commit** ```bash -cargo clippy -p clarion-storage --all-targets --all-features -- -D warnings -cargo nextest run -p clarion-storage -git add crates/clarion-storage/src/commands.rs crates/clarion-storage/src/writer.rs +cargo clippy -p loomweave-storage --all-targets --all-features -- -D warnings +cargo nextest run -p loomweave-storage +git add crates/loomweave-storage/src/commands.rs crates/loomweave-storage/src/writer.rs git commit -m "feat(storage): WriterCmd::UpsertWardlineTaintFact (W.1) Query-time write path (query_time_write), entity_id pre-resolved by caller. @@ -652,12 +652,12 @@ Co-Authored-By: Claude Opus 4.8 (1M context) " ## Task 4 (W.4 — `clarion-2f9ad948e5`): Resolve endpoint `POST /api/wardline/resolve` -> **Filigree dependency fix:** W.4 currently depends only on W.0. It also depends on **W.1** (it calls `resolve_wardline_qualnames` from T2). Add the edge `W.4 → W.1` (`clarion-2f9ad948e5` depends on `clarion-6cc50d7a1d`) before/at execution. Also drop a one-line comment on B.2 (`clarion-ca2d26ffbe`): "Exact-tier qualname resolver shipped in W.1 (`clarion-storage::wardline_taint::resolve_wardline_qualnames`); B.2 extends it with the heuristic tier — do not reimplement." +> **Filigree dependency fix:** W.4 currently depends only on W.0. It also depends on **W.1** (it calls `resolve_wardline_qualnames` from T2). Add the edge `W.4 → W.1` (`clarion-2f9ad948e5` depends on `clarion-6cc50d7a1d`) before/at execution. Also drop a one-line comment on B.2 (`clarion-ca2d26ffbe`): "Exact-tier qualname resolver shipped in W.1 (`loomweave-storage::wardline_taint::resolve_wardline_qualnames`); B.2 extends it with the heuristic tier — do not reimplement." This task is sequenced **before** the write/read endpoints because they share the route-registration + larger-body-limit scaffolding it introduces, and W.2 reuses the resolver. (W.4's filigree issue can still close independently.) **Files:** -- Modify: `crates/clarion-cli/src/http_read.rs` (route, handler, request/response types, ErrorCode additions, body-limit constant, wardline sub-router) +- Modify: `crates/loomweave-cli/src/http_read.rs` (route, handler, request/response types, ErrorCode additions, body-limit constant, wardline sub-router) - [ ] **Step 1: Add the larger body limit + batch cap constants** @@ -720,7 +720,7 @@ async fn post_wardline_resolve( let readers = state.readers.clone(); let result = tokio::task::spawn_blocking(move || { let conn = readers.get()?; - clarion_storage::resolve_wardline_qualnames(&conn, &req.qualnames) + loomweave_storage::resolve_wardline_qualnames(&conn, &req.qualnames) }) .await; match result { @@ -751,7 +751,7 @@ async fn post_wardline_resolve( - [ ] **Step 4: Add `reject_project_mismatch` to AppState** -The `project` field is a **guard** (must match the served project), per Decision 6. Add to `impl AppState` (project name = the project root's file name, or an explicit configured project id — verify which Clarion uses; the served project is one DB under one root): +The `project` field is a **guard** (must match the served project), per Decision 6. Add to `impl AppState` (project name = the project root's file name, or an explicit configured project id — verify which Loomweave uses; the served project is one DB under one root): ```rust impl AppState { @@ -802,14 +802,14 @@ Refactor `fn router(state: AppState)` to add a second protected sub-router for t - [ ] **Step 6: Write an HTTP-level test** -Add an integration test (mirror the existing http_read tests — `grep -n "#\[tokio::test\]\|fn router\|spawn_with_env\|reqwest\|TestServer\|oneshot" crates/clarion-cli/src/http_read.rs` to find the in-process test harness). The test builds a `router(state)` over a temp DB seeded with `python:function:a.b.c`, POSTs `{"qualnames":["a.b.c","x.y.z"]}` with a valid HMAC header, and asserts `resolved == {"a.b.c": "python:function:a.b.c"}` and `unresolved == ["x.y.z"]`. If the file has no in-process HTTP harness, add the test under `crates/clarion-cli/tests/` as an integration test that boots `spawn_with_env` on `127.0.0.1:0` and uses a stdlib/`reqwest` client (check `Cargo.toml` dev-deps for an HTTP client; `wp2_e2e` tests show the established pattern). +Add an integration test (mirror the existing http_read tests — `grep -n "#\[tokio::test\]\|fn router\|spawn_with_env\|reqwest\|TestServer\|oneshot" crates/loomweave-cli/src/http_read.rs` to find the in-process test harness). The test builds a `router(state)` over a temp DB seeded with `python:function:a.b.c`, POSTs `{"qualnames":["a.b.c","x.y.z"]}` with a valid HMAC header, and asserts `resolved == {"a.b.c": "python:function:a.b.c"}` and `unresolved == ["x.y.z"]`. If the file has no in-process HTTP harness, add the test under `crates/loomweave-cli/tests/` as an integration test that boots `spawn_with_env` on `127.0.0.1:0` and uses a stdlib/`reqwest` client (check `Cargo.toml` dev-deps for an HTTP client; `wp2_e2e` tests show the established pattern). - [ ] **Step 7: Run + gate + commit** ```bash -cargo nextest run -p clarion-cli wardline_resolve -cargo clippy -p clarion-cli --all-targets --all-features -- -D warnings -git add crates/clarion-cli/src/http_read.rs +cargo nextest run -p loomweave-cli wardline_resolve +cargo clippy -p loomweave-cli --all-targets --all-features -- -D warnings +git add crates/loomweave-cli/src/http_read.rs git commit -m "feat(serve): POST /api/wardline/resolve — exact-tier qualname resolve (W.4) Co-Authored-By: Claude Opus 4.8 (1M context) " @@ -820,13 +820,13 @@ Co-Authored-By: Claude Opus 4.8 (1M context) " ## Task 5 (W.2a — `clarion-96e80a907c`): Config knob + optional writer-actor on `serve` **Files:** -- Modify: `crates/clarion-mcp/src/config.rs` (`HttpReadConfig.wardline_taint_write`) -- Modify: `crates/clarion-cli/src/serve.rs` (pass the knob into spawn) -- Modify: `crates/clarion-cli/src/http_read.rs` (spawn an optional `Writer` inside the HTTP runtime; store its sender in `AppState`) +- Modify: `crates/loomweave-mcp/src/config.rs` (`HttpReadConfig.wardline_taint_write`) +- Modify: `crates/loomweave-cli/src/serve.rs` (pass the knob into spawn) +- Modify: `crates/loomweave-cli/src/http_read.rs` (spawn an optional `Writer` inside the HTTP runtime; store its sender in `AppState`) - [ ] **Step 1: Add the config field (failing config test)** -In `crates/clarion-mcp/src/config.rs`, add to `HttpReadConfig`: +In `crates/loomweave-mcp/src/config.rs`, add to `HttpReadConfig`: ```rust /// Enable the Wardline taint-store WRITE API (POST /api/wardline/taint-facts). @@ -838,7 +838,7 @@ In `crates/clarion-mcp/src/config.rs`, add to `HttpReadConfig`: and `wardline_taint_write: false` in the `Default` impl. Add/extend a config-parse test asserting the default is `false` and that `wardline_taint_write: true` in YAML parses. -Run: `cargo nextest run -p clarion-mcp config` +Run: `cargo nextest run -p loomweave-mcp config` Expected: PASS. - [ ] **Step 2: Thread the knob + an optional writer sender into `AppState`** @@ -848,7 +848,7 @@ Add to `struct AppState`: ```rust /// Present only when serve.http.wardline_taint_write is true (ADR-036). /// `None` ⇒ the write API is disabled and returns 403 WRITE_DISABLED. - taint_writer: Option>, + taint_writer: Option>, ``` - [ ] **Step 3: Spawn the optional writer inside the HTTP runtime** @@ -857,10 +857,10 @@ In `run_http_read_server`, after building the runtime and before constructing `A ```rust let (taint_writer, taint_writer_join) = if wardline_taint_write { - let (writer, join) = clarion_storage::Writer::spawn( + let (writer, join) = loomweave_storage::Writer::spawn( db_path.clone(), - clarion_storage::DEFAULT_BATCH_SIZE, - clarion_storage::DEFAULT_CHANNEL_CAPACITY, + loomweave_storage::DEFAULT_BATCH_SIZE, + loomweave_storage::DEFAULT_CHANNEL_CAPACITY, ) .map_err(|err| anyhow!("spawn taint writer-actor: {err}"))?; (Some(writer.sender()), Some(join)) @@ -873,7 +873,7 @@ In `run_http_read_server`, after building the runtime and before constructing `A - [ ] **Step 4: Pass the knob from `serve.rs`** -In `crates/clarion-cli/src/serve.rs`, the `http_read::spawn(...)` call gains the project's `db_path` (already computed at the top of `run`) — pass it, and the spawn reads `wardline_taint_write` from `&config.serve.http`. Update the `spawn` signature/call accordingly. Confirm the `Arc::ptr_eq` reader-identity assert still holds (the writer uses a *separate* connection, unrelated to the reader pool identity check). +In `crates/loomweave-cli/src/serve.rs`, the `http_read::spawn(...)` call gains the project's `db_path` (already computed at the top of `run`) — pass it, and the spawn reads `wardline_taint_write` from `&config.serve.http`. Update the `spawn` signature/call accordingly. Confirm the `Arc::ptr_eq` reader-identity assert still holds (the writer uses a *separate* connection, unrelated to the reader pool identity check). - [ ] **Step 5: Build the workspace** @@ -883,7 +883,7 @@ Expected: compiles. (No behavior test yet — exercised by T6.) - [ ] **Step 6: Commit** ```bash -git add crates/clarion-mcp/src/config.rs crates/clarion-cli/src/serve.rs crates/clarion-cli/src/http_read.rs +git add crates/loomweave-mcp/src/config.rs crates/loomweave-cli/src/serve.rs crates/loomweave-cli/src/http_read.rs git commit -m "feat(serve): optional writer-actor + wardline_taint_write config (W.2) Co-Authored-By: Claude Opus 4.8 (1M context) " @@ -894,7 +894,7 @@ Co-Authored-By: Claude Opus 4.8 (1M context) " ## Task 6 (W.2b — `clarion-96e80a907c`): Write endpoint `POST /api/wardline/taint-facts` **Files:** -- Modify: `crates/clarion-cli/src/http_read.rs` +- Modify: `crates/loomweave-cli/src/http_read.rs` - [ ] **Step 1: Request/response types** @@ -960,7 +960,7 @@ async fn post_wardline_taint_facts( let readers = state.readers.clone(); let resolved = match tokio::task::spawn_blocking(move || { let conn = readers.get()?; - clarion_storage::resolve_wardline_qualnames(&conn, &qualnames) + loomweave_storage::resolve_wardline_qualnames(&conn, &qualnames) }) .await { @@ -969,7 +969,7 @@ async fn post_wardline_taint_facts( Err(_) => return json_error(StatusCode::INTERNAL_SERVER_ERROR, ErrorCode::Internal, "resolve task panicked"), }; - let now = clarion_storage::now_iso8601(); // verify the project's ISO-8601 helper name/path + let now = loomweave_storage::now_iso8601(); // verify the project's ISO-8601 helper name/path let mut written = 0usize; let mut unresolved = Vec::new(); for (fact_input, (_, resolution)) in req.facts.iter().zip(resolved) { @@ -977,7 +977,7 @@ async fn post_wardline_taint_facts( unresolved.push(fact_input.qualname.clone()); continue; }; - let fact = clarion_storage::TaintFact { + let fact = loomweave_storage::TaintFact { entity_id, wardline_json: fact_input.wardline_json.to_string(), scan_id: fact_input.scan_id.clone().or_else(|| req.scan_id.clone()), @@ -986,7 +986,7 @@ async fn post_wardline_taint_facts( }; let (ack_tx, ack_rx) = tokio::sync::oneshot::channel(); if writer - .send(clarion_storage::WriterCmd::UpsertWardlineTaintFact { fact: Box::new(fact), ack: ack_tx }) + .send(loomweave_storage::WriterCmd::UpsertWardlineTaintFact { fact: Box::new(fact), ack: ack_tx }) .await .is_err() { @@ -1002,7 +1002,7 @@ async fn post_wardline_taint_facts( } ``` -> Verify: (a) the ISO-8601 timestamp helper — search `grep -rn "strftime\|iso8601\|Utc::now\|fn now" crates/clarion-storage/src crates/clarion-cli/src`; the writer-actor elsewhere uses `strftime('%Y-%m-%dT%H:%M:%fZ','now')` in SQL or a caller-supplied timestamp. Supply the timestamp caller-side (handlers run on the HTTP runtime where `chrono`/`time` may already be a dep — check `Cargo.toml`). If no helper exists, format with the same pattern the analyze path uses. (b) `WriterCmd`/`TaintFact` are re-exported from `clarion_storage` (added in T2/T3 — confirm the `pub use` includes `WriterCmd`; it is already public via `commands`, but check the crate root re-export and add it if missing). +> Verify: (a) the ISO-8601 timestamp helper — search `grep -rn "strftime\|iso8601\|Utc::now\|fn now" crates/loomweave-storage/src crates/loomweave-cli/src`; the writer-actor elsewhere uses `strftime('%Y-%m-%dT%H:%M:%fZ','now')` in SQL or a caller-supplied timestamp. Supply the timestamp caller-side (handlers run on the HTTP runtime where `chrono`/`time` may already be a dep — check `Cargo.toml`). If no helper exists, format with the same pattern the analyze path uses. (b) `WriterCmd`/`TaintFact` are re-exported from `loomweave_storage` (added in T2/T3 — confirm the `pub use` includes `WriterCmd`; it is already public via `commands`, but check the crate root re-export and add it if missing). - [ ] **Step 3: Register the route** on the `wardline` sub-router (T4 Step 5): @@ -1020,9 +1020,9 @@ async fn post_wardline_taint_facts( - [ ] **Step 5: Run + gate + commit** ```bash -cargo nextest run -p clarion-cli wardline_taint -cargo clippy -p clarion-cli --all-targets --all-features -- -D warnings -git add crates/clarion-cli/src/http_read.rs +cargo nextest run -p loomweave-cli wardline_taint +cargo clippy -p loomweave-cli --all-targets --all-features -- -D warnings +git add crates/loomweave-cli/src/http_read.rs git commit -m "feat(serve): POST /api/wardline/taint-facts — exact-only batch write (W.2) Co-Authored-By: Claude Opus 4.8 (1M context) " @@ -1033,7 +1033,7 @@ Co-Authored-By: Claude Opus 4.8 (1M context) " ## Task 7 (W.3 — `clarion-1c1a17f0f0`): Read endpoints — single + batch-get **Files:** -- Modify: `crates/clarion-cli/src/http_read.rs` +- Modify: `crates/loomweave-cli/src/http_read.rs` - [ ] **Step 1: Resolve the freshness/`current_content_hash` source** (the open item flagged in T2 Step 5). If `entities.content_hash` is populated post-analyze, `get_taint_facts`' join is sufficient. If it is `NULL` (lazy derivation), the handler must derive the live hash per entity via the existing `resolve_file`/`file_content_hash` path before responding. Confirm now; the handler below assumes `get_taint_facts` returns the hash and adds a derivation fallback when it is `None`. @@ -1114,13 +1114,13 @@ async fn respond_taint_facts( ) -> std::result::Result, Response> { let readers = state.readers.clone(); let qn = qualnames.clone(); - let fetched = tokio::task::spawn_blocking(move || -> clarion_storage::Result)>> { + let fetched = tokio::task::spawn_blocking(move || -> loomweave_storage::Result)>> { let conn = readers.get()?; - let resolved = clarion_storage::resolve_wardline_qualnames(&conn, &qn)?; + let resolved = loomweave_storage::resolve_wardline_qualnames(&conn, &qn)?; let resolved_ids: Vec = resolved.iter().filter_map(|(_, r)| r.entity_id.clone()).collect(); - let rows = clarion_storage::get_taint_facts(&conn, &resolved_ids)?; + let rows = loomweave_storage::get_taint_facts(&conn, &resolved_ids)?; // zip rows back to qualnames by entity id - let by_id: std::collections::HashMap = + let by_id: std::collections::HashMap = rows.into_iter().map(|r| (r.entity_id.clone(), r)).collect(); Ok(resolved .into_iter() @@ -1173,9 +1173,9 @@ async fn respond_taint_facts( - [ ] **Step 6: Run + gate + commit** ```bash -cargo nextest run -p clarion-cli wardline_taint -cargo clippy -p clarion-cli --all-targets --all-features -- -D warnings -git add crates/clarion-cli/src/http_read.rs +cargo nextest run -p loomweave-cli wardline_taint +cargo clippy -p loomweave-cli --all-targets --all-features -- -D warnings +git add crates/loomweave-cli/src/http_read.rs git commit -m "feat(serve): GET + :batch-get /api/wardline/taint-facts (W.3) Co-Authored-By: Claude Opus 4.8 (1M context) " @@ -1211,12 +1211,12 @@ Expected: all green. Fix anything red before proceeding. - Optionally create: `docs/federation/fixtures/post-api-wardline-taint-facts.json` etc. (mirror the existing fixture style) - [ ] **Step 1: Add a "Wardline taint-fact store (SP9)" section** to `contracts.md` pinning, verbatim against the implemented handlers: - - **Routes:** `POST /api/wardline/resolve`, `POST /api/wardline/taint-facts` (write), `GET /api/wardline/taint-facts?project=&qualname=`, `POST /api/wardline/taint-facts:batch-get`. All HMAC-gated (`X-Loom-Component: clarion:`, ADR-034). + - **Routes:** `POST /api/wardline/resolve`, `POST /api/wardline/taint-facts` (write), `GET /api/wardline/taint-facts?project=&qualname=`, `POST /api/wardline/taint-facts:batch-get`. All HMAC-gated (`X-Weft-Component: loomweave:`, ADR-034). - **Qualname keying:** pre-composed dotted `module.qualified_name` (byte-faithful to `module_dotted_name()` + `__qualname__`); conformance is the `wardline-qualname-normalization.json` fixture. Resolution is exact-tier; unresolved qualnames are returned, never guessed. Writes require `exact`. - **Body/batch limits:** `WARDLINE_BODY_LIMIT_BYTES` (4 MiB) and `WARDLINE_TAINT_BATCH_MAX` (2000 facts/qualnames per request); Wardline chunks client-side. State the exact numbers. - - **Freshness contract:** fetch returns `current_content_hash` (blake3 of the containing file's raw bytes, hex, whole-file — **not** sha256/LF-normalized); Wardline compares it to the `content_hash_at_compute` it stamped inside `wardline_json`; match → fresh, mismatch/`exists:false` → recompute. Clarion never asserts freshness. + - **Freshness contract:** fetch returns `current_content_hash` (blake3 of the containing file's raw bytes, hex, whole-file — **not** sha256/LF-normalized); Wardline compares it to the `content_hash_at_compute` it stamped inside `wardline_json`; match → fresh, mismatch/`exists:false` → recompute. Loomweave never asserts freshness. - **`project` guard:** the handle is the project-root dir name (or whatever T4 Step 4 settled); a non-empty mismatch → 403 `PROJECT_MISMATCH`; empty is accepted. - - **Opacity:** `wardline_json` stored/returned verbatim; `scan_id`/`content_hash_at_compute` sent top-level become queryable columns; Clarion never parses the blob. + - **Opacity:** `wardline_json` stored/returned verbatim; `scan_id`/`content_hash_at_compute` sent top-level become queryable columns; Loomweave never parses the blob. - **Write-disabled posture:** writes → 403 `WRITE_DISABLED` unless `serve.http.wardline_taint_write: true`; reads always available. - **Error codes:** enumerate the new `ErrorCode`s (`WRITE_DISABLED`, `PROJECT_MISMATCH`) alongside the existing list so federation clients route on `code`. diff --git a/docs/superpowers/plans/2026-06-02-clarion-integrated-delivery-plan.md b/docs/superpowers/plans/2026-06-02-loomweave-integrated-delivery-plan.md similarity index 85% rename from docs/superpowers/plans/2026-06-02-clarion-integrated-delivery-plan.md rename to docs/superpowers/plans/2026-06-02-loomweave-integrated-delivery-plan.md index d25d8db3..c1109e7f 100644 --- a/docs/superpowers/plans/2026-06-02-clarion-integrated-delivery-plan.md +++ b/docs/superpowers/plans/2026-06-02-loomweave-integrated-delivery-plan.md @@ -1,4 +1,4 @@ -# Clarion — Integrated Delivery Plan +# Loomweave — Integrated Delivery Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:subagent-driven-development` > (recommended) or `superpowers:executing-plans` to implement this plan task-by-task. @@ -7,21 +7,21 @@ **Date:** 2026-06-02 **Status:** Authoritative delivery plan **Inputs:** -- `docs/superpowers/specs/2026-06-01-clarion-roadmap-to-first-class.md` — the final-form target -- `/home/john/wardline/docs/superpowers/specs/2026-06-02-clarion-priority-brief.md` — the suite-unlocking priority stack -- `/home/john/wardline/docs/superpowers/specs/2026-06-01-loom-stable-entity-identity-conformance.md` — SEI spec (canonical) +- `docs/superpowers/specs/2026-06-01-loomweave-roadmap-to-first-class.md` — the final-form target +- `/home/john/wardline/docs/superpowers/specs/2026-06-02-loomweave-priority-brief.md` — the suite-unlocking priority stack +- `/home/john/wardline/docs/superpowers/specs/2026-06-01-weft-stable-entity-identity-conformance.md` — SEI spec (canonical) **Goal:** Deliver the priority-brief's three-phase critical path (HTTP linkages → SEI authority → core paradise / dossier) **and** integrate as much standalone-quality work (MCP catalogue completion, guidance maturity, incremental analysis) as sequencing allows. The suite moves as -fast as Clarion executes; every P0 item is autonomous and starts today. +fast as Loomweave executes; every P0 item is autonomous and starts today. --- ## Decisions baked in (REQ-C-01 and REQ-C-02 — resolved here) These are the two decisions the priority brief identifies as the last thing between "all four -subsystems reported" and SEI lock. They are Clarion's to make. They are made here. +subsystems reported" and SEI lock. They are Loomweave's to make. They are made here. ### REQ-C-01 — Signature schema @@ -53,13 +53,13 @@ column is **plain `TEXT`** — signatures are not unique and carry no `UNIQUE` c ### REQ-C-02 — SEI token scheme -**Decision: `clarion:eid:`**, +**Decision: `loomweave:eid:`**, where `mint_run_id` is the UUID of the run in which the SEI is *minted* (not carried). > **Correcting the framing (peer review, 2026-06-02).** An earlier draft keyed the token on > `first_seen_commit` to be a pure function of the entity, preserving byte-identical-run > determinism. That was wrong on two counts. (1) **`first_seen_commit` is never populated** — -> `crates/clarion-cli/src/analyze.rs` writes `first_seen_commit: None` on every entity; it is a +> `crates/loomweave-cli/src/analyze.rs` writes `first_seen_commit: None` on every entity; it is a > schema column the pipeline does not fill. A token keyed on it degenerates to `blake3(locator)`, > which is exactly the collision-on-reuse flaw the priority brief warned against. (2) The > pure-function frame is the wrong model: **SEI allocation is inherently stateful** — the matcher @@ -77,7 +77,7 @@ Properties this satisfies: - **Unique within a run**: locators are unique per run, so `blake3(locator ++ run_id)` cannot collide between two entities of the same run. - **No time/RNG component**: `run_id` is an already-allocated per-run UUID (no ad-hoc RNG, which - Clarion's determinism posture forbids); the token is not time-ordered, so the §8 oracle need not + Loomweave's determinism posture forbids); the token is not time-ordered, so the §8 oracle need not assume ordering. - **Reproducible-given-state**: re-deriving a carried SEI is never required — it is read back from `sei_bindings`. The token construction only runs at mint time. @@ -96,7 +96,7 @@ Phase 1 (P0 — autonomous, start now) ▼ (SEI lock) -Phase 2 (P1 — after lock; Clarion-autonomous) +Phase 2 (P1 — after lock; Loomweave-autonomous) Migration 0005 (sei_bindings + sei_lineage + entities.signature) SEI minting + deterministic matcher + lineage (identity lives in sei_bindings, NOT in an entities column — entities is cumulative/never-deleted, see §"SEI @@ -120,7 +120,7 @@ Parallel track (run alongside Phase 2 as capacity allows) ## SEI persistence model (peer-review correction, 2026-06-02) -> **Why identity is NOT a column on `entities`.** Ground truth: `crates/clarion-storage/src/writer.rs` +> **Why identity is NOT a column on `entities`.** Ground truth: `crates/loomweave-storage/src/writer.rs` > upserts entities with `INSERT ... ON CONFLICT(id) DO UPDATE` and there is **no `DELETE FROM > entities`** on re-index anywhere in the pipeline — `entities` is a **cumulative, never-pruned** > table; vanished and renamed entities' rows persist forever. An earlier draft put `sei TEXT UNIQUE` @@ -169,14 +169,14 @@ out explicitly as test-first. | File | Responsibility | Tasks | |---|---|---| -| `docs/clarion/adr/ADR-038-sei-token-and-signature.md` | Records REQ-C-01 + REQ-C-02 decisions as Accepted ADRs | T1.0 | -| `crates/clarion-storage/migrations/0004_sei_prior_index.sql` | Prior-index side table DDL | T1.1 | -| `crates/clarion-storage/src/schema.rs` | Register migration 0004, bump `CURRENT_SCHEMA_VERSION` to 4 | T1.1 | -| `crates/clarion-storage/src/prior_index.rs` | Upsert + read the last-run `locator → body_hash + signature` snapshot (no SEI column — shape-independent) | T1.2 | -| `crates/clarion-storage/src/commands.rs` | `WriterCmd::UpsertPriorIndex` variant | T1.3 | -| `crates/clarion-storage/src/writer.rs` | Actor dispatch arm for prior-index writes | T1.3 | -| `crates/clarion-cli/src/analyze.rs` | Flush prior-index snapshot at end of each successful run | T1.4 | -| `crates/clarion-cli/src/http_read.rs` | `GET /api/v1/entities/{id}/callers`, `.../callees`, batch POST variants; `_capabilities` linkages flag | T1.5, T1.6 | +| `docs/loomweave/adr/ADR-038-sei-token-and-signature.md` | Records REQ-C-01 + REQ-C-02 decisions as Accepted ADRs | T1.0 | +| `crates/loomweave-storage/migrations/0004_sei_prior_index.sql` | Prior-index side table DDL | T1.1 | +| `crates/loomweave-storage/src/schema.rs` | Register migration 0004, bump `CURRENT_SCHEMA_VERSION` to 4 | T1.1 | +| `crates/loomweave-storage/src/prior_index.rs` | Upsert + read the last-run `locator → body_hash + signature` snapshot (no SEI column — shape-independent) | T1.2 | +| `crates/loomweave-storage/src/commands.rs` | `WriterCmd::UpsertPriorIndex` variant | T1.3 | +| `crates/loomweave-storage/src/writer.rs` | Actor dispatch arm for prior-index writes | T1.3 | +| `crates/loomweave-cli/src/analyze.rs` | Flush prior-index snapshot at end of each successful run | T1.4 | +| `crates/loomweave-cli/src/http_read.rs` | `GET /api/v1/entities/{id}/callers`, `.../callees`, batch POST variants; `_capabilities` linkages flag | T1.5, T1.6 | | `docs/federation/contracts.md` | Pin new linkage routes + linkages capability flag | T1.7 | --- @@ -186,8 +186,8 @@ out explicitly as test-first. **Doc task, no TDD.** Write it complete and correct the first time; ADRs are immutable once Accepted. -> **Status: complete.** `docs/clarion/adr/ADR-038-sei-token-and-signature.md` is authored and -> Accepted; the ADR index (`docs/clarion/adr/README.md`) and the Loom glossary +> **Status: complete.** `docs/loomweave/adr/ADR-038-sei-token-and-signature.md` is authored and +> Accepted; the ADR index (`docs/loomweave/adr/README.md`) and the Weft glossary > (`docs/suite/glossary.md`: `SEI` + `locator`, verdict `no clash`) are updated. ADR-038 is > numbered 038 because **ADR-037 was already taken** (shared error vocabulary). The remaining > Phase-1 tasks (T1.1–T1.7) and all of Phase 2/3 are still open. The checklist below records what @@ -196,10 +196,10 @@ Accepted. - [x] **Step 1: Write ADR-038.** Follow the repo ADR format (see ADR-035 for header shape). Must carry: - **Status:** Accepted. **Date:** 2026-06-02. - - **Context:** SEI spec §0.3 requires Clarion to report its REQ-C-01 (signature schema) + - **Context:** SEI spec §0.3 requires Loomweave to report its REQ-C-01 (signature schema) and REQ-C-02 (token scheme) decisions before lock. These are the last open items in §0.5. - - **Decision (token):** `clarion:eid:`, where `mint_run_id` is the UUID of the run that *mints* the SEI — 128 bits of identity space, no time/RNG component. The SEI is stored in `sei_bindings` (migration 0005). The oracle tests opacity and behaviour, not the token's @@ -226,7 +226,7 @@ Accepted. (`ON CONFLICT(id) DO UPDATE`, no `DELETE`), so a `UNIQUE` SEI column would be violated the moment a rename carries an SEI while the stale row still holds it. Orphaning is a `status` flip on the binding. Record this rationale in the ADR. - - **Reserved namespace:** the `clarion:eid:` prefix is **reserved** — no plugin locator may + - **Reserved namespace:** the `loomweave:eid:` prefix is **reserved** — no plugin locator may occupy it. This is what lets `resolve(locator)` reject an SEI-shaped input (REQ-F-02); the ADR states the reservation. - **Consequences:** migration 0004 adds `sei_prior_index` (no SEI column); migration 0005 adds @@ -236,9 +236,9 @@ Accepted. - Reference: SEI spec §1–§3, §0.5, REQ-C-01, REQ-C-02; supersedes the REQ-C-01/02 reasoning sketched in the roadmap Appendix A. -- [x] **Step 2: Register in ADR index.** Add ADR-038 row to `docs/clarion/adr/README.md`. +- [x] **Step 2: Register in ADR index.** Add ADR-038 row to `docs/loomweave/adr/README.md`. -- [x] **Step 3: Loom vocabulary verdict.** `SEI` and `locator` are cross-product-visible; the +- [x] **Step 3: Weft vocabulary verdict.** `SEI` and `locator` are cross-product-visible; the ADR-acceptance rule requires a glossary verdict before Accepted. Both added to `docs/suite/glossary.md` with verdict `no clash` (new suite-wide terms, single meaning across all four subsystems), ADR-038 as authority. @@ -248,8 +248,8 @@ Accepted. ### T1.1 — Migration 0004: `sei_prior_index` table **Files:** -- Create: `crates/clarion-storage/migrations/0004_sei_prior_index.sql` -- Edit: `crates/clarion-storage/src/schema.rs` +- Create: `crates/loomweave-storage/migrations/0004_sei_prior_index.sql` +- Edit: `crates/loomweave-storage/src/schema.rs` - [ ] **Step 1: Write migration SQL.** @@ -262,7 +262,7 @@ Accepted. -- move/rename cases. SHAPE-INDEPENDENT: no SEI column, so this is safe to ship -- before SEI lock. The SEI itself lives in `sei_bindings` (migration 0005), -- which is the identity source of truth; the matcher reads SEIs from there. --- Rebuilt each run; cleared by `clarion install --force` (full .clarion/ wipe). +-- Rebuilt each run; cleared by `loomweave install --force` (full .loomweave/ wipe). -- Not part of the main entity graph; does not FK into entities. BEGIN; @@ -288,14 +288,14 @@ COMMIT; ### T1.2 — `prior_index.rs`: storage helpers **Files:** -- Create: `crates/clarion-storage/src/prior_index.rs` -- Edit: `crates/clarion-storage/src/lib.rs` — re-export public items +- Create: `crates/loomweave-storage/src/prior_index.rs` +- Edit: `crates/loomweave-storage/src/lib.rs` — re-export public items - [ ] **Step 1: Write `prior_index.rs`.** Implement: - `pub struct PriorIndexEntry { pub locator: String, pub body_hash: String, pub signature: Option }` (no SEI — identity is in `sei_bindings`) - `pub fn upsert_prior_index_entry(conn: &Connection, entry: &PriorIndexEntry) -> Result<()>` — INSERT OR REPLACE. - `pub fn load_prior_index(conn: &Connection) -> Result>` — full table load; called once at start of re-index for the incremental-analysis body_hash compare and (Phase 2) as a matcher input. - - `pub fn clear_prior_index(conn: &Connection) -> Result<()>` — DELETE FROM; called by `--force` path (if .clarion/ is wiped, this never runs — but it should exist for explicit reset). + - `pub fn clear_prior_index(conn: &Connection) -> Result<()>` — DELETE FROM; called by `--force` path (if .loomweave/ is wiped, this never runs — but it should exist for explicit reset). - [ ] **Step 2: Re-export from `lib.rs`.** @@ -308,8 +308,8 @@ COMMIT; ### T1.3 — WriterCmd: UpsertPriorIndex **Files:** -- Edit: `crates/clarion-storage/src/commands.rs` -- Edit: `crates/clarion-storage/src/writer.rs` +- Edit: `crates/loomweave-storage/src/commands.rs` +- Edit: `crates/loomweave-storage/src/writer.rs` - [ ] **Step 1: Add `WriterCmd::UpsertPriorIndex(PriorIndexEntry)` variant** to `commands.rs`, following the pattern of existing variants (e.g. `UpsertWardlineTaintFact`). @@ -323,7 +323,7 @@ COMMIT; ### T1.4 — Analysis pipeline: flush prior index after each run **Files:** -- Edit: `crates/clarion-cli/src/analyze.rs` +- Edit: `crates/loomweave-cli/src/analyze.rs` The prior index is written at successful run completion (Phase 8 / post-emission). It replaces the previous run's snapshot atomically: we DELETE all rows and re-insert the @@ -348,10 +348,10 @@ successful run." ### T1.5 — HTTP linkages: callers and callees **Files:** -- Edit: `crates/clarion-cli/src/http_read.rs` +- Edit: `crates/loomweave-cli/src/http_read.rs` The storage layer already provides `call_edges_targeting` (callers) and `call_edges_from` -(callees) in `clarion-storage/src/query.rs`. These need HTTP wrappers with pagination and +(callees) in `loomweave-storage/src/query.rs`. These need HTTP wrappers with pagination and confidence-tier filtering. - [ ] **Step 1: Add `LinkageEntry` response struct** (serializable): @@ -396,7 +396,7 @@ confidence-tier filtering. ### T1.6 — `_capabilities`: add linkages flag **Files:** -- Edit: `crates/clarion-cli/src/http_read.rs` +- Edit: `crates/loomweave-cli/src/http_read.rs` - [ ] **Step 1: Add `linkages: LinkagesCapability` to `CapabilitiesResponse`**: ```rust @@ -423,20 +423,20 @@ confidence-tier filtering. ## Phase 2 — P1 SEI Authority -*Gated on SEI lock (which Phase 1's decisions unblock). Clarion-autonomous once locked.* +*Gated on SEI lock (which Phase 1's decisions unblock). Loomweave-autonomous once locked.* ### File map | File | Responsibility | Tasks | |---|---|---| -| `crates/clarion-storage/migrations/0005_sei.sql` | `sei_bindings` + `sei_lineage` tables; plain `entities.signature` (no `entities.sei`) | T2.0 | -| `crates/clarion-storage/src/schema.rs` | Register 0005, bump to 5 | T2.0 | -| `crates/clarion-storage/src/sei.rs` | Minting, matcher, binding + lineage helpers | T2.1 | -| `crates/clarion-storage/src/commands.rs` | `WriterCmd::UpsertSeiBinding`, `OrphanSeiBinding`, `SetEntitySignature`, `AppendSeiLineage` | T2.2 | -| `crates/clarion-storage/src/writer.rs` | Dispatch arms | T2.2 | -| `crates/clarion-cli/src/analyze.rs` | SEI mint pass (post-extraction); matcher on re-index | T2.3 | -| `crates/clarion-cli/src/http_read.rs` | `resolve`, `resolve_sei`, `lineage`; `_capabilities` sei flag | T2.4 | -| `crates/clarion-mcp/src/lib.rs` + tool handlers | Return `sei` alongside `entity_id` (read-time join to `sei_bindings`) | T2.5 | +| `crates/loomweave-storage/migrations/0005_sei.sql` | `sei_bindings` + `sei_lineage` tables; plain `entities.signature` (no `entities.sei`) | T2.0 | +| `crates/loomweave-storage/src/schema.rs` | Register 0005, bump to 5 | T2.0 | +| `crates/loomweave-storage/src/sei.rs` | Minting, matcher, binding + lineage helpers | T2.1 | +| `crates/loomweave-storage/src/commands.rs` | `WriterCmd::UpsertSeiBinding`, `OrphanSeiBinding`, `SetEntitySignature`, `AppendSeiLineage` | T2.2 | +| `crates/loomweave-storage/src/writer.rs` | Dispatch arms | T2.2 | +| `crates/loomweave-cli/src/analyze.rs` | SEI mint pass (post-extraction); matcher on re-index | T2.3 | +| `crates/loomweave-cli/src/http_read.rs` | `resolve`, `resolve_sei`, `lineage`; `_capabilities` sei flag | T2.4 | +| `crates/loomweave-mcp/src/lib.rs` + tool handlers | Return `sei` alongside `entity_id` (read-time join to `sei_bindings`) | T2.5 | | `docs/federation/contracts.md` | Pin SEI routes + capability contract | T2.6 | --- @@ -444,8 +444,8 @@ confidence-tier filtering. ### T2.0 — Migration 0005: `sei_bindings`, `sei_lineage`, `entities.signature` **Files:** -- Create: `crates/clarion-storage/migrations/0005_sei.sql` -- Edit: `crates/clarion-storage/src/schema.rs` +- Create: `crates/loomweave-storage/migrations/0005_sei.sql` +- Edit: `crates/loomweave-storage/src/schema.rs` - [ ] **Step 1: Write migration SQL.** Note: **no `entities.sei` column** — identity lives in `sei_bindings` because `entities` is cumulative/never-pruned (see §"SEI persistence model"). @@ -465,7 +465,7 @@ BEGIN; ALTER TABLE entities ADD COLUMN signature TEXT; CREATE TABLE sei_bindings ( - sei TEXT PRIMARY KEY, -- clarion:eid: (opaque) + sei TEXT PRIMARY KEY, -- loomweave:eid: (opaque) current_locator TEXT, -- current address; the alive binding's entity id body_hash TEXT, -- content_hash at last (re)bind signature TEXT, -- signature at last (re)bind @@ -507,16 +507,16 @@ COMMIT; ### T2.1 — `sei.rs`: minting and matching **Files:** -- Create: `crates/clarion-storage/src/sei.rs` -- Edit: `crates/clarion-storage/src/lib.rs` +- Create: `crates/loomweave-storage/src/sei.rs` +- Edit: `crates/loomweave-storage/src/lib.rs` **Test-first task.** Identity is correctness-critical; write each test RED before implementing. - [ ] **Step 1 (RED): write the `mint_sei` test**, then implement. - `pub fn mint_sei(locator: &str, mint_run_id: &str) -> String` — REQ-C-02: `clarion:eid:` + + `pub fn mint_sei(locator: &str, mint_run_id: &str) -> String` — REQ-C-02: `loomweave:eid:` + lowercase hex of `blake3(locator ++ 0x00 ++ mint_run_id)` truncated to 32 hex chars (128 bits). Tests: same `(locator, run_id)` → same token; different `run_id` for the same locator → different - token (the collision-on-reuse guard); output always carries the reserved `clarion:eid:` prefix. + token (the collision-on-reuse guard); output always carries the reserved `loomweave:eid:` prefix. - [ ] **Step 2: Write the binding-state helpers** in `sei.rs` (the matcher reads/writes these, not an `entities` column): @@ -530,7 +530,7 @@ COMMIT; ```rust enum SeiDecision { Carry { sei: String, event: Option }, // locator present, or rename/move match - Mint { sei: String }, // new entity (clarion:eid minted) + Mint { sei: String }, // new entity (loomweave:eid minted) } // Orphaning is computed SEPARATELY (Step 5) by diffing the alive set against the // current run's locator set — it is a property of vanished bindings, not of a new entity. @@ -564,8 +564,8 @@ COMMIT; ### T2.2 — WriterCmd: SEI writes **Files:** -- Edit: `crates/clarion-storage/src/commands.rs` -- Edit: `crates/clarion-storage/src/writer.rs` +- Edit: `crates/loomweave-storage/src/commands.rs` +- Edit: `crates/loomweave-storage/src/writer.rs` - [ ] **Step 1: Add `WriterCmd::UpsertSeiBinding(SeiBindingRecord)`** — INSERT OR REPLACE into `sei_bindings` (mint a new alive binding, or update a carried binding's `current_locator` / @@ -592,7 +592,7 @@ COMMIT; ### T2.3 — Analysis pipeline: SEI mint pass **Files:** -- Edit: `crates/clarion-cli/src/analyze.rs` +- Edit: `crates/loomweave-cli/src/analyze.rs` This runs as a new sub-phase between Phase 1.5 (enrichment) and Phase 2 (graph completion): "Phase 1.75 — SEI rebinding." It requires the prior index (already populated by Phase 1), @@ -618,7 +618,7 @@ the git-rename signal, and the current run's entity list. - [ ] **Step 2: Update the prior-index flush** (T1.4) to also write `signature` alongside `body_hash`, now that `entities.signature` is populated. -- [ ] **Step 3: Add a `--no-sei` flag** to `clarion analyze` that skips the mint pass — +- [ ] **Step 3: Add a `--no-sei` flag** to `loomweave analyze` that skips the mint pass — escape hatch for diagnostic runs on pre-migration DBs. - [ ] **Step 4: Determinism note.** Document in the code that SEI *values* are not part of the @@ -631,9 +631,9 @@ the git-rename signal, and the current run's entity list. ### T2.4 — HTTP wire contract: resolve, resolve_sei, lineage **Files:** -- Edit: `crates/clarion-cli/src/http_read.rs` +- Edit: `crates/loomweave-cli/src/http_read.rs` -- [ ] **Step 1: Add storage helpers** in `clarion-storage/src/sei.rs`. Resolution reads +- [ ] **Step 1: Add storage helpers** in `loomweave-storage/src/sei.rs`. Resolution reads `sei_bindings` (the identity source of truth), joining to `entities` only for `content_hash`: - `pub fn resolve_locator(conn, locator) -> Result>` — find the alive binding with `current_locator = locator`; return `{ sei, current_locator, content_hash, alive: true }`. @@ -644,8 +644,8 @@ the git-rename signal, and the current run's entity list. - [ ] **Step 2: Add `POST /api/v1/identity/resolve`** handler. Input: `{ locator: String }`. **Validation (REQ-F-02, fail-closed):** reject any input beginning with the reserved - `clarion:eid:` prefix (it is an SEI, not a locator) with a documented `"not a valid locator"` - 400 error — **do not** rely on a colon count, since an SEI `clarion:eid:` has the same two + `loomweave:eid:` prefix (it is an SEI, not a locator) with a documented `"not a valid locator"` + 400 error — **do not** rely on a colon count, since an SEI `loomweave:eid:` has the same two colons a locator does. Also reject inputs that are not `{plugin}:{kind}:{qualname}`-shaped (3 non-empty colon-separated segments). Returns `{ sei, current_locator, content_hash, alive: true }` or `{ alive: false }`. The reserved-prefix rule is what makes the idempotent, @@ -675,7 +675,7 @@ the git-rename signal, and the current run's entity list. ### T2.5 — MCP surface: carry SEI alongside entity_id **Files:** -- Edit: `crates/clarion-mcp/src/lib.rs` and tool handler modules +- Edit: `crates/loomweave-mcp/src/lib.rs` and tool handler modules Per invariant §4 of the priority brief: every surface that returns an identity for use as a binding key carries the SEI. No "MCP locator exception." @@ -692,7 +692,7 @@ a binding key carries the SEI. No "MCP locator exception." - [ ] **Step 3: Add `orientation_pack` and `project_status` sei metadata** — these should reflect whether the current index has SEI populated. -- [ ] **Step 4: Update the `clarion-workflow` skill** (embedded in `clarion-mcp/assets/`) +- [ ] **Step 4: Update the `loomweave-workflow` skill** (embedded in `loomweave-mcp/assets/`) to document that MCP tool responses carry `sei` alongside `entity_id`, and that `sei` is the key to use for cross-tool bindings. @@ -709,8 +709,8 @@ a binding key carries the SEI. No "MCP locator exception." rejection contract. - [ ] **Step 2: Document the hard cutover protocol** in `contracts.md` or a new - `docs/federation/sei-migration-playbook.md`: Clarion ships SEI, mints SEIs for all - entities, Filigree backfill re-keys `clarion_entity_id` from locators to SEIs, + `docs/federation/sei-migration-playbook.md`: Loomweave ships SEI, mints SEIs for all + entities, Filigree backfill re-keys `loomweave_entity_id` from locators to SEIs, Wardline client-layer update keys taint facts on SEI. Single coordinated release. Unresolvable orphans flagged for human review, never silently dropped. @@ -723,8 +723,8 @@ a binding key carries the SEI. No "MCP locator exception." ### T3.1 — Incremental analysis (skip unchanged files) **Files:** -- Edit: `crates/clarion-cli/src/analyze.rs` -- Edit: `crates/clarion-storage/src/query.rs` +- Edit: `crates/loomweave-cli/src/analyze.rs` +- Edit: `crates/loomweave-storage/src/query.rs` The prior-index retention from Phase 1 (T1.1–T1.4) provides the prerequisite: we have a per-locator `body_hash` from the previous run. File-level incremental skipping extends this @@ -760,19 +760,19 @@ to file entities. ### T3.2 — Dossier participation surface **Files:** -- Create: `docs/superpowers/specs/2026-06-02-clarion-dossier-participation.md` +- Create: `docs/superpowers/specs/2026-06-02-loomweave-dossier-participation.md` - Edit: `docs/federation/contracts.md` -Clarion does not assemble the dossier envelope (Wardline does). Clarion contributes its +Loomweave does not assemble the dossier envelope (Wardline does). Loomweave contributes its slice over HTTP. This task makes the contract explicit. -- [ ] **Step 1: Write the participation spec** documenting exactly which Clarion endpoints +- [ ] **Step 1: Write the participation spec** documenting exactly which Loomweave endpoints the dossier assembler calls and what it gets back: `resolve(locator)` → SEI, `/api/v1/entities/{id}/callers` + `/callees` → structural linkages, `GET /api/v1/files/{path}` → file context, `issues_for` (MCP) or equivalent HTTP → Filigree associations. This is the surface the Wardline dossier design - (`2026-06-01-wardline-loom-entity-dossier-design.md`) consumes. + (`2026-06-01-wardline-weft-entity-dossier-design.md`) consumes. - [ ] **Step 2: Pin any new HTTP endpoints** this reveals in `contracts.md`. @@ -784,14 +784,14 @@ slice over HTTP. This task makes the contract explicit. > **Scope cut (noted for honesty).** This integrated plan carries the roadmap's MCP-catalogue and > guidance items but **defers** the roadmap's other Half-1 operational-quality items — -> `clarion doctor` DB/plugin/config extensions and cost-estimate accuracy validation. They are not +> `loomweave doctor` DB/plugin/config extensions and cost-estimate accuracy validation. They are not > on the suite critical path and not cut for cause; they re-enter when the P0–P2 path is clear. The > roadmap remains the full Half-1 backlog; this plan is the critical-path-first slice of it. ### MCP-P1 — Navigation tools **Files:** -- Edit: `crates/clarion-mcp/src/lib.rs` +- Edit: `crates/loomweave-mcp/src/lib.rs` - [ ] `goto(entity_id)` — set session cursor - [ ] `goto_path(path, line?)` — resolve file+line to entity, set cursor @@ -805,7 +805,7 @@ slice over HTTP. This task makes the contract explicit. ### MCP-P2 — Inspection tools **Files:** -- Edit: `crates/clarion-mcp/src/lib.rs` +- Edit: `crates/loomweave-mcp/src/lib.rs` - [ ] `source(entity_id?)` — return source range content for entity (defaults to cursor) - [ ] `metadata(entity_id?)` — return full entity metadata including wardline, tags, properties @@ -817,7 +817,7 @@ slice over HTTP. This task makes the contract explicit. ### MCP-P3 — Search tools **Files:** -- Edit: `crates/clarion-mcp/src/lib.rs` +- Edit: `crates/loomweave-mcp/src/lib.rs` - [ ] `find_by_tag(tag, scope?)` — entities matching a tag - [ ] `find_by_kind(kind, scope?)` — entities of a specific kind @@ -830,13 +830,13 @@ slice over HTTP. This task makes the contract explicit. ### MCP-P4 — Guidance CLI **Files:** -- Edit: `crates/clarion-cli/src/main.rs` and new `crates/clarion-cli/src/guidance.rs` +- Edit: `crates/loomweave-cli/src/main.rs` and new `crates/loomweave-cli/src/guidance.rs` -- [ ] `clarion guidance create --match --scope-level ` — create guidance sheet -- [ ] `clarion guidance list [--for-entity ] [--stale] [--expired]` -- [ ] `clarion guidance show ` -- [ ] `clarion guidance edit ` — open in `$EDITOR` -- [ ] `clarion guidance promote ` — promote Filigree observation to sheet +- [ ] `loomweave guidance create --match --scope-level ` — create guidance sheet +- [ ] `loomweave guidance list [--for-entity ] [--stale] [--expired]` +- [ ] `loomweave guidance show ` +- [ ] `loomweave guidance edit ` — open in `$EDITOR` +- [ ] `loomweave guidance promote ` — promote Filigree observation to sheet --- @@ -845,7 +845,7 @@ slice over HTTP. This task makes the contract explicit. Per the priority brief §4 — apply to every task above: 1. **Opacity.** SEI is opaque. `resolve` and `resolve_sei` are the only legitimate entry - points. Nothing parses `clarion:eid:…` internally. + points. Nothing parses `loomweave:eid:…` internally. 2. **No binding keyed on a locator on any surface.** MCP and HTTP both carry SEI once Phase 2 ships. No MCP locator exception. 3. **Fail-closed / no false-green.** When the matcher cannot prove sameness, it mints and @@ -853,9 +853,9 @@ Per the priority brief §4 — apply to every task above: 4. **Typed git-rename interface.** `ShellGitRenameSource` implements a typed trait; `legis` supplies a second impl later without touching the model. 5. **Lineage is append-only with no backfill path.** `sei_lineage` has no UPDATE path; - only INSERT. No Clarion-side hash-chain in v1. -6. **Prior index is a side table.** Not a retained prior `clarion.db`. Nothing inflates it. -7. **No dossier assembly.** Clarion contributes its slice; the consumer composes. + only INSERT. No Loomweave-side hash-chain in v1. +6. **Prior index is a side table.** Not a retained prior `loomweave.db`. Nothing inflates it. +7. **No dossier assembly.** Loomweave contributes its slice; the consumer composes. --- @@ -865,7 +865,7 @@ Per the priority brief §4 — apply to every task above: |---|---| | **Phase 1 complete** | HTTP linkages live and tested; `sei_prior_index` populated after every run; `_capabilities` reflects `linkages: { http: true }`; ADR-038 accepted | | **SEI lock** | REQ-C-01 and REQ-C-02 decisions (Phase 1 / ADR-038) submitted to SEI spec §0.5 intake; all four subsystems reported; oracle spec finalized | -| **Phase 2 complete** | Every alive entity has an `alive` `sei_bindings` row after analysis; matcher handles rename/move/orphan cases per test suite; a back-to-back unchanged re-run **carries** (never re-mints) SEIs; HTTP identity routes live with the REQ-F-02 `clarion:eid:` rejection; MCP responses carry SEI via the binding join; `_capabilities` reflects `sei: { supported: true, version: 1 }` | +| **Phase 2 complete** | Every alive entity has an `alive` `sei_bindings` row after analysis; matcher handles rename/move/orphan cases per test suite; a back-to-back unchanged re-run **carries** (never re-mints) SEIs; HTTP identity routes live with the REQ-F-02 `loomweave:eid:` rejection; MCP responses carry SEI via the binding join; `_capabilities` reflects `sei: { supported: true, version: 1 }` | | **Migration cutover** | Coordinated release with Filigree + Wardline; all stored locators re-keyed to SEI; orphaned locators flagged; no mixed-format state | -| **Phase 3 complete** | Incremental analysis skips unchanged files; dossier participation contract pinned; `dossier(entity)` achievable by the Wardline assembler using Clarion's HTTP surface | +| **Phase 3 complete** | Incremental analysis skips unchanged files; dossier participation contract pinned; `dossier(entity)` achievable by the Wardline assembler using Loomweave's HTTP surface | | **Core paradise** | `dossier(entity)` returns complete, freshness-stamped, SEI-keyed envelope for a renamed function without orphaning its Wardline facts or Filigree associations | diff --git a/docs/superpowers/plans/2026-06-02-clarion-wave-2-execution-plan.md b/docs/superpowers/plans/2026-06-02-loomweave-wave-2-execution-plan.md similarity index 87% rename from docs/superpowers/plans/2026-06-02-clarion-wave-2-execution-plan.md rename to docs/superpowers/plans/2026-06-02-loomweave-wave-2-execution-plan.md index 6297192d..0d4bf2f1 100644 --- a/docs/superpowers/plans/2026-06-02-clarion-wave-2-execution-plan.md +++ b/docs/superpowers/plans/2026-06-02-loomweave-wave-2-execution-plan.md @@ -4,11 +4,11 @@ **Status:** In execution **Branch:** `feat/wave2-dossier-participation` (stacked on `feat/wave1-sei-authority`) **Source of truth:** program design §4 (Wave 2) + §5 invariants + D3; integrated delivery -plan Phase 3 (T3.1, T3.2); Wardline dossier design (`/home/john/wardline/.../2026-06-01-wardline-loom-entity-dossier-design.md`). +plan Phase 3 (T3.1, T3.2); Wardline dossier design (`/home/john/wardline/.../2026-06-01-wardline-weft-entity-dossier-design.md`). **Filigree:** WS4 = `clarion-4bccfb5f44`; T3.1 = `clarion-a96573d734`. This wave **closes core paradise**: `dossier(entity)` becomes achievable by the Wardline -assembler over Clarion's HTTP surface, and stays correct after a function is renamed. +assembler over Loomweave's HTTP surface, and stays correct after a function is renamed. ## Gate (verified in code, not just the plan) - **Wave 1 (WS1):** `/api/v1/identity/resolve`, `:batch`, `sei/:sei`, `lineage/:sei`, @@ -18,10 +18,10 @@ assembler over Clarion's HTTP surface, and stays correct after a function is ren - Both live on the linear stack (`main` is an ancestor). "Merged" ⇒ stable/won't-shift, which a linear stack satisfies. Proceed. -## Framing: Clarion serves, it does not assemble -The dossier is assembled by Wardline. Clarion's Wave 2 job is (a) guarantee every slice the +## Framing: Loomweave serves, it does not assemble +The dossier is assembled by Wardline. Loomweave's Wave 2 job is (a) guarantee every slice the assembler needs is HTTP-reachable + pin it, and (b) ship the incremental skip deferred from Wave 1. -**Do NOT** build a Clarion-owned dossier envelope or proxy sibling data. +**Do NOT** build a Loomweave-owned dossier envelope or proxy sibling data. --- @@ -83,14 +83,14 @@ assembler needs is HTTP-reachable + pin it, and (b) ship the incremental skip de ## T3.2 / WS4 — Dossier participation surface [contract + verification, light code] ### The surface (each verified HTTP-reachable) -| Dossier section | Clarion surface | HTTP | Status | +| Dossier section | Loomweave surface | HTTP | Status | |---|---|---|---| | identity (entity_id, content_hash, **content axis**) | `resolve(locator)` | POST `/api/v1/identity/resolve` (+`:batch`) | ✅ Wave 1 | | identity (**identity axis**: alive/orphaned + lineage) | `resolve_sei(sei)` | GET `/api/v1/identity/sei/:sei`, `/lineage/:sei` | ✅ Wave 1 | | linkages: callers/callees | — | GET `/api/v1/entities/:id/callers`/`callees` (+batch) | ✅ Wave 0 | | linkages: scc_peers | subsystem clustering (≠ true SCC) | — | ⚠ graceful-degrade + recommendation | | file context | file catalog | GET `/api/v1/files` (+`:resolve`, `/batch`) | ✅ existing | -| work (Filigree associations) | **Filigree's own** `/api/entity-associations` (ADR-029) | — | ✅ read DIRECTLY, not via Clarion | +| work (Filigree associations) | **Filigree's own** `/api/entity-associations` (ADR-029) | — | ✅ read DIRECTLY, not via Loomweave | ### Two-axis freshness (explicit, neither inferred from the other) - **Content axis:** `resolve(locator)` → `content_hash`; the assembler hash-compares its stored @@ -100,15 +100,15 @@ assembler needs is HTTP-reachable + pin it, and (b) ship the incremental skip de ### Filigree associations — the "GAP" resolved (enrich-only) The Wardline dossier design (§4, §9, §6.1) reads Filigree associations **directly** from Filigree's -own `GET /api/entity-associations?entity_id=…` (ADR-029, frozen). Clarion's `issues_for` is MCP-only, -but that is **not** a dossier gap: making Clarion serve Filigree associations would make Clarion a -Filigree **proxy/aggregator** — a direct violation of the enrich-only axiom (loom.md §5) and the Wave 2 -hard boundary ("do NOT aggregate Filigree issues into a Clarion object"). **Recommendation:** Clarion +own `GET /api/entity-associations?entity_id=…` (ADR-029, frozen). Loomweave's `issues_for` is MCP-only, +but that is **not** a dossier gap: making Loomweave serve Filigree associations would make Loomweave a +Filigree **proxy/aggregator** — a direct violation of the enrich-only axiom (weft.md §5) and the Wave 2 +hard boundary ("do NOT aggregate Filigree issues into a Loomweave object"). **Recommendation:** Loomweave provides only the **join key** (the SEI, via `resolve`); the assembler keys Filigree's own endpoint on -it. No Clarion endpoint added. +it. No Loomweave endpoint added. ### scc_peers -Clarion has subsystem clustering (`subsystem_members`/`subsystem_of_entity`), not strongly-connected- +Loomweave has subsystem clustering (`subsystem_members`/`subsystem_of_entity`), not strongly-connected- component peers. Surfacing subsystem peers under the dossier's `scc_peers[]` would be a semantic mismatch. The dossier degrades gracefully on partial linkages (callers/callees carry the load-bearing "fix locus / responsible boundary" synthesis). **Recommendation:** expose a thin subsystem-peers HTTP @@ -116,7 +116,7 @@ route as a follow-up only if Wardline confirms it wants subsystem peers (not tru not silently dropped. ### Deliverables -- `docs/superpowers/specs/2026-06-02-clarion-dossier-participation.md` (the spec above, expanded). +- `docs/superpowers/specs/2026-06-02-loomweave-dossier-participation.md` (the spec above, expanded). - Pin every depended-on endpoint in `docs/federation/contracts.md`. - e2e demonstration (extend `tests/serve.rs`): renamed-function fixture → the full set of assembler HTTP calls succeeds, SEI carried, facts not orphaned, freshness stamped. @@ -128,6 +128,6 @@ not silently dropped. (unchanged-file entities keep their SEI, not orphaned); prior-index decay test passes. - Participation spec written; every depended-on endpoint HTTP-reachable + pinned (or gap surfaced with recommendation — Filigree-direct + scc_peers). -- `dossier(entity)` achievable over Clarion's HTTP surface for a renamed-function fixture. +- `dossier(entity)` achievable over Loomweave's HTTP surface for a renamed-function fixture. - All ADR-023 Rust gates green. (Python untouched.) - Code review requested; honest core-paradise statement surfaced. Do NOT proceed into the parallel band / WS9. diff --git a/docs/superpowers/plans/2026-06-02-clarion-wave-3-legis-governance-plan.md b/docs/superpowers/plans/2026-06-02-loomweave-wave-3-legis-governance-plan.md similarity index 78% rename from docs/superpowers/plans/2026-06-02-clarion-wave-3-legis-governance-plan.md rename to docs/superpowers/plans/2026-06-02-loomweave-wave-3-legis-governance-plan.md index e32d0cab..728b47cb 100644 --- a/docs/superpowers/plans/2026-06-02-clarion-wave-3-legis-governance-plan.md +++ b/docs/superpowers/plans/2026-06-02-loomweave-wave-3-legis-governance-plan.md @@ -3,7 +3,7 @@ **Date:** 2026-06-02 **Branch:** `feat/wave3-legis-governance` (stacked on `feat/wave2-dossier-participation`) **Prompt:** `docs/superpowers/prompts/2026-06-02-wave-3-execution.md` -**Closes:** GOVERNED PARADISE — `legis` adds IRAP-grade governance keyed to Clarion's +**Closes:** GOVERNED PARADISE — `legis` adds IRAP-grade governance keyed to Loomweave's stable identity, as an OPT-IN layer invisible to a solo project. Does **not** gate core paradise. ## Gate check — DONE, both conditions met @@ -19,12 +19,12 @@ stable identity, as an OPT-IN layer invisible to a solo project. Does **not** ga ## Ground truth (verified in code, both sides) -**Clarion (the seam already exists, Wave 1):** -- `clarion-storage/src/sei.rs`: `GitRename { old_locator, new_locator }` + the typed `GitRenameSource` +**Loomweave (the seam already exists, Wave 1):** +- `loomweave-storage/src/sei.rs`: `GitRename { old_locator, new_locator }` + the typed `GitRenameSource` trait (`fn renames_since(&self, base_commit: &str) -> Vec`). The matcher is **fail-closed**: a rename is only a *hint*; the carry is confirmed by byte-identical body hash, so an over- or under-broad rename signal can never cause a *false* carry — only a missed/relabeled one. -- `clarion-cli/src/sei_git.rs`: `ShellGitRenameSource` (v1 concrete). The translation +- `loomweave-cli/src/sei_git.rs`: `ShellGitRenameSource` (v1 concrete). The translation `parse_git_rename_lines` → `path_to_module` → `file_renames_to_locator_renames` are **shared free functions** — reusable by any provider. - `analyze.rs:1107-1115`: builds `ShellGitRenameSource` and calls `.renames_since("")`. @@ -35,19 +35,19 @@ stable identity, as an OPT-IN layer invisible to a solo project. Does **not** ga - `src/legis/git/surface.py` `GitSurface.renames(rev_range)` → `RenameEvidence{commit_sha, old_path, new_path, similarity}` via `git log -M --diff-filter=R` over a **committed rev-range**. - `src/legis/api/app.py` exposes `GET /git/renames?rev_range=…`. Docstring: "WP-6.3 re-exposes this - surface to Clarion's matcher." `docs/federation/sei-conformance.md` claims the §6 seam (REQ-L-02). -- legis is a **pull-only polling consumer** of Clarion's `resolve_sei` + `lineage`; for REQ-L-01 it + surface to Loomweave's matcher." `docs/federation/sei-conformance.md` claims the §6 seam (REQ-L-02). +- legis is a **pull-only polling consumer** of Loomweave's `resolve_sei` + `lineage`; for REQ-L-01 it accepts **Option 3** (store its own lineage snapshot hash, detect divergence on re-read) → it wants - **no Clarion-side hash-chain / signature**. + **no Loomweave-side hash-chain / signature**. ## The load-bearing design fact — window mismatch (drives the deliverable shape) `analyze` depends on the **working-tree-vs-HEAD** rename window (empty base). legis's `/git/renames` -serves only **committed** renames over a rev-range. In Clarion's canonical dev flow legis returns +serves only **committed** renames over a rev-range. In Loomweave's canonical dev flow legis returns **empty** → naively flipping the operative source to legis would be **enrich-NEGATIVE** (silently lose the renamed-and-edited uncommitted case, relabel `locator_changed`→`moved`). That violates both the -prompt's enrich-only invariant and the unstated corollary *Clarion-with-legis must not be worse than -Clarion-without*. +prompt's enrich-only invariant and the unstated corollary *Loomweave-with-legis must not be worse than +Loomweave-without*. **Resolution (honest, regression-free):** build the `LegisGitRenameSource` seam + degrade path and make the selector **capability-aware on the base window** — for the empty (working-tree) window the @@ -59,8 +59,8 @@ neither choice risks a false carry. ## Workstream A — audit-spine consumption contract (doc + verify only) -- legis reads Clarion's **existing** Wave-1 surface (`resolve_sei`, `lineage`) as its audit spine. - No new endpoint; no Clarion-side integrity machinery (REQ-L-01 Option 3). +- legis reads Loomweave's **existing** Wave-1 surface (`resolve_sei`, `lineage`) as its audit spine. + No new endpoint; no Loomweave-side integrity machinery (REQ-L-01 Option 3). - Verify reachability of the surfaces legis relies on; pin the consumption contract in `docs/federation/contracts.md` (consumer obligations, two-axis status, honest-degrade flag, orphan→governance-gap reliance). Fill an additive gap **only if** legis surfaces a blocking one — @@ -74,33 +74,33 @@ neither choice risks a false carry. shell-parse path and the legis-JSON-parse path (shared translation fn). - `LegisGitRenameSource` parses legis `/git/renames` JSON → `(old_path,new_path)` pairs → locators. - **degrade:** legis absent / URL unset / unreachable → empty signal, and the selector falls back - to `ShellGitRenameSource` (enrich-only). Clarion-without-legis is byte-identical to today. + to `ShellGitRenameSource` (enrich-only). Loomweave-without-legis is byte-identical to today. - capability: empty base → selector returns shell even when legis is configured (no regression). -2. **GREEN:** add `reqwest.workspace = true` to clarion-cli; implement `LegisGitRenameSource` +2. **GREEN:** add `reqwest.workspace = true` to loomweave-cli; implement `LegisGitRenameSource` (`base_url`, `current_locators`) behind `GitRenameSource`; add `resolve_git_rename_source(...)` with enrich-only discipline mirroring `filigree_url.rs`. Thread `legis_url: Option` into `AnalyzeOptions` + a `--legis-url` CLI flag (default `None` → shell). ## Workstream C — trust vocabulary carried verbatim (verify, no adjudication) -- Clarion carries `declared_tier` / `wardline_groups` exactly as Wardline emits them (the v1.0 +- Loomweave carries `declared_tier` / `wardline_groups` exactly as Wardline emits them (the v1.0 `WardlineMeta` ingestion concept). WS9 adds **no** trust adjudication and **no** policy/attestation - engine — attestations live in `legis`, keyed on Clarion's SEI. Verify nothing regresses; note the + engine — attestations live in `legis`, keyed on Loomweave's SEI. Verify nothing regresses; note the posture in the contract. Any incompleteness in v1.0 carriage is a **pre-existing gap**, not WS9's job. -## Invariants held (loom.md §5) +## Invariants held (weft.md §5) -- **Opt-in:** `--legis-url` unset → behavior identical to today. Solo Clarion untouched. +- **Opt-in:** `--legis-url` unset → behavior identical to today. Solo Loomweave untouched. - **Fail-closed:** legis unreachable → empty signal → shell fallback → matcher confirms by body hash. - **Enrich-only:** legis may supply the git signal; it is never *required*; it never moves identity - authority out of Clarion; Clarion never adjudicates trust (one judge: Wardline analyses, legis governs). + authority out of Loomweave; Loomweave never adjudicates trust (one judge: Wardline analyses, legis governs). ## Definition of done - Audit-spine contract pinned; legis reads SEI+lineage over the existing surface; no integrity machinery. - `LegisGitRenameSource` behind the typed interface; `ShellGitRenameSource` the fallback; tests prove identical `GitRename` output for identical input + clean legis-absent degrade. -- Window-semantics gap surfaced with a recommendation (legis working-tree rename surface, or Clarion +- Window-semantics gap surfaced with a recommendation (legis working-tree rename surface, or Loomweave rev-range re-index driving) — not papered over. - Trust vocab verbatim; no adjudication added. -- Clarion solo + Clarion-without-legis both fully functional. All ADR-023 gates green. Code review. +- Loomweave solo + Loomweave-without-legis both fully functional. All ADR-023 gates green. Code review. diff --git a/docs/superpowers/plans/2026-06-02-clarion-ws5-mcp-catalogue-plan.md b/docs/superpowers/plans/2026-06-02-loomweave-ws5-mcp-catalogue-plan.md similarity index 94% rename from docs/superpowers/plans/2026-06-02-clarion-ws5-mcp-catalogue-plan.md rename to docs/superpowers/plans/2026-06-02-loomweave-ws5-mcp-catalogue-plan.md index 8468c30b..f0c03abd 100644 --- a/docs/superpowers/plans/2026-06-02-clarion-ws5-mcp-catalogue-plan.md +++ b/docs/superpowers/plans/2026-06-02-loomweave-ws5-mcp-catalogue-plan.md @@ -1,13 +1,13 @@ # WS5 — MCP Catalogue Completion — Execution Plan (Wave 4) **Date:** 2026-06-02 -**Design (source of truth):** `docs/superpowers/specs/2026-06-02-clarion-ws5-mcp-catalogue-design.md` +**Design (source of truth):** `docs/superpowers/specs/2026-06-02-loomweave-ws5-mcp-catalogue-design.md` **Execution prompt:** `docs/superpowers/prompts/2026-06-02-wave-4-execution.md` **Method:** subagent-driven-development, TDD, task-by-task. All ADR-023 Rust gates green. ## Ground truth established (recon, 2026-06-02) -- **Tool registry** lives in `crates/clarion-mcp/src/lib.rs`: `list_tools()` (Vec) +- **Tool registry** lives in `crates/loomweave-mcp/src/lib.rs`: `list_tools()` (Vec) + `handle_tool_call()` match dispatch → `impl ServerState` `tool_*` async methods returning `Result`. 19 stateless tools ship today. - **SEI for free:** route every entity-returning response through `entity_json(conn, &entity)` @@ -35,7 +35,7 @@ composition over the `guidance_sheets` view (READ only; authoring is WS6). - `find_circular_imports` / `find_coupling_hotspots` are **real** graph queries over `edges`. - **emit_observation:** no HTTP observation-write route exists (Filigree 2.2.0: `GET - /api/loom/observations` 200, `POST` 405); ADR-016's sanctioned `filigree mcp` subprocess + /api/weft/observations` 200, `POST` 405); ADR-016's sanctioned `filigree mcp` subprocess transport is unbuilt; v0.2 HTTP trigger un-fired. → **Deferred + tracked** (no permanent-fail stub, no scan-results coercion, no silent CLI shell-out). Mirrors WS5b / WP9-B deferral discipline. - No `glob`/`globset` dep → implement a minimal path-glob matcher (`*`, `**`, literal) in the @@ -43,7 +43,7 @@ ## Architecture -New submodule `crates/clarion-mcp/src/catalogue/` (child of crate root → can access `ServerState` +New submodule `crates/loomweave-mcp/src/catalogue/` (child of crate root → can access `ServerState` private fields + crate-private helpers). Implementations + helpers live there; the only edits to the 6800-line `lib.rs` are: `mod catalogue;`, the new `ToolDefinition`s in `list_tools()`, and the new dispatch arms. Keep those three edits serialized (one implementer at a time — never parallel). @@ -78,7 +78,7 @@ Shared foundation (`catalogue/mod.rs`): unemitted today. **TDD the honest-empty behaviour.** REQ-MCP-03. - [ ] **Task 5 — emit_observation deferral.** Do NOT register a tool. File the deferral Filigree issue citing ADR-016 + REQ-MCP-05; record the rationale. (Mirrors WS5b/WP9-B.) -- [ ] **Task 6 — Docs.** Update `crates/clarion-mcp/assets/skills/clarion-workflow/SKILL.md` to +- [ ] **Task 6 — Docs.** Update `crates/loomweave-mcp/assets/skills/loomweave-workflow/SKILL.md` to describe the new tools (stateless, SEI-carrying, bounded, honest-empty). File the §8 cursor-model "see WS5 — stateless" reconciliation doc task (Filigree). Do NOT edit the 1.0 canonical doc's decisions. diff --git a/docs/superpowers/plans/2026-06-02-clarion-ws5b-advanced-queries-plan.md b/docs/superpowers/plans/2026-06-02-loomweave-ws5b-advanced-queries-plan.md similarity index 85% rename from docs/superpowers/plans/2026-06-02-clarion-ws5b-advanced-queries-plan.md rename to docs/superpowers/plans/2026-06-02-loomweave-ws5b-advanced-queries-plan.md index b67df546..75c1812c 100644 --- a/docs/superpowers/plans/2026-06-02-clarion-ws5b-advanced-queries-plan.md +++ b/docs/superpowers/plans/2026-06-02-loomweave-ws5b-advanced-queries-plan.md @@ -5,19 +5,19 @@ **Date:** 2026-06-02 **Status:** Design + delivery plan -**Workstream:** WS5b of the Clarion first-class program — the two tools split out of WS5 -(`2026-06-02-clarion-ws5-mcp-catalogue-design.md` §7). Parallel band; soft-gated on WS5 (the +**Workstream:** WS5b of the Loomweave first-class program — the two tools split out of WS5 +(`2026-06-02-loomweave-ws5-mcp-catalogue-design.md` §7). Parallel band; soft-gated on WS5 (the stateless catalogue surface it extends). **Goal:** Deliver `search_semantic` (Part A) and `find_dead_code` (Part B) — the two capabilities that need infrastructure beyond a catalog query. These are **scheduled, not deferred**. **Inputs / authorities:** -- `2026-06-02-clarion-ws5-mcp-catalogue-design.md` — the stateless MCP surface this extends -- `docs/clarion/1.0/system-design.md` §5 (Policy Engine / LLM provider abstraction — the pattern +- `2026-06-02-loomweave-ws5-mcp-catalogue-design.md` — the stateless MCP surface this extends +- `docs/loomweave/1.0/system-design.md` §5 (Policy Engine / LLM provider abstraction — the pattern the `EmbeddingProvider` mirrors), §3 (Data Model — edges) -- `docs/clarion/adr/ADR-005-clarion-dir-tracking.md` (git-committable `.clarion/` — the +- `docs/loomweave/adr/ADR-005-loomweave-dir-tracking.md` (git-committable `.loomweave/` — the embedding-storage posture must respect this) -- `docs/clarion/adr/ADR-028-edge-confidence-tiers.md` (reachability uses edge tiers) +- `docs/loomweave/adr/ADR-028-edge-confidence-tiers.md` (reachability uses edge tiers) --- @@ -34,7 +34,7 @@ and is recommended first. ### A.1 Design decisions **Opt-in, mirroring the LLM policy (local-first doctrine).** Embeddings are **disabled by -default**, exactly like `llm_policy`. Loom is local-first and single-binary; nothing here may make +default**, exactly like `llm_policy`. Weft is local-first and single-binary; nothing here may make a hosted service *required*. When semantic search is off, `search_semantic` returns an honest `"semantic search not enabled"` — never a faked or empty-as-if-complete result. @@ -46,13 +46,13 @@ trait EmbeddingProvider { fn model_id(&self) -> &str; } ``` -Configured under `clarion.yaml: semantic_search:` (`enabled`, `provider`, `model_id`, +Configured under `loomweave.yaml: semantic_search:` (`enabled`, `provider`, `model_id`, `api_key_env`, dim). A `RecordingEmbeddingProvider` exists for deterministic tests (like `RecordingProvider`). **Storage posture (load-bearing — protects the git-committable DB).** Embeddings are large (~100–200k entities × dim × 4 bytes ≈ hundreds of MB) and must NOT bloat the committed -`.clarion/clarion.db`. They live in a **separate, git-ignored sidecar** `.clarion/embeddings.db` +`.loomweave/loomweave.db`. They live in a **separate, git-ignored sidecar** `.loomweave/embeddings.db` (added to the ADR-005 `.gitignore` list), keyed by `(entity_id, content_hash, model_id)` so they invalidate on content change exactly like the summary cache. Textual export excludes them (rebuildable). @@ -82,10 +82,10 @@ embedding cost; `on_exceed` applies. Re-embedding skips unchanged `(entity_id, c ### A.3 Tasks — Part A - [ ] **A.T0 — ADR.** `ADR-039-semantic-search-embeddings.md`: opt-in posture, `EmbeddingProvider` - trait, sidecar storage (ADR-005 amendment for `.clarion/embeddings.db` gitignore), policy-engine + trait, sidecar storage (ADR-005 amendment for `.loomweave/embeddings.db` gitignore), policy-engine cost governance, the D-WS5b-1 provider decision. Register in the ADR index + glossary if any - cross-product term is introduced (likely none — `search_semantic` is Clarion-local). -- [ ] **A.T1 — sidecar + gitignore.** `.clarion/embeddings.db` (separate connection; not the + cross-product term is introduced (likely none — `search_semantic` is Loomweave-local). +- [ ] **A.T1 — sidecar + gitignore.** `.loomweave/embeddings.db` (separate connection; not the writer-actor's committed DB). Add to the ADR-005 `.gitignore` defaults. Migration for the embeddings schema (`entity_embeddings(entity_id, content_hash, model_id, dim, vec BLOB)`). - [ ] **A.T2 — `EmbeddingProvider` trait + impls** (test-first): the trait, the chosen impl @@ -116,7 +116,7 @@ dynamic-dispatch / reflection signals as reachability barriers that keep targets under-report dead code than to over-report it. **Heuristic output, honestly labelled.** Results are **Findings** -(`CLA-FACT-DEAD-CODE-CANDIDATE`, `confidence < 1`, `confidence_basis: heuristic`) and the MCP tool +(`LMWV-FACT-DEAD-CODE-CANDIDATE`, `confidence < 1`, `confidence_basis: heuristic`) and the MCP tool returns the same with a confidence and the reason it could not prove reach. Never presented as certain. Framework-magic entry kinds (decorated handlers, plugin hooks) are excluded from candidacy by default with a documented exclusion list. @@ -134,7 +134,7 @@ changes). No new analyze-time precompute required. all confidence tiers; dynamic-dispatch/reflection barriers keep targets live. Cache the reachable set per index version. Fixtures: a known-dead leaf is flagged; a reflectively-called function is NOT flagged; an ambiguous-edge target is NOT flagged. -- [ ] **B.T3 — `find_dead_code(scope?)` MCP tool + `CLA-FACT-DEAD-CODE-CANDIDATE` finding**: +- [ ] **B.T3 — `find_dead_code(scope?)` MCP tool + `LMWV-FACT-DEAD-CODE-CANDIDATE` finding**: unreached entities as candidates with confidence + reason; `sei` carried; bounded + paginated. - [ ] **B.T4 — docs**: rule catalogue entry for the new finding; contracts/skill update. @@ -154,7 +154,7 @@ changes). No new analyze-time precompute required. the git-ignored sidecar (committed DB unbloated); cost governed by the policy engine; results carry `sei` + score, bounded. ADR-039 accepted; ADR-005 gitignore amended. - **Part B:** `find_dead_code` ships on-demand, conservative (fails toward "live"), heuristic - results labelled with confidence; `CLA-FACT-DEAD-CODE-CANDIDATE` finding emitted; fixtures prove + results labelled with confidence; `LMWV-FACT-DEAD-CODE-CANDIDATE` finding emitted; fixtures prove no live-code false-positives on reflection/ambiguous edges. - Deferrals from WS5 are now **delivered**, not slipped. Any further deferral (an ANN backend if the exact scan misses latency) is itself logged with a trigger, never silent. diff --git a/docs/superpowers/plans/2026-06-02-clarion-ws6-guidance-maturity-plan.md b/docs/superpowers/plans/2026-06-02-loomweave-ws6-guidance-maturity-plan.md similarity index 74% rename from docs/superpowers/plans/2026-06-02-clarion-ws6-guidance-maturity-plan.md rename to docs/superpowers/plans/2026-06-02-loomweave-ws6-guidance-maturity-plan.md index ca42b310..2a9b1b47 100644 --- a/docs/superpowers/plans/2026-06-02-clarion-ws6-guidance-maturity-plan.md +++ b/docs/superpowers/plans/2026-06-02-loomweave-ws6-guidance-maturity-plan.md @@ -5,21 +5,21 @@ **Date:** 2026-06-02 **Status:** Design + delivery plan (the design is folded in; this is not a bare task list) -**Workstream:** WS6 of the Clarion first-class program — **Wave 6**. Code-intelligence half; +**Workstream:** WS6 of the Loomweave first-class program — **Wave 6**. Code-intelligence half; ungated/concurrent. **Goal:** Bring the guidance system from "schema baseline" to "mature" — the authoring CLI, the anti-poisoning propose→promote lifecycle, Wardline-derived generation, staleness signals, and team import/export. **Inputs / authorities:** -- `docs/clarion/1.0/system-design.md` §7 (Guidance System — the design this implements) -- `docs/clarion/1.0/requirements.md` REQ-GUIDANCE-*, REQ-BRIEFING-05, NFR-SEC-02 -- `docs/clarion/adr/ADR-024-guidance-schema-vocabulary.md` (the `scope_level`/`scope_rank`/ +- `docs/loomweave/1.0/system-design.md` §7 (Guidance System — the design this implements) +- `docs/loomweave/1.0/requirements.md` REQ-GUIDANCE-*, REQ-BRIEFING-05, NFR-SEC-02 +- `docs/loomweave/adr/ADR-024-guidance-schema-vocabulary.md` (the `scope_level`/`scope_rank`/ `pinned`/`provenance` vocabulary) -- `docs/superpowers/specs/2026-06-02-clarion-ws5-mcp-catalogue-design.md` §6 (the WS5/WS6 boundary) +- `docs/superpowers/specs/2026-06-02-loomweave-ws5-mcp-catalogue-design.md` §6 (the WS5/WS6 boundary) - Ground truth: the guidance schema exists (migration 0001) and `guidance_fingerprint` feeds the - summary-cache key (`clarion-storage/src/cache.rs`); the **authoring CLI, the propose/promote - lifecycle, the `CLA-FACT-GUIDANCE-*` staleness findings, Wardline-derived generation, and + summary-cache key (`loomweave-storage/src/cache.rs`); the **authoring CLI, the propose/promote + lifecycle, the `LMWV-FACT-GUIDANCE-*` staleness findings, Wardline-derived generation, and import/export are NOT built.** Verify the exact composition state before building. --- @@ -37,7 +37,7 @@ staleness signals as reviewable findings · import/export. **Verify the composit ## 1. Design decisions **1.1 Two authoring surfaces, one boundary (WS5/WS6).** -- **Human authoring → CLI.** `clarion guidance create/edit/show/list/delete/promote` — direct, +- **Human authoring → CLI.** `loomweave guidance create/edit/show/list/delete/promote` — direct, operator-driven sheet management. - **Agent suggestion → MCP, mediated.** `propose_guidance(entity_id, content, rules?)` produces a Filigree **observation**, NOT a sheet; `promote_guidance(obs_id)` (operator action, CLI or MCP) @@ -53,16 +53,16 @@ preserved by ID (`provenance: wardline_derived_overridden`). The gap today is th actual `wardline.yaml` + fingerprint. **1.3 Staleness as reviewable findings (no auto-expiry).** Emit, per `analyze`: -- `CLA-FACT-GUIDANCE-CHURN-STALE` — aggregate git-churn over matched entities since +- `LMWV-FACT-GUIDANCE-CHURN-STALE` — aggregate git-churn over matched entities since `authored_at`/`reviewed_at` exceeds threshold (50; **20 for `pinned`** — asymmetric on purpose, pinned sheets shape output most). `confidence: 0.7, confidence_basis: heuristic`. -- `CLA-FACT-GUIDANCE-ORPHAN` — explicit-entity `match_rules` pointing at a deleted entity. -- `CLA-FACT-GUIDANCE-EXPIRED` — past `expires` (excluded from composition, kept in store). -- `CLA-FACT-GUIDANCE-STALE` — `wardline.yaml` ↔ derived-guidance drift. -Surfaced via `clarion guidance list --stale`/`--expired` + the findings. **Auto-expiry is NOT the +- `LMWV-FACT-GUIDANCE-ORPHAN` — explicit-entity `match_rules` pointing at a deleted entity. +- `LMWV-FACT-GUIDANCE-EXPIRED` — past `expires` (excluded from composition, kept in store). +- `LMWV-FACT-GUIDANCE-STALE` — `wardline.yaml` ↔ derived-guidance drift. +Surfaced via `loomweave guidance list --stale`/`--expired` + the findings. **Auto-expiry is NOT the design** — the signal pushes operators to review; the decision stays human. -**1.4 Import/export.** `clarion guidance export --to ` / `import ` for team sharing +**1.4 Import/export.** `loomweave guidance export --to ` / `import ` for team sharing (deterministic, diff-friendly). ## 2. Owner-decision (flag, don't pre-empt) @@ -73,9 +73,9 @@ design** — the signal pushes operators to review; the decision stays human. ## 3. Tasks -- [ ] **T1 — CLI authoring.** `clarion guidance create --match <…> --scope-level <…>` / `edit ` +- [ ] **T1 — CLI authoring.** `loomweave guidance create --match <…> --scope-level <…>` / `edit ` (in `$EDITOR`) / `show ` / `list [--for-entity ] [--stale] [--expired]` / `delete `. - New `crates/clarion-cli/src/guidance.rs` + `cli.rs` subcommand. Test-first on match-rule parsing + New `crates/loomweave-cli/src/guidance.rs` + `cli.rs` subcommand. Test-first on match-rule parsing + scope-rank ordering. - [ ] **T2 — propose→promote lifecycle (MCP).** `propose_guidance` → Filigree observation (not a sheet); `promote_guidance(obs_id)` → sheet (operator action). Test the anti-poisoning property: @@ -83,11 +83,11 @@ design** — the signal pushes operators to review; the decision stays human. - [ ] **T3 — Wardline-derived generation.** Generate/regenerate derived sheets each analyze; preserve user overrides by ID. **Conformance test against a real `wardline.yaml` + fingerprint**, not only synthetic fixtures. -- [ ] **T4 — staleness findings.** The four `CLA-FACT-GUIDANCE-*` findings (§1.3) + the +- [ ] **T4 — staleness findings.** The four `LMWV-FACT-GUIDANCE-*` findings (§1.3) + the `list --stale/--expired` filters + churn-eager cache invalidation tie-in (ADR-007). Rule-catalogue entries. Test the asymmetric pinned threshold. - [ ] **T5 — import/export.** Deterministic export/import; round-trip test. -- [ ] **T6 — docs.** `clarion-workflow` skill + guidance docs; rule catalogue. +- [ ] **T6 — docs.** `loomweave-workflow` skill + guidance docs; rule catalogue. ## 4. Hard boundaries — do NOT - Do NOT build `guidance_for` (READ) — that's WS5 (Wave 4). WS6 owns authoring + lifecycle + write. @@ -106,9 +106,9 @@ design** — the signal pushes operators to review; the decision stays human. no silent auto-expiry), enrich-only. ## 6. Definition of done (Wave 6 / WS6) -- `clarion guidance` CLI complete (create/edit/show/list/delete/promote). +- `loomweave guidance` CLI complete (create/edit/show/list/delete/promote). - propose→observation→promote lifecycle live; proposed sheets provably NOT composed until promoted. - Wardline-derived generation stable, override-preserving, tested against REAL Wardline output. -- The four `CLA-FACT-GUIDANCE-*` staleness findings emit + are reviewable; no auto-expiry. +- The four `LMWV-FACT-GUIDANCE-*` staleness findings emit + are reviewable; no auto-expiry. - Import/export round-trips deterministically. - All CI gates green. D5's deferred items (review UI) logged with a trigger, not silently dropped. diff --git a/docs/superpowers/plans/2026-06-02-clarion-ws7-multi-language-plan.md b/docs/superpowers/plans/2026-06-02-loomweave-ws7-multi-language-plan.md similarity index 85% rename from docs/superpowers/plans/2026-06-02-clarion-ws7-multi-language-plan.md rename to docs/superpowers/plans/2026-06-02-loomweave-ws7-multi-language-plan.md index f0663e11..10692d4c 100644 --- a/docs/superpowers/plans/2026-06-02-clarion-ws7-multi-language-plan.md +++ b/docs/superpowers/plans/2026-06-02-loomweave-ws7-multi-language-plan.md @@ -5,19 +5,19 @@ **Date:** 2026-06-02 **Status:** Design + delivery plan (design folded in; not a bare task list) -**Workstream:** WS7 of the Clarion first-class program — **Wave 7**. Code-intelligence half; +**Workstream:** WS7 of the Loomweave first-class program — **Wave 7**. Code-intelligence half; ungated/concurrent. **Goal:** Make "any language" real, not aspirational — publish the plugin protocol as an external spec, ship a conformance harness, validate distribution with a real out-of-tree plugin, and build a second-language plugin. **Other languages are *other producers*, not a core rewrite.** **Inputs / authorities:** -- `docs/clarion/1.0/system-design.md` §2 (Core / Plugin Architecture — the protocol this publishes) -- `docs/clarion/adr/ADR-022-core-plugin-ontology.md` (core stays language-agnostic; plugin owns +- `docs/loomweave/1.0/system-design.md` §2 (Core / Plugin Architecture — the protocol this publishes) +- `docs/loomweave/adr/ADR-022-core-plugin-ontology.md` (core stays language-agnostic; plugin owns ontology), ADR-027 (ontology-version semver), ADR-033 (v1.0 distribution via GitHub Releases), ADR-026 (containment wire + edge identity), ADR-038 (manifest `signature_schemas`) -- Ground truth: the protocol IS implemented (`clarion-core/src/plugin/{manifest,host,discovery}.rs`) - and `clarion-plugin-fixture` exists — but there is **NO external protocol documentation** and no +- Ground truth: the protocol IS implemented (`loomweave-core/src/plugin/{manifest,host,discovery}.rs`) + and `loomweave-plugin-fixture` exists — but there is **NO external protocol documentation** and no out-of-tree conformance/distribution validation. Confirm before building. --- @@ -27,7 +27,7 @@ second-language plugin. **Other languages are *other producers*, not a core rewr | Piece | What | Gate | |---|---|---| | **1. Publish the protocol spec** | the L4 JSON-RPC contract + manifest, as a versioned external doc | none | -| **2. Conformance harness** | generalise `clarion-plugin-fixture` into a suite a new author runs | none | +| **2. Conformance harness** | generalise `loomweave-plugin-fixture` into a suite a new author runs | none | | **3. Distribution validation** | prove ADR-033 with a real out-of-tree plugin | none | | **4. Second-language plugin** | a TS/Go/Rust plugin proving the core is language-agnostic | **D2** | @@ -46,7 +46,7 @@ Content-Length JSON-RPC framing, and every manifest field (`plugin_id`, `kinds`, `docs/federation/`) as the authority an external author implements against. **Version it** (ADR-027 semver) so plugins can pin. -**1.2 Conformance harness — the plugin oracle.** Generalise `clarion-plugin-fixture` into a +**1.2 Conformance harness — the plugin oracle.** Generalise `loomweave-plugin-fixture` into a reusable suite a new plugin author runs against the core: manifest validity, entity/edge/finding emission shapes, streaming framing, `build_prompt` rendering, the ADR-026 containment/edge-identity contract. Analogous to the SEI conformance oracle — "conformant" is proven, not claimed. Structural @@ -54,7 +54,7 @@ compatibility is necessary, not sufficient. **1.3 Distribution validation (ADR-033).** Validate the real path end-to-end with an **out-of-tree** plugin (not the in-repo fixture): GitHub-Release asset → `pipx install ` (or the -language-appropriate equivalent) → `~/.config/clarion/plugins.toml` registration → discovery → +language-appropriate equivalent) → `~/.config/loomweave/plugins.toml` registration → discovery → analyze. This is what proves a third party can actually ship a plugin. **1.4 Core stays language-agnostic (ADR-022 — load-bearing).** WS7 adds NO fixed kinds, no @@ -75,7 +75,7 @@ Python AST analyzer stays Python (North Star — no rewrite). - [ ] **T1 — publish the protocol spec.** From the implementation, write the versioned external `docs/plugin-protocol/` spec (lifecycle, framing, every manifest field, the reserved core edge kinds, ontology-version semver). Cross-check it against `manifest.rs`/`host.rs` so doc = reality. -- [ ] **T2 — conformance harness.** Generalise `clarion-plugin-fixture` into a runnable conformance +- [ ] **T2 — conformance harness.** Generalise `loomweave-plugin-fixture` into a runnable conformance suite + a "your plugin conforms" report. Test-first: a deliberately-broken fixture fails the right check; the Python plugin passes. - [ ] **T3 — distribution validation (ADR-033).** Drive the full out-of-tree install→register→ @@ -84,8 +84,8 @@ Python AST analyzer stays Python (North Star — no rewrite). - [ ] **T4 — second-language plugin (gated on D2).** Build a minimal but real plugin in the chosen language: manifest, entity/edge extraction for that language, conformance-suite green, distributed via the validated path. Proves the core needed no change (ADR-022). -- [ ] **T5 — docs.** Author-facing "write a Clarion plugin" guide referencing the protocol spec + - conformance harness; `clarion-workflow`/README updates. +- [ ] **T5 — docs.** Author-facing "write a Loomweave plugin" guide referencing the protocol spec + + conformance harness; `loomweave-workflow`/README updates. ## 4. Hard boundaries — do NOT - Do NOT add language-specific logic, fixed kinds, or hardcoded concepts to the core (ADR-022). A diff --git a/docs/superpowers/plans/2026-06-02-clarion-ws8-operational-quality-plan.md b/docs/superpowers/plans/2026-06-02-loomweave-ws8-operational-quality-plan.md similarity index 86% rename from docs/superpowers/plans/2026-06-02-clarion-ws8-operational-quality-plan.md rename to docs/superpowers/plans/2026-06-02-loomweave-ws8-operational-quality-plan.md index 82d71090..dc3ecb86 100644 --- a/docs/superpowers/plans/2026-06-02-clarion-ws8-operational-quality-plan.md +++ b/docs/superpowers/plans/2026-06-02-loomweave-ws8-operational-quality-plan.md @@ -5,24 +5,24 @@ **Date:** 2026-06-02 **Status:** Design + delivery plan (design folded in; not a bare task list) -**Workstream:** WS8 of the Clarion first-class program — **Wave 8 (Track B)**. Code-intelligence +**Workstream:** WS8 of the Loomweave first-class program — **Wave 8 (Track B)**. Code-intelligence half; ungated/concurrent. The last workstream to be planned — this locks the program at the plan level (all nine planned). -**Goal:** Operator-grade robustness — extend `clarion doctor` beyond orientation surfaces, make the +**Goal:** Operator-grade robustness — extend `loomweave doctor` beyond orientation surfaces, make the cost estimate trustworthy, and surface summary-cache semantic staleness before operators act on a stale briefing. **Inputs / authorities:** -- `docs/clarion/1.0/system-design.md` §5 (Policy Engine / cost estimate / summary-cache staleness) -- `docs/clarion/1.0/requirements.md` NFR-COST-03 (±50% estimate accuracy), NFR-OPS-* -- `docs/clarion/adr/ADR-035-operational-tuning-discipline.md` (the declared-basis discipline this +- `docs/loomweave/1.0/system-design.md` §5 (Policy Engine / cost estimate / summary-cache staleness) +- `docs/loomweave/1.0/requirements.md` NFR-COST-03 (±50% estimate accuracy), NFR-OPS-* +- `docs/loomweave/adr/ADR-035-operational-tuning-discipline.md` (the declared-basis discipline this serves), ADR-007 (summary-cache key + staleness) - Ground truth (verify before building): - - `clarion doctor` (`crates/clarion-cli/src/doctor.rs`) today checks **only** three orientation + - `loomweave doctor` (`crates/loomweave-cli/src/doctor.rs`) today checks **only** three orientation surfaces (skill pack, hook, `.mcp.json`) + the index snapshot, on a report-or-`--fix` model. DB health / plugin availability / config validation are **net-new** checks. - `summary_cache.stale_semantic` **exists** (schema + the MCP status tool returns it, - `clarion-mcp/src/tools/status.rs`). **Open question to verify:** does neighborhood-drift + `loomweave-mcp/src/tools/status.rs`). **Open question to verify:** does neighborhood-drift detection actually *set* it during `analyze`, or is it only ever stored false? Scope T5 by the answer. - **A grep for the cost estimator (`estimate_cost`/`dry_run`/`CostEstimate`/`on_exceed`) found nothing in core/cli.** Either it is named differently or **it is not implemented.** Verify @@ -40,7 +40,7 @@ stale briefing. could lose data). - **Plugin availability** — declared plugins in `plugins.toml` resolve on `$PATH`/at their recorded path; the Python version requirement is satisfiable; manifest loads. Report, don't auto-install. -- **Config validation** — `clarion.yaml` parses against its schema; referenced env vars +- **Config validation** — `loomweave.yaml` parses against its schema; referenced env vars (`api_key_env`, `token_env`, …) are present; integration endpoints are well-formed. Report; `--fix` only for safe normalisations. @@ -72,7 +72,7 @@ refreshes — this is an honesty signal, not a mutation. pass/fail classification. - [ ] **T2 — doctor: plugin availability.** Resolve `plugins.toml` entries, version requirement, manifest load. Report-only. -- [ ] **T3 — doctor: config validation.** `clarion.yaml` schema + env-var presence + endpoint +- [ ] **T3 — doctor: config validation.** `loomweave.yaml` schema + env-var presence + endpoint well-formedness. Safe-normalisation `--fix` only. - [ ] **T4 — cost-estimate accuracy.** First resolve the §2 fork (estimator exists?). Then instrument estimated-vs-actual, close the two overestimation paths, emit an accuracy report, @@ -80,7 +80,7 @@ refreshes — this is an honesty signal, not a mutation. - [ ] **T5 — semantic-staleness surfacing.** Verify drift detection sets `stale_semantic` during analyze (fix if it never sets true); surface the flag on briefing/`summary` responses + a list/doctor view. Test that a drifted entity is flagged. -- [ ] **T6 — docs.** `clarion doctor` doc update; operator runbook for the new checks + the cost +- [ ] **T6 — docs.** `loomweave doctor` doc update; operator runbook for the new checks + the cost report; rule/finding catalogue if any new finding is emitted. ## 4. Hard boundaries — do NOT @@ -102,7 +102,7 @@ refreshes — this is an honesty signal, not a mutation. report makes the ±50% claim measured). ## 6. Definition of done (Wave 8 / WS8) -- `clarion doctor` checks DB health, plugin availability, and config validation, with conservative +- `loomweave doctor` checks DB health, plugin availability, and config validation, with conservative `--fix`, on the existing report model. - The cost estimate is *measured* against a real run and meets ±50% (NFR-COST-03), with the two overestimation paths closed and an accuracy report emitted — OR, if the estimator was unbuilt, it diff --git a/docs/superpowers/plans/archive/2026-05-29-clarion-agent-orientation.md b/docs/superpowers/plans/archive/2026-05-29-loomweave-agent-orientation.md similarity index 67% rename from docs/superpowers/plans/archive/2026-05-29-clarion-agent-orientation.md rename to docs/superpowers/plans/archive/2026-05-29-loomweave-agent-orientation.md index b84b92c9..ff655f6d 100644 --- a/docs/superpowers/plans/archive/2026-05-29-clarion-agent-orientation.md +++ b/docs/superpowers/plans/archive/2026-05-29-loomweave-agent-orientation.md @@ -1,33 +1,33 @@ -# Clarion Agent Orientation Implementation Plan +# Loomweave Agent Orientation Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. -**Goal:** Give Clarion "Filigree-parity" agent orientation — install a bundled `clarion-workflow` skill into a project, merge a SessionStart hook, serve a fail-soft project snapshot via `clarion hook session-start`, and add MCP `instructions` + `resources` (`clarion://context`) + a `clarion-workflow` prompt — so a consult-mode agent self-orients the moment it lands. +**Goal:** Give Loomweave "Filigree-parity" agent orientation — install a bundled `loomweave-workflow` skill into a project, merge a SessionStart hook, serve a fail-soft project snapshot via `loomweave hook session-start`, and add MCP `instructions` + `resources` (`loomweave://context`) + a `loomweave-workflow` prompt — so a consult-mode agent self-orients the moment it lands. -**Architecture:** A single embedded `SKILL.md` (already committed at `crates/clarion-cli/assets/skills/clarion-workflow/SKILL.md`) is compiled into the `clarion` binary with `include_str!`. New `install` flags (`--skills`, `--hooks`, `--all`) copy that pack atomically (temp-stage→rename) and merge a SessionStart hook into `.claude/settings.json` without clobbering existing keys. A new `clarion hook session-start` subcommand reads `.clarion/clarion.db` and prints a project snapshot; the snapshot logic is factored into one reusable function (`project_snapshot`) homed in `clarion-mcp`, which the MCP `clarion://context` resource also calls. The MCP `initialize` result gains static `instructions` and `prompts`+`resources` capabilities. +**Architecture:** A single embedded `SKILL.md` (already committed at `crates/loomweave-cli/assets/skills/loomweave-workflow/SKILL.md`) is compiled into the `loomweave` binary with `include_str!`. New `install` flags (`--skills`, `--hooks`, `--all`) copy that pack atomically (temp-stage→rename) and merge a SessionStart hook into `.claude/settings.json` without clobbering existing keys. A new `loomweave hook session-start` subcommand reads `.loomweave/loomweave.db` and prints a project snapshot; the snapshot logic is factored into one reusable function (`project_snapshot`) homed in `loomweave-mcp`, which the MCP `loomweave://context` resource also calls. The MCP `initialize` result gains static `instructions` and `prompts`+`resources` capabilities. -**Tech Stack:** Rust (clap, anyhow, serde_json, rusqlite, blake3, time), hand-rolled JSON-RPC MCP server (no rmcp), SQLite via `clarion-storage` `ReaderPool`, bash e2e harness. +**Tech Stack:** Rust (clap, anyhow, serde_json, rusqlite, blake3, time), hand-rolled JSON-RPC MCP server (no rmcp), SQLite via `loomweave-storage` `ReaderPool`, bash e2e harness. --- ## Decision Points -**(a) `include_str!` vs `include_dir`.** Use **`include_str!`** (one `const &str` per file). The pack is a single `SKILL.md` today; `include_str!` matches the existing convention (`crates/clarion-storage/src/schema.rs:20` embeds a migration `.sql` the same way), adds **zero new dependencies**, and stays clear of `cargo deny check`. Tradeoff: a future `references/` pack means adding one `const` per file plus one `(rel_path, contents)` tuple to a static slice — acceptable for a small, slowly-changing pack. We design the copier around a `&[(&str, &str)]` slice of `(relative_path, contents)` so growing the pack is a data change, not a logic change. +**(a) `include_str!` vs `include_dir`.** Use **`include_str!`** (one `const &str` per file). The pack is a single `SKILL.md` today; `include_str!` matches the existing convention (`crates/loomweave-storage/src/schema.rs:20` embeds a migration `.sql` the same way), adds **zero new dependencies**, and stays clear of `cargo deny check`. Tradeoff: a future `references/` pack means adding one `const` per file plus one `(rel_path, contents)` tuple to a static slice — acceptable for a small, slowly-changing pack. We design the copier around a `&[(&str, &str)]` slice of `(relative_path, contents)` so growing the pack is a data change, not a logic change. -**(b) Snapshot staleness — one bounded algorithm.** Order: (1) db missing → `db_present: false`, nudge "run `clarion analyze`"; (2) zero rows in `runs` with a non-null `completed_at` → `staleness: never_analyzed`, nudge; (3) otherwise take the latest run `completed_at`, parse it, and compare against the filesystem mtime of each **distinct `entities.source_file_path`** (the files Clarion actually ingested), short-circuiting on the first source file whose mtime is newer than the run → `staleness: stale`; if none are newer → `staleness: fresh`. Any FS/parse/IO error inside the staleness check degrades to `staleness: unknown` (never an error — the hook is fail-soft, always exit 0). Bounded by indexed-file count; respects exactly what was ingested. Tradeoff: a *new* source file not yet ingested won't be detected as drift (it has no `source_file_path` row) — acceptable, and the "run analyze" nudge on `never_analyzed`/`stale` covers the common case. +**(b) Snapshot staleness — one bounded algorithm.** Order: (1) db missing → `db_present: false`, nudge "run `loomweave analyze`"; (2) zero rows in `runs` with a non-null `completed_at` → `staleness: never_analyzed`, nudge; (3) otherwise take the latest run `completed_at`, parse it, and compare against the filesystem mtime of each **distinct `entities.source_file_path`** (the files Loomweave actually ingested), short-circuiting on the first source file whose mtime is newer than the run → `staleness: stale`; if none are newer → `staleness: fresh`. Any FS/parse/IO error inside the staleness check degrades to `staleness: unknown` (never an error — the hook is fail-soft, always exit 0). Bounded by indexed-file count; respects exactly what was ingested. Tradeoff: a *new* source file not yet ingested won't be detected as drift (it has no `source_file_path` row) — acceptable, and the "run analyze" nudge on `never_analyzed`/`stale` covers the common case. -**(c) Install flag semantics (resolved ambiguity).** Today **bare `clarion install` refuses if `.clarion/` exists**. So `--skills`/`--hooks` must NOT imply `.clarion/` init — they'd fail on every already-installed project (the common case for adding skills later). Resolution: -- **bare `clarion install`** = `.clarion/` init only (today's behavior, refuses-if-exists). All existing `crates/clarion-cli/tests/install.rs` tests pass unmodified. -- **`--skills`** = copy the skill pack (independent component; does NOT init `.clarion/`). -- **`--hooks`** = merge the SessionStart hook (independent component; does NOT init `.clarion/`). +**(c) Install flag semantics (resolved ambiguity).** Today **bare `loomweave install` refuses if `.loomweave/` exists**. So `--skills`/`--hooks` must NOT imply `.loomweave/` init — they'd fail on every already-installed project (the common case for adding skills later). Resolution: +- **bare `loomweave install`** = `.loomweave/` init only (today's behavior, refuses-if-exists). All existing `crates/loomweave-cli/tests/install.rs` tests pass unmodified. +- **`--skills`** = copy the skill pack (independent component; does NOT init `.loomweave/`). +- **`--hooks`** = merge the SessionStart hook (independent component; does NOT init `.loomweave/`). - **`--all`** = init + `--skills` + `--hooks`. - Flags compose: `--skills --hooks` does both, no init. When any component flag is present, the bare init runs only if `--all` is set. -**(d) Snapshot function signature.** `pub fn project_snapshot(conn: &rusqlite::Connection, project_root: &Path) -> ProjectSnapshot` (infallible — folds errors into the snapshot's `staleness`/counts). Homed in **`clarion-mcp`** (`crates/clarion-mcp/src/snapshot.rs`): the CLI already depends on `clarion-mcp`, and the MCP `clarion://context` resource lives right next to it. Both callers have a `&Connection` and a `project_root` (the hook opens a read-only `Connection`; MCP runs it inside `readers.with_reader(move |conn| ...)` with `project_root` cloned into the `'static` closure). `ProjectSnapshot` is owned `Send + 'static`. +**(d) Snapshot function signature.** `pub fn project_snapshot(conn: &rusqlite::Connection, project_root: &Path) -> ProjectSnapshot` (infallible — folds errors into the snapshot's `staleness`/counts). Homed in **`loomweave-mcp`** (`crates/loomweave-mcp/src/snapshot.rs`): the CLI already depends on `loomweave-mcp`, and the MCP `loomweave://context` resource lives right next to it. Both callers have a `&Connection` and a `project_root` (the hook opens a read-only `Connection`; MCP runs it inside `readers.with_reader(move |conn| ...)` with `project_root` cloned into the `'static` closure). `ProjectSnapshot` is owned `Send + 'static`. **(e) Dogfood product gaps — DOCUMENT, do not fix here.** Two real gaps surfaced during dogfooding: `find_entity` has no `kind` filter, and there is no module→subsystem reverse lookup. The committed `SKILL.md` already documents both (lines 73–75). For this slice: **file each as a child of Filigree epic `clarion-8fe3060d4c`** (see "Filigree bookkeeping" task), no fix-tasks. Expanding the MCP tool surface is out of scope. -**(f) MCP prompt is the lowest-value piece (optional).** `prompts/list` + `prompts/get` for `clarion-workflow` returns the *same* `SKILL.md` text the installed skill already provides, so it duplicates the on-disk skill. It is included for MCP-client completeness (clients that surface prompts but not skills) but is the first thing to cut under time pressure. Implement it last (Phase 5 Task 5.5) and treat it as droppable. +**(f) MCP prompt is the lowest-value piece (optional).** `prompts/list` + `prompts/get` for `loomweave-workflow` returns the *same* `SKILL.md` text the installed skill already provides, so it duplicates the on-disk skill. It is included for MCP-client completeness (clients that surface prompts but not skills) but is the first thing to cut under time pressure. Implement it last (Phase 5 Task 5.5) and treat it as droppable. --- @@ -35,19 +35,19 @@ | File | Create/Modify | Single responsibility | |------|---------------|-----------------------| -| `crates/clarion-cli/assets/skills/clarion-workflow/SKILL.md` | Exists (committed) | The bundled skill text. Source of truth for both the installed pack and the MCP prompt. | -| `crates/clarion-cli/src/skill_pack.rs` | Create | Embeds the pack via `include_str!`, exposes `SKILL_PACK: &[(&str, &str)]`, `pack_fingerprint()`, and `install_skill_pack(dest_root)` (atomic, fingerprint-aware copy into both `.claude/skills/clarion-workflow/` and `.agents/skills/clarion-workflow/`). | -| `crates/clarion-cli/src/install.rs` | Modify (`run`, add `InstallComponents`) | Orchestrates init + skills + hooks per the resolved flag semantics. | -| `crates/clarion-cli/src/hooks_settings.rs` | Create | Pure `.claude/settings.json` SessionStart-hook merge logic (parse → add-if-absent → serialize) + the file-level `install_session_start_hook(dest_root)`. | -| `crates/clarion-cli/src/hook.rs` | Create | `clarion hook session-start` handler: re-sync skill on drift, open read-only db `Connection`, call `project_snapshot`, print a snapshot + nudge to stdout. Always returns `Ok(())` (fail-soft). | -| `crates/clarion-cli/src/cli.rs` | Modify (`Install` variant ~14, add `Hook`) | CLI surface: new install flags + `Hook { SessionStart }` subcommand. | -| `crates/clarion-cli/src/main.rs` | Modify (dispatch ~28) | Wire `Install` flags and `Hook` into the dispatch match. | -| `crates/clarion-cli/src/serve.rs` | Modify (`run_mcp_stdio` ~107) | Pass `project_root` to MCP state (already passed) — no change needed beyond confirming; the resource reads via `readers`. | -| `crates/clarion-mcp/src/snapshot.rs` | Create | `ProjectSnapshot` struct + `project_snapshot(conn, project_root)` shared function. | -| `crates/clarion-mcp/src/lib.rs` | Modify (`initialize_result` ~2059, `ServerState::handle_json_rpc` ~255, add `mod snapshot`, embed `SKILL.md`) | `instructions` + `prompts`/`resources` capabilities; new RPC arms for `prompts/list|get`, `resources/list|read`. | -| `crates/clarion-cli/tests/skills.rs` | Create | Integration tests for `install --skills`, `--hooks`, `--all` flag semantics and idempotency. | -| `crates/clarion-cli/tests/hook.rs` | Create | Integration tests for `clarion hook session-start` (fail-soft, snapshot output). | -| `tests/e2e/sprint_2_mcp_surface.sh` | Modify (assertions after line 379) | Assert `initialize` `instructions` field and a `resources/read` of `clarion://context`. | +| `crates/loomweave-cli/assets/skills/loomweave-workflow/SKILL.md` | Exists (committed) | The bundled skill text. Source of truth for both the installed pack and the MCP prompt. | +| `crates/loomweave-cli/src/skill_pack.rs` | Create | Embeds the pack via `include_str!`, exposes `SKILL_PACK: &[(&str, &str)]`, `pack_fingerprint()`, and `install_skill_pack(dest_root)` (atomic, fingerprint-aware copy into both `.claude/skills/loomweave-workflow/` and `.agents/skills/loomweave-workflow/`). | +| `crates/loomweave-cli/src/install.rs` | Modify (`run`, add `InstallComponents`) | Orchestrates init + skills + hooks per the resolved flag semantics. | +| `crates/loomweave-cli/src/hooks_settings.rs` | Create | Pure `.claude/settings.json` SessionStart-hook merge logic (parse → add-if-absent → serialize) + the file-level `install_session_start_hook(dest_root)`. | +| `crates/loomweave-cli/src/hook.rs` | Create | `loomweave hook session-start` handler: re-sync skill on drift, open read-only db `Connection`, call `project_snapshot`, print a snapshot + nudge to stdout. Always returns `Ok(())` (fail-soft). | +| `crates/loomweave-cli/src/cli.rs` | Modify (`Install` variant ~14, add `Hook`) | CLI surface: new install flags + `Hook { SessionStart }` subcommand. | +| `crates/loomweave-cli/src/main.rs` | Modify (dispatch ~28) | Wire `Install` flags and `Hook` into the dispatch match. | +| `crates/loomweave-cli/src/serve.rs` | Modify (`run_mcp_stdio` ~107) | Pass `project_root` to MCP state (already passed) — no change needed beyond confirming; the resource reads via `readers`. | +| `crates/loomweave-mcp/src/snapshot.rs` | Create | `ProjectSnapshot` struct + `project_snapshot(conn, project_root)` shared function. | +| `crates/loomweave-mcp/src/lib.rs` | Modify (`initialize_result` ~2059, `ServerState::handle_json_rpc` ~255, add `mod snapshot`, embed `SKILL.md`) | `instructions` + `prompts`/`resources` capabilities; new RPC arms for `prompts/list|get`, `resources/list|read`. | +| `crates/loomweave-cli/tests/skills.rs` | Create | Integration tests for `install --skills`, `--hooks`, `--all` flag semantics and idempotency. | +| `crates/loomweave-cli/tests/hook.rs` | Create | Integration tests for `loomweave hook session-start` (fail-soft, snapshot output). | +| `tests/e2e/sprint_2_mcp_surface.sh` | Modify (assertions after line 379) | Assert `initialize` `instructions` field and a `resources/read` of `loomweave://context`. | --- @@ -55,28 +55,28 @@ The plan assumes the crate facts below. Confirm each against the real files first; if one differs, adjust the citing task before writing its code. Each is a hard compile blocker if wrong, so catch them up front rather than mid-task. -- **`clarion-cli` deps:** `skill_pack.rs` (Task 1.1) uses `blake3`; `skill_pack.rs`/`install.rs` use `anyhow`. Confirm both are in `crates/clarion-cli/Cargo.toml` `[dependencies]`. If `blake3` is absent, add `blake3.workspace = true` (the workspace pin already exists — it's a `clarion-mcp` dep) and `git add` the Cargo.toml with Task 1.1. -- **`clarion-cli` → `clarion-mcp` dependency:** `hook.rs` (Task 3.2) imports `clarion_mcp::snapshot::*`. Confirm `crates/clarion-cli/Cargo.toml` depends on `clarion-mcp` (it should — `serve.rs` uses `clarion_mcp::ServerState`). If absent, add it with Task 3.2. -- **`clarion-cli` dev-deps:** `tests/skills.rs` / `tests/hook.rs` use `assert_cmd` + `tempfile`. Confirm both are `[dev-dependencies]` (the existing `tests/install.rs` uses them; if it does, they're present). +- **`loomweave-cli` deps:** `skill_pack.rs` (Task 1.1) uses `blake3`; `skill_pack.rs`/`install.rs` use `anyhow`. Confirm both are in `crates/loomweave-cli/Cargo.toml` `[dependencies]`. If `blake3` is absent, add `blake3.workspace = true` (the workspace pin already exists — it's a `loomweave-mcp` dep) and `git add` the Cargo.toml with Task 1.1. +- **`loomweave-cli` → `loomweave-mcp` dependency:** `hook.rs` (Task 3.2) imports `loomweave_mcp::snapshot::*`. Confirm `crates/loomweave-cli/Cargo.toml` depends on `loomweave-mcp` (it should — `serve.rs` uses `loomweave_mcp::ServerState`). If absent, add it with Task 3.2. +- **`loomweave-cli` dev-deps:** `tests/skills.rs` / `tests/hook.rs` use `assert_cmd` + `tempfile`. Confirm both are `[dev-dependencies]` (the existing `tests/install.rs` uses them; if it does, they're present). - **`ServerState` shape (Phase 5):** confirm the constructor is `ServerState::new(project_root: PathBuf, readers: ReaderPool)` and the struct has fields `project_root` + `readers`, and that `self.readers.with_reader(move |conn| Ok(..)).await` returns `Result`. If the names/signature differ, adjust Task 5.2's `context_snapshot_json` and the Phase-5 tests' `ServerState::new(...)` calls to match. (Recon: serve.rs builds `ServerState::new(...)` then `.with_summary_llm(..)`/`.with_filigree_client(..)`.) - **`runs.completed_at` format:** `parse_iso8601_to_systemtime` (Task 2.1) parses with `Rfc3339`. Confirm the column is RFC3339 (e.g. `2026-05-29T12:00:00.000Z`). If it's a space-separated SQLite form (`2026-05-29 12:00:00`), swap to a matching `time` `format_description!` — and update the Phase-2 test fixtures to the real format. --- -## PHASE 1 — Embedding + `clarion install --skills` +## PHASE 1 — Embedding + `loomweave install --skills` Atomic, fingerprint-aware copy of the bundled skill pack. Independently testable: produces the two on-disk skill copies. ### Task 1.1: Embed the skill pack and compute a fingerprint **Files:** -- Create: `crates/clarion-cli/src/skill_pack.rs` -- Modify: `crates/clarion-cli/src/main.rs` (add `mod skill_pack;`) -- Test: inline `#[cfg(test)]` in `crates/clarion-cli/src/skill_pack.rs` +- Create: `crates/loomweave-cli/src/skill_pack.rs` +- Modify: `crates/loomweave-cli/src/main.rs` (add `mod skill_pack;`) +- Test: inline `#[cfg(test)]` in `crates/loomweave-cli/src/skill_pack.rs` - [ ] **Step 1: Write the failing test** -Add to the bottom of the new file `crates/clarion-cli/src/skill_pack.rs`: +Add to the bottom of the new file `crates/loomweave-cli/src/skill_pack.rs`: ```rust #[cfg(test)] @@ -91,7 +91,7 @@ mod tests { .expect("SKILL.md present in pack"); assert_eq!(*rel, "SKILL.md"); assert!( - contents.contains("name: clarion-workflow"), + contents.contains("name: loomweave-workflow"), "SKILL.md is missing its frontmatter name" ); } @@ -109,19 +109,19 @@ mod tests { - [ ] **Step 2: Run test to verify it fails** -Run: `cargo nextest run -p clarion-cli skill_pack` +Run: `cargo nextest run -p loomweave-cli skill_pack` Expected: FAIL — `skill_pack.rs` does not yet define `SKILL_PACK`/`pack_fingerprint` (compile error: unresolved import / module not found until `mod skill_pack;` and the items exist). - [ ] **Step 3: Write minimal implementation** -Top of `crates/clarion-cli/src/skill_pack.rs`: +Top of `crates/loomweave-cli/src/skill_pack.rs`: ```rust -//! Embedded `clarion-workflow` skill pack and its on-disk installer. +//! Embedded `loomweave-workflow` skill pack and its on-disk installer. //! //! The pack is compiled into the binary with `include_str!` (matching the //! `include_str!` migration-embedding convention in -//! `clarion-storage/src/schema.rs`). Each entry is `(relative_path, contents)`; +//! `loomweave-storage/src/schema.rs`). Each entry is `(relative_path, contents)`; //! growing the pack with a `references/` directory is a data change here, not a //! logic change. The fingerprint over the pack bytes drives drift-aware //! re-copy (Phase 3 hook resync + `--skills` idempotency). @@ -129,11 +129,11 @@ Top of `crates/clarion-cli/src/skill_pack.rs`: /// `(relative_path, contents)` for every file in the bundled skill pack. pub const SKILL_PACK: &[(&str, &str)] = &[( "SKILL.md", - include_str!("../assets/skills/clarion-workflow/SKILL.md"), + include_str!("../assets/skills/loomweave-workflow/SKILL.md"), )]; /// The on-disk subdirectory name the pack installs into. -pub const PACK_DIR_NAME: &str = "clarion-workflow"; +pub const PACK_DIR_NAME: &str = "loomweave-workflow"; /// Deterministic blake3 hex digest over the pack's `(rel_path, contents)` /// entries. Order-stable because `SKILL_PACK` is a fixed slice. Used as the @@ -152,7 +152,7 @@ pub fn pack_fingerprint() -> String { } ``` -Add to `crates/clarion-cli/src/main.rs` after `mod secret_scan;` (keep modules alphabetical-ish; placement is cosmetic): +Add to `crates/loomweave-cli/src/main.rs` after `mod secret_scan;` (keep modules alphabetical-ish; placement is cosmetic): ```rust mod skill_pack; @@ -160,25 +160,25 @@ mod skill_pack; - [ ] **Step 4: Run test to verify it passes** -Run: `cargo nextest run -p clarion-cli skill_pack` +Run: `cargo nextest run -p loomweave-cli skill_pack` Expected: PASS (both tests green). - [ ] **Step 5: Commit** ```bash -git add crates/clarion-cli/src/skill_pack.rs crates/clarion-cli/src/main.rs -git commit -m "feat(cli): embed clarion-workflow skill pack with blake3 fingerprint" +git add crates/loomweave-cli/src/skill_pack.rs crates/loomweave-cli/src/main.rs +git commit -m "feat(cli): embed loomweave-workflow skill pack with blake3 fingerprint" ``` ### Task 1.2: Atomic, fingerprint-aware pack installer **Files:** -- Modify: `crates/clarion-cli/src/skill_pack.rs` (add `install_skill_pack`) -- Test: inline `#[cfg(test)]` in `crates/clarion-cli/src/skill_pack.rs` +- Modify: `crates/loomweave-cli/src/skill_pack.rs` (add `install_skill_pack`) +- Test: inline `#[cfg(test)]` in `crates/loomweave-cli/src/skill_pack.rs` - [ ] **Step 1: Write the failing test** -Add these tests inside the existing `mod tests` in `crates/clarion-cli/src/skill_pack.rs`: +Add these tests inside the existing `mod tests` in `crates/loomweave-cli/src/skill_pack.rs`: ```rust #[test] @@ -192,11 +192,11 @@ fn install_writes_pack_into_both_skill_roots() { let skill = dir .path() .join(root) - .join("clarion-workflow") + .join("loomweave-workflow") .join("SKILL.md"); assert!(skill.exists(), "missing {}", skill.display()); let body = std::fs::read_to_string(&skill).unwrap(); - assert!(body.contains("name: clarion-workflow")); + assert!(body.contains("name: loomweave-workflow")); } } @@ -218,28 +218,28 @@ fn install_recopies_when_installed_pack_drifted() { // Corrupt one installed copy + its fingerprint to simulate drift. let skill = dir .path() - .join(".claude/skills/clarion-workflow/SKILL.md"); + .join(".claude/skills/loomweave-workflow/SKILL.md"); std::fs::write(&skill, "STALE").unwrap(); let fp = dir .path() - .join(".claude/skills/clarion-workflow/.fingerprint"); + .join(".claude/skills/loomweave-workflow/.fingerprint"); std::fs::write(&fp, "deadbeef").unwrap(); let report = install_skill_pack(dir.path()).unwrap(); assert!(report.copied, "drift should trigger re-copy"); let body = std::fs::read_to_string(&skill).unwrap(); - assert!(body.contains("name: clarion-workflow"), "drift not repaired"); + assert!(body.contains("name: loomweave-workflow"), "drift not repaired"); } ``` - [ ] **Step 2: Run test to verify it fails** -Run: `cargo nextest run -p clarion-cli skill_pack` +Run: `cargo nextest run -p loomweave-cli skill_pack` Expected: FAIL — `install_skill_pack` and its `SkillInstallReport` are not defined (compile error). - [ ] **Step 3: Write minimal implementation** -Add to `crates/clarion-cli/src/skill_pack.rs` (above the `#[cfg(test)]` block): +Add to `crates/loomweave-cli/src/skill_pack.rs` (above the `#[cfg(test)]` block): ```rust use std::fs; @@ -247,7 +247,7 @@ use std::path::Path; use anyhow::{Context, Result}; -/// The two skill roots Clarion installs into, relative to the project root. +/// The two skill roots Loomweave installs into, relative to the project root. /// `.claude/skills/` is read by Claude Code; `.agents/skills/` is the /// tool-agnostic convention so non-Claude agent harnesses find it too. const SKILL_ROOTS: &[&str] = &[".claude/skills", ".agents/skills"]; @@ -265,7 +265,7 @@ pub struct SkillInstallReport { /// Install (or re-sync on drift) the embedded skill pack into both skill roots /// under `project_root`, idempotently. /// -/// For each root, the pack lands at `/clarion-workflow/`. A +/// For each root, the pack lands at `/loomweave-workflow/`. A /// `.fingerprint` file recording [`pack_fingerprint`] is written alongside the /// pack files. If every root's `.fingerprint` already equals the embedded /// fingerprint, the call is a no-op (`copied: false`). @@ -302,7 +302,7 @@ fn installed_fingerprint(dest: &Path) -> Option { fn stage_and_swap(root: &Path, dest: &Path, fingerprint: &str) -> Result<()> { fs::create_dir_all(root).with_context(|| format!("mkdir {}", root.display()))?; // Stage in a sibling temp dir so the final rename is same-filesystem. - let staging = root.join(format!(".clarion-workflow.tmp-{}", std::process::id())); + let staging = root.join(format!(".loomweave-workflow.tmp-{}", std::process::id())); if staging.exists() { fs::remove_dir_all(&staging) .with_context(|| format!("clear stale staging {}", staging.display()))?; @@ -330,43 +330,43 @@ fn stage_and_swap(root: &Path, dest: &Path, fingerprint: &str) -> Result<()> { - [ ] **Step 4: Run test to verify it passes** -Run: `cargo nextest run -p clarion-cli skill_pack` +Run: `cargo nextest run -p loomweave-cli skill_pack` Expected: PASS (all five tests green). - [ ] **Step 5: Commit** ```bash -git add crates/clarion-cli/src/skill_pack.rs +git add crates/loomweave-cli/src/skill_pack.rs git commit -m "feat(cli): atomic fingerprint-aware skill-pack installer" ``` -### Task 1.3: `clarion install --skills` CLI flag +### Task 1.3: `loomweave install --skills` CLI flag **Files:** -- Modify: `crates/clarion-cli/src/cli.rs` (`Install` variant ~14-23) -- Modify: `crates/clarion-cli/src/install.rs` (`run` signature + `InstallComponents`) -- Modify: `crates/clarion-cli/src/main.rs` (dispatch ~29) -- Test: `crates/clarion-cli/tests/skills.rs` (create) +- Modify: `crates/loomweave-cli/src/cli.rs` (`Install` variant ~14-23) +- Modify: `crates/loomweave-cli/src/install.rs` (`run` signature + `InstallComponents`) +- Modify: `crates/loomweave-cli/src/main.rs` (dispatch ~29) +- Test: `crates/loomweave-cli/tests/skills.rs` (create) - [ ] **Step 1: Write the failing test** -Create `crates/clarion-cli/tests/skills.rs`: +Create `crates/loomweave-cli/tests/skills.rs`: ```rust -//! `clarion install --skills/--hooks/--all` integration tests. +//! `loomweave install --skills/--hooks/--all` integration tests. use std::fs; use assert_cmd::Command; -fn clarion_bin() -> Command { - Command::cargo_bin("clarion").expect("clarion binary") +fn loomweave_bin() -> Command { + Command::cargo_bin("loomweave").expect("loomweave binary") } #[test] -fn install_skills_writes_pack_without_initialising_clarion_dir() { +fn install_skills_writes_pack_without_initialising_loomweave_dir() { let dir = tempfile::tempdir().unwrap(); - clarion_bin() + loomweave_bin() .args(["install", "--skills", "--path"]) .arg(dir.path()) .assert() @@ -374,20 +374,20 @@ fn install_skills_writes_pack_without_initialising_clarion_dir() { assert!( dir.path() - .join(".claude/skills/clarion-workflow/SKILL.md") + .join(".claude/skills/loomweave-workflow/SKILL.md") .exists(), "skill not installed under .claude" ); assert!( dir.path() - .join(".agents/skills/clarion-workflow/SKILL.md") + .join(".agents/skills/loomweave-workflow/SKILL.md") .exists(), "skill not installed under .agents" ); - // --skills MUST NOT init .clarion/. + // --skills MUST NOT init .loomweave/. assert!( - !dir.path().join(".clarion").exists(), - "--skills should not create .clarion/" + !dir.path().join(".loomweave").exists(), + "--skills should not create .loomweave/" ); } @@ -395,38 +395,38 @@ fn install_skills_writes_pack_without_initialising_clarion_dir() { fn install_skills_is_idempotent() { let dir = tempfile::tempdir().unwrap(); for _ in 0..2 { - clarion_bin() + loomweave_bin() .args(["install", "--skills", "--path"]) .arg(dir.path()) .assert() .success(); } let body = fs::read_to_string( - dir.path().join(".claude/skills/clarion-workflow/SKILL.md"), + dir.path().join(".claude/skills/loomweave-workflow/SKILL.md"), ) .unwrap(); - assert!(body.contains("name: clarion-workflow")); + assert!(body.contains("name: loomweave-workflow")); } ``` - [ ] **Step 2: Run test to verify it fails** -Run: `cargo nextest run -p clarion-cli --test skills` +Run: `cargo nextest run -p loomweave-cli --test skills` Expected: FAIL — `--skills` is an unknown argument (clap errors with a nonzero exit; `assert().success()` fails). - [ ] **Step 3: Write minimal implementation** -In `crates/clarion-cli/src/cli.rs`, replace the `Install` variant (lines ~14-23) with: +In `crates/loomweave-cli/src/cli.rs`, replace the `Install` variant (lines ~14-23) with: ```rust - /// Initialise .clarion/ and/or install agent-orientation assets. + /// Initialise .loomweave/ and/or install agent-orientation assets. /// - /// Bare `clarion install` initialises .clarion/ only (refuses if it + /// Bare `loomweave install` initialises .loomweave/ only (refuses if it /// already exists). `--skills` and `--hooks` install the orientation - /// assets and do NOT initialise .clarion/. `--all` does init + skills + + /// assets and do NOT initialise .loomweave/. `--all` does init + skills + /// hooks. Install { - /// Overwrite an existing .clarion/ directory. + /// Overwrite an existing .loomweave/ directory. #[arg(long)] force: bool, @@ -434,7 +434,7 @@ In `crates/clarion-cli/src/cli.rs`, replace the `Install` variant (lines ~14-23) #[arg(long, default_value = ".")] path: PathBuf, - /// Install the bundled clarion-workflow skill pack into + /// Install the bundled loomweave-workflow skill pack into /// .claude/skills/ and .agents/skills/. #[arg(long)] skills: bool, @@ -443,13 +443,13 @@ In `crates/clarion-cli/src/cli.rs`, replace the `Install` variant (lines ~14-23) #[arg(long)] hooks: bool, - /// Do everything: .clarion/ init + --skills + --hooks. + /// Do everything: .loomweave/ init + --skills + --hooks. #[arg(long)] all: bool, }, ``` -In `crates/clarion-cli/src/install.rs`, replace the `pub fn run(path: &Path, force: bool)` signature and body opening. First add this struct + helper above `run` (after the `const` stubs, before the `/// Run the install subcommand` doc comment): +In `crates/loomweave-cli/src/install.rs`, replace the `pub fn run(path: &Path, force: bool)` signature and body opening. First add this struct + helper above `run` (after the `const` stubs, before the `/// Run the install subcommand` doc comment): ```rust /// Which install components to perform. Resolved from CLI flags in @@ -458,7 +458,7 @@ In `crates/clarion-cli/src/install.rs`, replace the `pub fn run(path: &Path, for /// independent and do NOT init; `--all` = init + skills + hooks. #[derive(Debug, Clone, Copy)] pub struct InstallComponents { - pub init_clarion: bool, + pub init_loomweave: bool, pub skills: bool, pub hooks: bool, } @@ -468,7 +468,7 @@ impl InstallComponents { pub fn from_flags(skills: bool, hooks: bool, all: bool) -> Self { if all { return Self { - init_clarion: true, + init_loomweave: true, skills: true, hooks: true, }; @@ -476,7 +476,7 @@ impl InstallComponents { let any_component = skills || hooks; Self { // Bare install (no component flags) keeps today's behavior: init. - init_clarion: !any_component, + init_loomweave: !any_component, skills, hooks, } @@ -498,38 +498,38 @@ pub fn run(path: &Path, force: bool, components: InstallComponents) -> Result<() if !path.exists() { ``` -Then, before the existing `let project_root = path.canonicalize()...` block, the canonicalization is still needed by all branches. Keep it. After the `let project_root = ...` line, wrap the `.clarion/` work in the `init_clarion` guard. Replace the block from `let clarion_dir = project_root.join(".clarion");` down through the final `println!("Initialised {}", clarion_dir.display());\n Ok(())\n}` with: +Then, before the existing `let project_root = path.canonicalize()...` block, the canonicalization is still needed by all branches. Keep it. After the `let project_root = ...` line, wrap the `.loomweave/` work in the `init_loomweave` guard. Replace the block from `let loomweave_dir = project_root.join(".loomweave");` down through the final `println!("Initialised {}", loomweave_dir.display());\n Ok(())\n}` with: ```rust - if components.init_clarion { - let clarion_dir = project_root.join(".clarion"); - if clarion_dir.exists() { + if components.init_loomweave { + let loomweave_dir = project_root.join(".loomweave"); + if loomweave_dir.exists() { if !force { bail!( - ".clarion/ already exists at {}. Delete it or pass --force to overwrite it.", - clarion_dir.display() + ".loomweave/ already exists at {}. Delete it or pass --force to overwrite it.", + loomweave_dir.display() ); } - if !clarion_dir.is_dir() { + if !loomweave_dir.is_dir() { bail!( - "--force can only overwrite an existing .clarion/ directory; \ + "--force can only overwrite an existing .loomweave/ directory; \ found non-directory at {}.", - clarion_dir.display() + loomweave_dir.display() ); } - fs::remove_dir_all(&clarion_dir) - .with_context(|| format!("remove existing {}", clarion_dir.display()))?; + fs::remove_dir_all(&loomweave_dir) + .with_context(|| format!("remove existing {}", loomweave_dir.display()))?; } - fs::create_dir_all(&clarion_dir) - .with_context(|| format!("mkdir {}", clarion_dir.display()))?; + fs::create_dir_all(&loomweave_dir) + .with_context(|| format!("mkdir {}", loomweave_dir.display()))?; - if let Err(err) = populate_after_mkdir(&clarion_dir, &project_root) { - if let Err(cleanup_err) = fs::remove_dir_all(&clarion_dir) { + if let Err(err) = populate_after_mkdir(&loomweave_dir, &project_root) { + if let Err(cleanup_err) = fs::remove_dir_all(&loomweave_dir) { tracing::warn!( - clarion_dir = %clarion_dir.display(), + loomweave_dir = %loomweave_dir.display(), error = %cleanup_err, - "install failed and cleanup of partial .clarion/ also failed; \ + "install failed and cleanup of partial .loomweave/ also failed; \ manual rm -rf may be required" ); } @@ -537,23 +537,23 @@ Then, before the existing `let project_root = path.canonicalize()...` block, the } tracing::info!( - clarion_dir = %clarion_dir.display(), - "clarion install complete" + loomweave_dir = %loomweave_dir.display(), + "loomweave install complete" ); - println!("Initialised {}", clarion_dir.display()); + println!("Initialised {}", loomweave_dir.display()); } if components.skills { let report = crate::skill_pack::install_skill_pack(&project_root) - .context("install clarion-workflow skill pack")?; + .context("install loomweave-workflow skill pack")?; if report.copied { println!( - "Installed clarion-workflow skill into {}/.claude/skills and {}/.agents/skills", + "Installed loomweave-workflow skill into {}/.claude/skills and {}/.agents/skills", project_root.display(), project_root.display() ); } else { - println!("clarion-workflow skill already up to date"); + println!("loomweave-workflow skill already up to date"); } } @@ -563,7 +563,7 @@ Then, before the existing `let project_root = path.canonicalize()...` block, the } ``` -In `crates/clarion-cli/src/main.rs`, change the `Install` match arm (line ~29) from: +In `crates/loomweave-cli/src/main.rs`, change the `Install` match arm (line ~29) from: ```rust cli::Command::Install { force, path } => install::run(&path, force), @@ -587,23 +587,23 @@ to: - [ ] **Step 4: Run test to verify it passes** -Run: `cargo nextest run -p clarion-cli --test skills` +Run: `cargo nextest run -p loomweave-cli --test skills` Expected: PASS. Also run the existing install tests to prove bare-install behavior is unchanged: -Run: `cargo nextest run -p clarion-cli --test install` -Expected: PASS (all existing tests green — `from_flags(false, false, false)` sets `init_clarion: true`). +Run: `cargo nextest run -p loomweave-cli --test install` +Expected: PASS (all existing tests green — `from_flags(false, false, false)` sets `init_loomweave: true`). - [ ] **Step 5: Commit** ```bash -git add crates/clarion-cli/src/cli.rs crates/clarion-cli/src/install.rs crates/clarion-cli/src/main.rs crates/clarion-cli/tests/skills.rs -git commit -m "feat(cli): clarion install --skills installs orientation skill pack" +git add crates/loomweave-cli/src/cli.rs crates/loomweave-cli/src/install.rs crates/loomweave-cli/src/main.rs crates/loomweave-cli/tests/skills.rs +git commit -m "feat(cli): loomweave install --skills installs orientation skill pack" ``` ### Task 1.4: Phase 1 gate - [ ] **Step 1: Run the full Rust gate for the crate** -Run: `cargo fmt --all -- --check && cargo clippy -p clarion-cli --all-targets --all-features -- -D warnings && cargo nextest run -p clarion-cli` +Run: `cargo fmt --all -- --check && cargo clippy -p loomweave-cli --all-targets --all-features -- -D warnings && cargo nextest run -p loomweave-cli` Expected: PASS, no warnings. - [ ] **Step 2: Commit any fmt/clippy fixes (only if the gate changed files)** @@ -617,25 +617,25 @@ git commit -m "style(cli): fmt/clippy after skill-pack phase" ## PHASE 2 — Shared snapshot module -A pure-ish function over a `&Connection` + `project_root`, unit-tested against a fixture db. Homed in `clarion-mcp`. +A pure-ish function over a `&Connection` + `project_root`, unit-tested against a fixture db. Homed in `loomweave-mcp`. ### Task 2.1: `ProjectSnapshot` + counts (no staleness yet) **Files:** -- Create: `crates/clarion-mcp/src/snapshot.rs` -- Modify: `crates/clarion-mcp/src/lib.rs` (add `pub mod snapshot;`) -- Test: inline `#[cfg(test)]` in `crates/clarion-mcp/src/snapshot.rs` +- Create: `crates/loomweave-mcp/src/snapshot.rs` +- Modify: `crates/loomweave-mcp/src/lib.rs` (add `pub mod snapshot;`) +- Test: inline `#[cfg(test)]` in `crates/loomweave-mcp/src/snapshot.rs` - [ ] **Step 1: Write the failing test** -Create `crates/clarion-mcp/src/snapshot.rs` with this test block at the bottom: +Create `crates/loomweave-mcp/src/snapshot.rs` with this test block at the bottom: ```rust #[cfg(test)] mod tests { use rusqlite::Connection; - use clarion_storage::{pragma, schema}; + use loomweave_storage::{pragma, schema}; use super::{Staleness, project_snapshot}; @@ -673,7 +673,7 @@ mod tests { "INSERT INTO findings \ (id, tool, tool_version, run_id, rule_id, kind, severity, entity_id, \ related_entities, message, evidence, properties, supports, supported_by, status, created_at, updated_at) \ - VALUES ('f1','clarion','1.0','run1','R1','defect','WARN','python:module:a', \ + VALUES ('f1','loomweave','1.0','run1','R1','defect','WARN','python:module:a', \ '[]','m','{}','{}','[]','[]','open','2026-01-01T00:00:00.000Z','2026-01-01T00:00:00.000Z')", [], ) @@ -694,18 +694,18 @@ mod tests { - [ ] **Step 2: Run test to verify it fails** -Run: `cargo nextest run -p clarion-mcp snapshot` +Run: `cargo nextest run -p loomweave-mcp snapshot` Expected: FAIL — module/items not defined (compile error until `pub mod snapshot;` + the items exist). - [ ] **Step 3: Write minimal implementation** -Top of `crates/clarion-mcp/src/snapshot.rs`: +Top of `crates/loomweave-mcp/src/snapshot.rs`: ```rust //! Shared project snapshot: entity/subsystem/finding counts + index staleness. //! -//! One function, two callers: the `clarion hook session-start` subcommand and -//! the MCP `clarion://context` resource. Infallible by design — every failure +//! One function, two callers: the `loomweave hook session-start` subcommand and +//! the MCP `loomweave://context` resource. Infallible by design — every failure //! folds into the snapshot (zero counts, `Staleness::Unknown`) so the fail-soft //! hook never has to handle an error. @@ -715,7 +715,7 @@ use std::time::SystemTime; use rusqlite::Connection; use serde::Serialize; -/// Freshness of the `.clarion/` index relative to the source files Clarion +/// Freshness of the `.loomweave/` index relative to the source files Loomweave /// ingested. See the plan's Decision Point (b) for the algorithm. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] #[serde(rename_all = "snake_case")] @@ -730,7 +730,7 @@ pub enum Staleness { Unknown, } -/// Counts + freshness for one Clarion project, safe to serialize into the MCP +/// Counts + freshness for one Loomweave project, safe to serialize into the MCP /// resource or print from the hook. #[derive(Debug, Clone, Serialize)] pub struct ProjectSnapshot { @@ -810,7 +810,7 @@ fn compute_staleness( return Staleness::Unknown; }; - // Distinct source files Clarion actually ingested. + // Distinct source files Loomweave actually ingested. let mut stmt = match conn.prepare( "SELECT DISTINCT source_file_path FROM entities \ WHERE source_file_path IS NOT NULL", @@ -859,23 +859,23 @@ fn parse_iso8601_to_systemtime(iso: &str) -> Option { } ``` -Add to `crates/clarion-mcp/src/lib.rs` near the other `pub mod` lines (top of file, after `pub mod filigree;`): +Add to `crates/loomweave-mcp/src/lib.rs` near the other `pub mod` lines (top of file, after `pub mod filigree;`): ```rust pub mod snapshot; ``` -Confirm `time` is a dependency of `clarion-mcp` (it is — `lib.rs` already `use time::...`). Confirm `serde` is too (it is — `use serde::{Deserialize, Serialize};`). +Confirm `time` is a dependency of `loomweave-mcp` (it is — `lib.rs` already `use time::...`). Confirm `serde` is too (it is — `use serde::{Deserialize, Serialize};`). - [ ] **Step 4: Run test to verify it passes** -Run: `cargo nextest run -p clarion-mcp snapshot` +Run: `cargo nextest run -p loomweave-mcp snapshot` Expected: PASS. - [ ] **Step 5: Commit** ```bash -git add crates/clarion-mcp/src/snapshot.rs crates/clarion-mcp/src/lib.rs +git add crates/loomweave-mcp/src/snapshot.rs crates/loomweave-mcp/src/lib.rs git commit -m "feat(mcp): shared project_snapshot (counts + staleness)" ``` @@ -883,11 +883,11 @@ git commit -m "feat(mcp): shared project_snapshot (counts + staleness)" **Files:** - Modify: none (logic already in 2.1; this task adds the proving tests) -- Test: inline `#[cfg(test)]` in `crates/clarion-mcp/src/snapshot.rs` +- Test: inline `#[cfg(test)]` in `crates/loomweave-mcp/src/snapshot.rs` - [ ] **Step 1: Write the failing test** -Add to `mod tests` in `crates/clarion-mcp/src/snapshot.rs`: +Add to `mod tests` in `crates/loomweave-mcp/src/snapshot.rs`: ```rust #[test] @@ -943,7 +943,7 @@ fn stale_when_a_source_is_newer_than_run() { - [ ] **Step 2: Run test to verify it fails or passes** -Run: `cargo nextest run -p clarion-mcp snapshot` +Run: `cargo nextest run -p loomweave-mcp snapshot` Expected: PASS — the staleness logic from Task 2.1 already satisfies these. (If any fail, the bug is in 2.1's `compute_staleness`; fix it there, do not weaken the test.) This task exists to lock the fresh/stale boundary with explicit fixtures. - [ ] **Step 3: (no new implementation — logic landed in 2.1)** @@ -952,13 +952,13 @@ Skip; the tests prove the existing implementation. - [ ] **Step 4: Re-run to confirm green** -Run: `cargo nextest run -p clarion-mcp snapshot` +Run: `cargo nextest run -p loomweave-mcp snapshot` Expected: PASS. - [ ] **Step 5: Commit** ```bash -git add crates/clarion-mcp/src/snapshot.rs +git add crates/loomweave-mcp/src/snapshot.rs git commit -m "test(mcp): lock fresh/stale boundary for project_snapshot" ``` @@ -966,7 +966,7 @@ git commit -m "test(mcp): lock fresh/stale boundary for project_snapshot" - [ ] **Step 1: Run the crate gate** -Run: `cargo fmt --all -- --check && cargo clippy -p clarion-mcp --all-targets --all-features -- -D warnings && cargo nextest run -p clarion-mcp` +Run: `cargo fmt --all -- --check && cargo clippy -p loomweave-mcp --all-targets --all-features -- -D warnings && cargo nextest run -p loomweave-mcp` Expected: PASS, no warnings. - [ ] **Step 2: Commit fixes if any** @@ -978,43 +978,43 @@ git commit -m "style(mcp): fmt/clippy after snapshot module" --- -## PHASE 3 — `clarion hook session-start` subcommand +## PHASE 3 — `loomweave hook session-start` subcommand Fail-soft: always exits 0. Re-syncs the skill on drift, prints the snapshot + nudge. ### Task 3.1: CLI `Hook { SessionStart }` subcommand wired to a no-op handler **Files:** -- Modify: `crates/clarion-cli/src/cli.rs` (add `Hook` variant + `HookCommand` enum) -- Create: `crates/clarion-cli/src/hook.rs` (handler) -- Modify: `crates/clarion-cli/src/main.rs` (add `mod hook;` + dispatch) -- Test: `crates/clarion-cli/tests/hook.rs` (create) +- Modify: `crates/loomweave-cli/src/cli.rs` (add `Hook` variant + `HookCommand` enum) +- Create: `crates/loomweave-cli/src/hook.rs` (handler) +- Modify: `crates/loomweave-cli/src/main.rs` (add `mod hook;` + dispatch) +- Test: `crates/loomweave-cli/tests/hook.rs` (create) - [ ] **Step 1: Write the failing test** -Create `crates/clarion-cli/tests/hook.rs`: +Create `crates/loomweave-cli/tests/hook.rs`: ```rust -//! `clarion hook session-start` integration tests. +//! `loomweave hook session-start` integration tests. use assert_cmd::Command; -fn clarion_bin() -> Command { - Command::cargo_bin("clarion").expect("clarion binary") +fn loomweave_bin() -> Command { + Command::cargo_bin("loomweave").expect("loomweave binary") } #[test] -fn hook_session_start_exits_zero_without_clarion_db() { - // Fail-soft: no .clarion/ at all must still exit 0 and nudge. +fn hook_session_start_exits_zero_without_loomweave_db() { + // Fail-soft: no .loomweave/ at all must still exit 0 and nudge. let dir = tempfile::tempdir().unwrap(); - let assert = clarion_bin() + let assert = loomweave_bin() .args(["hook", "session-start", "--path"]) .arg(dir.path()) .assert() .success(); let out = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); assert!( - out.contains("clarion analyze"), + out.contains("loomweave analyze"), "missing analyze nudge in: {out}" ); } @@ -1022,12 +1022,12 @@ fn hook_session_start_exits_zero_without_clarion_db() { - [ ] **Step 2: Run test to verify it fails** -Run: `cargo nextest run -p clarion-cli --test hook` +Run: `cargo nextest run -p loomweave-cli --test hook` Expected: FAIL — `hook` is an unknown subcommand (clap nonzero exit). - [ ] **Step 3: Write minimal implementation** -In `crates/clarion-cli/src/cli.rs`, add a new variant to `enum Command` (after `Serve { ... }`): +In `crates/loomweave-cli/src/cli.rs`, add a new variant to `enum Command` (after `Serve { ... }`): ```rust /// Agent-lifecycle hook entrypoints. Always exit 0 (fail-soft) so a @@ -1038,39 +1038,39 @@ In `crates/clarion-cli/src/cli.rs`, add a new variant to `enum Command` (after ` }, ``` -And add this enum at the bottom of `crates/clarion-cli/src/cli.rs`: +And add this enum at the bottom of `crates/loomweave-cli/src/cli.rs`: ```rust #[derive(Subcommand)] pub enum HookCommand { /// Print a project snapshot and re-sync the skill pack on drift. SessionStart { - /// Project directory containing .clarion/clarion.db. + /// Project directory containing .loomweave/loomweave.db. #[arg(long, default_value = ".")] path: PathBuf, }, } ``` -Create `crates/clarion-cli/src/hook.rs`: +Create `crates/loomweave-cli/src/hook.rs`: ```rust -//! `clarion hook session-start` — fail-soft session-start orientation. +//! `loomweave hook session-start` — fail-soft session-start orientation. //! //! Never returns an error to the caller: the SessionStart hook must never //! block an agent's session start. All failures degrade to a printed note. use std::path::Path; -/// Run `clarion hook session-start`. Always returns `Ok(())`. +/// Run `loomweave hook session-start`. Always returns `Ok(())`. pub fn session_start(path: &Path) -> anyhow::Result<()> { - println!("Clarion: orientation hook (snapshot wired in Task 3.2)."); - println!("If briefings look empty, run `clarion analyze {}`.", path.display()); + println!("Loomweave: orientation hook (snapshot wired in Task 3.2)."); + println!("If briefings look empty, run `loomweave analyze {}`.", path.display()); Ok(()) } ``` -In `crates/clarion-cli/src/main.rs`, add `mod hook;` near the other modules, and add the dispatch arm after the `Serve` arm: +In `crates/loomweave-cli/src/main.rs`, add `mod hook;` near the other modules, and add the dispatch arm after the `Serve` arm: ```rust cli::Command::Hook { command } => match command { @@ -1080,38 +1080,38 @@ In `crates/clarion-cli/src/main.rs`, add `mod hook;` near the other modules, and - [ ] **Step 4: Run test to verify it passes** -Run: `cargo nextest run -p clarion-cli --test hook` +Run: `cargo nextest run -p loomweave-cli --test hook` Expected: PASS. - [ ] **Step 5: Commit** ```bash -git add crates/clarion-cli/src/cli.rs crates/clarion-cli/src/hook.rs crates/clarion-cli/src/main.rs crates/clarion-cli/tests/hook.rs -git commit -m "feat(cli): add fail-soft clarion hook session-start subcommand" +git add crates/loomweave-cli/src/cli.rs crates/loomweave-cli/src/hook.rs crates/loomweave-cli/src/main.rs crates/loomweave-cli/tests/hook.rs +git commit -m "feat(cli): add fail-soft loomweave hook session-start subcommand" ``` ### Task 3.2: Snapshot output + skill resync in the hook **Files:** -- Modify: `crates/clarion-cli/src/hook.rs` (full snapshot + resync) -- Test: `crates/clarion-cli/tests/hook.rs` (add) +- Modify: `crates/loomweave-cli/src/hook.rs` (full snapshot + resync) +- Test: `crates/loomweave-cli/tests/hook.rs` (add) - [ ] **Step 1: Write the failing test** -Add to `crates/clarion-cli/tests/hook.rs`: +Add to `crates/loomweave-cli/tests/hook.rs`: ```rust #[test] fn hook_session_start_prints_counts_for_installed_project() { let dir = tempfile::tempdir().unwrap(); - // Initialise .clarion/ (bare install). - clarion_bin() + // Initialise .loomweave/ (bare install). + loomweave_bin() .args(["install", "--path"]) .arg(dir.path()) .assert() .success(); - let assert = clarion_bin() + let assert = loomweave_bin() .args(["hook", "session-start", "--path"]) .arg(dir.path()) .assert() @@ -1119,24 +1119,24 @@ fn hook_session_start_prints_counts_for_installed_project() { let out = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); // Empty db: 0 entities, never analyzed → nudge present. assert!(out.contains("entities"), "missing entity count line: {out}"); - assert!(out.contains("clarion analyze"), "missing nudge: {out}"); + assert!(out.contains("loomweave analyze"), "missing nudge: {out}"); } #[test] fn hook_session_start_resyncs_skill_when_present_and_drifted() { let dir = tempfile::tempdir().unwrap(); // Install the skill, then corrupt it to simulate drift. - clarion_bin() + loomweave_bin() .args(["install", "--skills", "--path"]) .arg(dir.path()) .assert() .success(); let skill = dir .path() - .join(".claude/skills/clarion-workflow/SKILL.md"); + .join(".claude/skills/loomweave-workflow/SKILL.md"); std::fs::write(&skill, "STALE").unwrap(); - clarion_bin() + loomweave_bin() .args(["hook", "session-start", "--path"]) .arg(dir.path()) .assert() @@ -1144,7 +1144,7 @@ fn hook_session_start_resyncs_skill_when_present_and_drifted() { let body = std::fs::read_to_string(&skill).unwrap(); assert!( - body.contains("name: clarion-workflow"), + body.contains("name: loomweave-workflow"), "hook did not repair drifted skill: {body}" ); } @@ -1152,28 +1152,28 @@ fn hook_session_start_resyncs_skill_when_present_and_drifted() { - [ ] **Step 2: Run test to verify it fails** -Run: `cargo nextest run -p clarion-cli --test hook` +Run: `cargo nextest run -p loomweave-cli --test hook` Expected: FAIL — current stub prints neither counts nor resyncs (the new assertions fail). - [ ] **Step 3: Write minimal implementation** -Replace the body of `crates/clarion-cli/src/hook.rs` with: +Replace the body of `crates/loomweave-cli/src/hook.rs` with: ```rust -//! `clarion hook session-start` — fail-soft session-start orientation. +//! `loomweave hook session-start` — fail-soft session-start orientation. //! //! Never returns an error to the caller: the SessionStart hook must never //! block an agent's session start. All failures degrade to a printed note. use std::path::Path; -use clarion_mcp::snapshot::{ProjectSnapshot, Staleness, missing_db_snapshot, project_snapshot}; +use loomweave_mcp::snapshot::{ProjectSnapshot, Staleness, missing_db_snapshot, project_snapshot}; use rusqlite::{Connection, OpenFlags}; -/// Run `clarion hook session-start`. Always returns `Ok(())`. +/// Run `loomweave hook session-start`. Always returns `Ok(())`. pub fn session_start(path: &Path) -> anyhow::Result<()> { // (1) Re-sync the skill pack ONLY if it's already installed and drifted. - // We don't install where absent — that's `clarion install --skills`'s + // We don't install where absent — that's `loomweave install --skills`'s // job. A drift repair keeps an installed copy honest across upgrades. resync_skill_if_present(path); @@ -1185,22 +1185,22 @@ pub fn session_start(path: &Path) -> anyhow::Result<()> { fn resync_skill_if_present(project_root: &Path) { let installed = project_root - .join(".claude/skills/clarion-workflow/SKILL.md") + .join(".claude/skills/loomweave-workflow/SKILL.md") .exists() || project_root - .join(".agents/skills/clarion-workflow/SKILL.md") + .join(".agents/skills/loomweave-workflow/SKILL.md") .exists(); if !installed { return; } if let Err(err) = crate::skill_pack::install_skill_pack(project_root) { // Fail-soft: log, never propagate. - tracing::warn!(error = %err, "clarion-workflow skill resync failed"); + tracing::warn!(error = %err, "loomweave-workflow skill resync failed"); } } fn load_snapshot(project_root: &Path) -> ProjectSnapshot { - let db_path = project_root.join(".clarion").join("clarion.db"); + let db_path = project_root.join(".loomweave").join("loomweave.db"); if !db_path.exists() { return missing_db_snapshot(); } @@ -1212,7 +1212,7 @@ fn load_snapshot(project_root: &Path) -> ProjectSnapshot { project_snapshot(&conn, &root) } Err(err) => { - tracing::warn!(error = %err, "open .clarion/clarion.db read-only failed"); + tracing::warn!(error = %err, "open .loomweave/loomweave.db read-only failed"); missing_db_snapshot() } } @@ -1221,8 +1221,8 @@ fn load_snapshot(project_root: &Path) -> ProjectSnapshot { fn print_snapshot(project_root: &Path, snapshot: &ProjectSnapshot) { if !snapshot.db_present { println!( - "Clarion: no index at {}/.clarion/clarion.db. \ - Run `clarion install --path {}` then `clarion analyze {}`.", + "Loomweave: no index at {}/.loomweave/loomweave.db. \ + Run `loomweave install --path {}` then `loomweave analyze {}`.", project_root.display(), project_root.display(), project_root.display() @@ -1230,34 +1230,34 @@ fn print_snapshot(project_root: &Path, snapshot: &ProjectSnapshot) { return; } println!( - "Clarion index: {} entities, {} subsystems, {} findings.", + "Loomweave index: {} entities, {} subsystems, {} findings.", snapshot.entity_count, snapshot.subsystem_count, snapshot.finding_count ); match snapshot.staleness { Staleness::Fresh => { println!( - "Index is fresh (last analyzed {}). Ask Clarion before re-exploring \ - the tree; see the clarion-workflow skill.", + "Index is fresh (last analyzed {}). Ask Loomweave before re-exploring \ + the tree; see the loomweave-workflow skill.", snapshot.last_analyzed_at.as_deref().unwrap_or("unknown") ); } Staleness::Stale => { println!( "Index may be stale: source files changed since the last run. \ - Run `clarion analyze {}` to refresh.", + Run `loomweave analyze {}` to refresh.", project_root.display() ); } Staleness::NeverAnalyzed => { println!( - "No analysis recorded yet. Run `clarion analyze {}` to build the index.", + "No analysis recorded yet. Run `loomweave analyze {}` to build the index.", project_root.display() ); } Staleness::Unknown => { println!( "Index freshness unknown. If briefings look empty, run \ - `clarion analyze {}`.", + `loomweave analyze {}`.", project_root.display() ); } @@ -1267,13 +1267,13 @@ fn print_snapshot(project_root: &Path, snapshot: &ProjectSnapshot) { - [ ] **Step 4: Run test to verify it passes** -Run: `cargo nextest run -p clarion-cli --test hook` -Expected: PASS (all three hook tests green). The empty-db case yields `NeverAnalyzed` → prints "clarion analyze". +Run: `cargo nextest run -p loomweave-cli --test hook` +Expected: PASS (all three hook tests green). The empty-db case yields `NeverAnalyzed` → prints "loomweave analyze". - [ ] **Step 5: Commit** ```bash -git add crates/clarion-cli/src/hook.rs crates/clarion-cli/tests/hook.rs +git add crates/loomweave-cli/src/hook.rs crates/loomweave-cli/tests/hook.rs git commit -m "feat(cli): hook session-start prints snapshot and resyncs skill on drift" ``` @@ -1281,7 +1281,7 @@ git commit -m "feat(cli): hook session-start prints snapshot and resyncs skill o - [ ] **Step 1: Run the crate gate** -Run: `cargo fmt --all -- --check && cargo clippy -p clarion-cli --all-targets --all-features -- -D warnings && cargo nextest run -p clarion-cli` +Run: `cargo fmt --all -- --check && cargo clippy -p loomweave-cli --all-targets --all-features -- -D warnings && cargo nextest run -p loomweave-cli` Expected: PASS, no warnings. - [ ] **Step 2: Commit fixes if any** @@ -1293,22 +1293,22 @@ git commit -m "style(cli): fmt/clippy after hook subcommand" --- -## PHASE 4 — `clarion install --hooks` (settings.json merge) + `--all` +## PHASE 4 — `loomweave install --hooks` (settings.json merge) + `--all` Merge a SessionStart hook into `.claude/settings.json` without clobbering existing keys. ### Task 4.1: Pure settings.json merge function **Files:** -- Create: `crates/clarion-cli/src/hooks_settings.rs` -- Modify: `crates/clarion-cli/src/main.rs` (add `mod hooks_settings;`) -- Test: inline `#[cfg(test)]` in `crates/clarion-cli/src/hooks_settings.rs` +- Create: `crates/loomweave-cli/src/hooks_settings.rs` +- Modify: `crates/loomweave-cli/src/main.rs` (add `mod hooks_settings;`) +- Test: inline `#[cfg(test)]` in `crates/loomweave-cli/src/hooks_settings.rs` -The verified `.claude/settings.json` shape (from the settings JSON schema): `hooks` is an object keyed by event name; `hooks.SessionStart` is an array of matcher-groups; each group is `{ "matcher"?: string, "hooks": [ { "type": "command", "command": "..." } ] }`. `matcher` is optional. Idempotency predicate: skip if any SessionStart group has a `hooks[]` entry whose `command` contains `clarion hook session-start`. +The verified `.claude/settings.json` shape (from the settings JSON schema): `hooks` is an object keyed by event name; `hooks.SessionStart` is an array of matcher-groups; each group is `{ "matcher"?: string, "hooks": [ { "type": "command", "command": "..." } ] }`. `matcher` is optional. Idempotency predicate: skip if any SessionStart group has a `hooks[]` entry whose `command` contains `loomweave hook session-start`. - [ ] **Step 1: Write the failing test** -Create `crates/clarion-cli/src/hooks_settings.rs` with this test block at the bottom: +Create `crates/loomweave-cli/src/hooks_settings.rs` with this test block at the bottom: ```rust #[cfg(test)] @@ -1380,18 +1380,18 @@ mod tests { - [ ] **Step 2: Run test to verify it fails** -Run: `cargo nextest run -p clarion-cli hooks_settings` +Run: `cargo nextest run -p loomweave-cli hooks_settings` Expected: FAIL — `merge_session_start_hook`/`HOOK_COMMAND` not defined (compile error). - [ ] **Step 3: Write minimal implementation** -Top of `crates/clarion-cli/src/hooks_settings.rs`: +Top of `crates/loomweave-cli/src/hooks_settings.rs`: ```rust //! `.claude/settings.json` SessionStart-hook merge. //! //! Merge semantics (never clobber): parse existing JSON, append a SessionStart -//! matcher-group running `clarion hook session-start` only if no existing +//! matcher-group running `loomweave hook session-start` only if no existing //! SessionStart entry already runs that command, and preserve every other key. //! //! Verified against the Claude Code settings schema: `hooks.SessionStart` is an @@ -1403,10 +1403,10 @@ use std::path::Path; use anyhow::{Context, Result}; use serde_json::{Map, Value, json}; -/// Substring that identifies Clarion's own SessionStart hook command. -pub const HOOK_COMMAND: &str = "clarion hook session-start"; +/// Substring that identifies Loomweave's own SessionStart hook command. +pub const HOOK_COMMAND: &str = "loomweave hook session-start"; -/// Merge Clarion's SessionStart hook into a parsed settings `Value` in place. +/// Merge Loomweave's SessionStart hook into a parsed settings `Value` in place. /// Returns `true` if a change was made, `false` if the hook was already present. #[must_use] pub fn merge_session_start_hook(settings: &mut Value) -> bool { @@ -1455,7 +1455,7 @@ pub fn merge_session_start_hook(settings: &mut Value) -> bool { "hooks": [ { "type": "command", - "command": "clarion hook session-start" + "command": "loomweave hook session-start" } ] })); @@ -1463,7 +1463,7 @@ pub fn merge_session_start_hook(settings: &mut Value) -> bool { } /// Read `.claude/settings.json` under `project_root` (creating an empty object -/// if absent), merge Clarion's SessionStart hook, and write it back +/// if absent), merge Loomweave's SessionStart hook, and write it back /// pretty-printed. Returns `true` if the file changed. /// /// # Errors @@ -1502,7 +1502,7 @@ pub fn install_session_start_hook(project_root: &Path) -> Result { } ``` -Add to `crates/clarion-cli/src/main.rs` near the modules: +Add to `crates/loomweave-cli/src/main.rs` near the modules: ```rust mod hooks_settings; @@ -1510,25 +1510,25 @@ mod hooks_settings; - [ ] **Step 4: Run test to verify it passes** -Run: `cargo nextest run -p clarion-cli hooks_settings` +Run: `cargo nextest run -p loomweave-cli hooks_settings` Expected: PASS (all three merge tests green). - [ ] **Step 5: Commit** ```bash -git add crates/clarion-cli/src/hooks_settings.rs crates/clarion-cli/src/main.rs +git add crates/loomweave-cli/src/hooks_settings.rs crates/loomweave-cli/src/main.rs git commit -m "feat(cli): non-clobbering SessionStart hook merge for .claude/settings.json" ``` ### Task 4.2: Wire `--hooks` and `--all` into `install::run` **Files:** -- Modify: `crates/clarion-cli/src/install.rs` (`run`) -- Test: `crates/clarion-cli/tests/skills.rs` (add) +- Modify: `crates/loomweave-cli/src/install.rs` (`run`) +- Test: `crates/loomweave-cli/tests/skills.rs` (add) - [ ] **Step 1: Write the failing test** -Add to `crates/clarion-cli/tests/skills.rs`: +Add to `crates/loomweave-cli/tests/skills.rs`: ```rust #[test] @@ -1543,7 +1543,7 @@ fn install_hooks_merges_session_start_without_clobbering() { ) .unwrap(); - clarion_bin() + loomweave_bin() .args(["install", "--hooks", "--path"]) .arg(dir.path()) .assert() @@ -1565,40 +1565,40 @@ fn install_hooks_merges_session_start_without_clobbering() { .flat_map(|g| g["hooks"].as_array().unwrap()) .map(|h| h["command"].as_str().unwrap().to_string()) .collect(); - assert!(cmds.iter().any(|c| c.contains("clarion hook session-start"))); - // --hooks alone must NOT init .clarion/. - assert!(!dir.path().join(".clarion").exists()); + assert!(cmds.iter().any(|c| c.contains("loomweave hook session-start"))); + // --hooks alone must NOT init .loomweave/. + assert!(!dir.path().join(".loomweave").exists()); } #[test] fn install_all_does_init_skills_and_hooks() { let dir = tempfile::tempdir().unwrap(); - clarion_bin() + loomweave_bin() .args(["install", "--all", "--path"]) .arg(dir.path()) .assert() .success(); - assert!(dir.path().join(".clarion/clarion.db").exists(), "no db"); + assert!(dir.path().join(".loomweave/loomweave.db").exists(), "no db"); assert!( dir.path() - .join(".claude/skills/clarion-workflow/SKILL.md") + .join(".claude/skills/loomweave-workflow/SKILL.md") .exists(), "no skill" ); let raw = fs::read_to_string(dir.path().join(".claude/settings.json")).unwrap(); - assert!(raw.contains("clarion hook session-start"), "no hook: {raw}"); + assert!(raw.contains("loomweave hook session-start"), "no hook: {raw}"); } ``` - [ ] **Step 2: Run test to verify it fails** -Run: `cargo nextest run -p clarion-cli --test skills` +Run: `cargo nextest run -p loomweave-cli --test skills` Expected: FAIL — `--hooks` currently does nothing (the comment placeholder from Task 1.3); the SessionStart assertion fails. - [ ] **Step 3: Write minimal implementation** -In `crates/clarion-cli/src/install.rs`, replace the placeholder line: +In `crates/loomweave-cli/src/install.rs`, replace the placeholder line: ```rust // --hooks wired in Phase 4. @@ -1612,32 +1612,32 @@ with: .context("merge SessionStart hook into .claude/settings.json")?; if changed { println!( - "Added clarion SessionStart hook to {}/.claude/settings.json", + "Added loomweave SessionStart hook to {}/.claude/settings.json", project_root.display() ); } else { - println!("clarion SessionStart hook already present"); + println!("loomweave SessionStart hook already present"); } } ``` - [ ] **Step 4: Run test to verify it passes** -Run: `cargo nextest run -p clarion-cli --test skills` +Run: `cargo nextest run -p loomweave-cli --test skills` Expected: PASS (both new tests green plus the Phase-1 skills tests). - [ ] **Step 5: Commit** ```bash -git add crates/clarion-cli/src/install.rs crates/clarion-cli/tests/skills.rs -git commit -m "feat(cli): wire clarion install --hooks and --all" +git add crates/loomweave-cli/src/install.rs crates/loomweave-cli/tests/skills.rs +git commit -m "feat(cli): wire loomweave install --hooks and --all" ``` ### Task 4.3: Phase 4 gate - [ ] **Step 1: Run the crate gate** -Run: `cargo fmt --all -- --check && cargo clippy -p clarion-cli --all-targets --all-features -- -D warnings && cargo nextest run -p clarion-cli` +Run: `cargo fmt --all -- --check && cargo clippy -p loomweave-cli --all-targets --all-features -- -D warnings && cargo nextest run -p loomweave-cli` Expected: PASS, no warnings. Existing `tests/install.rs` still green. - [ ] **Step 2: Commit fixes if any** @@ -1651,17 +1651,17 @@ git commit -m "style(cli): fmt/clippy after hooks install" ## PHASE 5 — MCP `instructions` + `resources` + (optional) `prompt` -`clarion://context` reuses `project_snapshot`. The prompt duplicates `SKILL.md` and is the droppable piece. +`loomweave://context` reuses `project_snapshot`. The prompt duplicates `SKILL.md` and is the droppable piece. ### Task 5.1: `initialize` gains `instructions` + extended `capabilities` **Files:** -- Modify: `crates/clarion-mcp/src/lib.rs` (`initialize_result` ~2059, embed `SKILL.md`) -- Test: inline `#[cfg(test)]` in `crates/clarion-mcp/src/lib.rs` (extend `initialize_returns_server_info_and_tools_capability`, ~2901) +- Modify: `crates/loomweave-mcp/src/lib.rs` (`initialize_result` ~2059, embed `SKILL.md`) +- Test: inline `#[cfg(test)]` in `crates/loomweave-mcp/src/lib.rs` (extend `initialize_returns_server_info_and_tools_capability`, ~2901) - [ ] **Step 1: Write the failing test** -In `crates/clarion-mcp/src/lib.rs`, extend the existing test `initialize_returns_server_info_and_tools_capability` (after the final `assert!(response["result"]["capabilities"]["tools"].is_object());`, before the closing `}`): +In `crates/loomweave-mcp/src/lib.rs`, extend the existing test `initialize_returns_server_info_and_tools_capability` (after the final `assert!(response["result"]["capabilities"]["tools"].is_object());`, before the closing `}`): ```rust // Orientation instructions present and mention the skill + entity model. @@ -1669,7 +1669,7 @@ In `crates/clarion-mcp/src/lib.rs`, extend the existing test `initialize_returns .as_str() .expect("initialize result has instructions"); assert!( - instructions.contains("clarion-workflow"), + instructions.contains("loomweave-workflow"), "instructions should point at the skill" ); assert!( @@ -1683,27 +1683,27 @@ In `crates/clarion-mcp/src/lib.rs`, extend the existing test `initialize_returns - [ ] **Step 2: Run test to verify it fails** -Run: `cargo nextest run -p clarion-mcp initialize_returns_server_info` +Run: `cargo nextest run -p loomweave-mcp initialize_returns_server_info` Expected: FAIL — `instructions` is absent (`as_str()` on Null panics → test fails); `capabilities.prompts`/`resources` absent. - [ ] **Step 3: Write minimal implementation** -In `crates/clarion-mcp/src/lib.rs`, near the top (after `const EMPTY_GUIDANCE_FINGERPRINT`), add the embedded skill text + the instructions constant: +In `crates/loomweave-mcp/src/lib.rs`, near the top (after `const EMPTY_GUIDANCE_FINGERPRINT`), add the embedded skill text + the instructions constant: ```rust -/// The bundled clarion-workflow skill text, embedded for the `prompts/get` +/// The bundled loomweave-workflow skill text, embedded for the `prompts/get` /// surface and reused as the canonical orientation reference. Same file the /// CLI installs on disk. -pub const CLARION_WORKFLOW_SKILL: &str = - include_str!("../../clarion-cli/assets/skills/clarion-workflow/SKILL.md"); +pub const LOOMWEAVE_WORKFLOW_SKILL: &str = + include_str!("../../loomweave-cli/assets/skills/loomweave-workflow/SKILL.md"); /// Static orientation text returned in the MCP `initialize` result's /// `instructions` field. Kept consistent with `list_tools()` and the -/// clarion-workflow skill. +/// loomweave-workflow skill. const SERVER_INSTRUCTIONS: &str = "\ -Clarion is a code-archaeology server: it has pre-extracted this project into a \ +Loomweave is a code-archaeology server: it has pre-extracted this project into a \ queryable map of entities (functions, classes, modules, files), the call / \ -reference / import edges between them, and subsystem clusters. Ask Clarion \ +reference / import edges between them, and subsystem clusters. Ask Loomweave \ instead of re-reading or grepping the tree. Entity IDs are `{plugin}:{kind}:{qualified_name}` (e.g. \ @@ -1716,9 +1716,9 @@ subsystem_members, summary, issues_for. `callers_of` / `neighborhood` / \ `execution_paths_from` take a `confidence` tier (resolved | ambiguous | \ inferred; default resolved). -For the full workflow see the clarion-workflow skill (installed by \ -`clarion install --skills`), or read the `clarion-workflow` prompt. Live \ -project counts and index freshness are in the `clarion://context` resource."; +For the full workflow see the loomweave-workflow skill (installed by \ +`loomweave install --skills`), or read the `loomweave-workflow` prompt. Live \ +project counts and index freshness are in the `loomweave://context` resource."; ``` Replace `initialize_result` (lines ~2059-2070) with: @@ -1733,7 +1733,7 @@ fn initialize_result() -> Value { "resources": {} }, "serverInfo": { - "name": "clarion", + "name": "loomweave", "version": env!("CARGO_PKG_VERSION") }, "instructions": SERVER_INSTRUCTIONS @@ -1743,33 +1743,33 @@ fn initialize_result() -> Value { - [ ] **Step 4: Run test to verify it passes** -Run: `cargo nextest run -p clarion-mcp initialize` +Run: `cargo nextest run -p loomweave-mcp initialize` Expected: PASS. Also confirm the existing `tools_list_exposes_exact_docstrings` (asserts `tools.len() == 8`) still passes — `list_tools()` is untouched: -Run: `cargo nextest run -p clarion-mcp tools_list_exposes_exact_docstrings` +Run: `cargo nextest run -p loomweave-mcp tools_list_exposes_exact_docstrings` Expected: PASS. - [ ] **Step 5: Commit** ```bash -git add crates/clarion-mcp/src/lib.rs +git add crates/loomweave-mcp/src/lib.rs git commit -m "feat(mcp): initialize advertises instructions + prompts/resources capabilities" ``` -### Task 5.2: `resources/list` returns `clarion://context` +### Task 5.2: `resources/list` returns `loomweave://context` **Files:** -- Modify: `crates/clarion-mcp/src/lib.rs` (`ServerState::handle_json_rpc` match ~255) -- Test: inline `#[cfg(test)]` in `crates/clarion-mcp/src/lib.rs` +- Modify: `crates/loomweave-mcp/src/lib.rs` (`ServerState::handle_json_rpc` match ~255) +- Test: inline `#[cfg(test)]` in `crates/loomweave-mcp/src/lib.rs` - [ ] **Step 1: Write the failing test** -Look at how existing `ServerState` tests build state (the test module imports `ServerState`, `ReaderPool`, `pragma`, `schema` — see `crates/clarion-mcp/src/lib.rs:2845-2852`). Add this test to the `mod tests` block. It builds a migrated db on disk, opens a `ReaderPool`, and drives `handle_json_rpc`: +Look at how existing `ServerState` tests build state (the test module imports `ServerState`, `ReaderPool`, `pragma`, `schema` — see `crates/loomweave-mcp/src/lib.rs:2845-2852`). Add this test to the `mod tests` block. It builds a migrated db on disk, opens a `ReaderPool`, and drives `handle_json_rpc`: ```rust #[tokio::test] - async fn resources_list_includes_clarion_context() { + async fn resources_list_includes_loomweave_context() { let dir = tempfile::tempdir().unwrap(); - let db = dir.path().join("clarion.db"); + let db = dir.path().join("loomweave.db"); { let mut conn = rusqlite::Connection::open(&db).unwrap(); pragma::apply_write_pragmas(&conn).unwrap(); @@ -1792,22 +1792,22 @@ Look at how existing `ServerState` tests build state (the test module imports `S assert!( resources .iter() - .any(|r| r["uri"] == "clarion://context"), - "clarion://context not listed: {resources:?}" + .any(|r| r["uri"] == "loomweave://context"), + "loomweave://context not listed: {resources:?}" ); } ``` -(Confirm `tempfile` is a dev-dependency of `clarion-mcp`. If `cargo nextest` reports it missing, add `tempfile.workspace = true` under `[dev-dependencies]` in `crates/clarion-mcp/Cargo.toml` and `git add` it with this task.) +(Confirm `tempfile` is a dev-dependency of `loomweave-mcp`. If `cargo nextest` reports it missing, add `tempfile.workspace = true` under `[dev-dependencies]` in `crates/loomweave-mcp/Cargo.toml` and `git add` it with this task.) - [ ] **Step 2: Run test to verify it fails** -Run: `cargo nextest run -p clarion-mcp resources_list` +Run: `cargo nextest run -p loomweave-mcp resources_list` Expected: FAIL — `resources/list` hits the `_ => error_response(..., "method not found")` arm, so `result.resources` is absent. - [ ] **Step 3: Write minimal implementation** -In `crates/clarion-mcp/src/lib.rs`, extend the `ServerState::handle_json_rpc` match (the `Some(match method { ... })` at ~255) by adding arms before the final `_ =>`: +In `crates/loomweave-mcp/src/lib.rs`, extend the `ServerState::handle_json_rpc` match (the `Some(match method { ... })` at ~255) by adding arms before the final `_ =>`: ```rust "resources/list" => result_response(&id, &resources_list()), @@ -1823,8 +1823,8 @@ fn resources_list() -> Value { json!({ "resources": [ { - "uri": "clarion://context", - "name": "Clarion project context", + "uri": "loomweave://context", + "name": "Loomweave project context", "description": "Live entity / subsystem / finding counts and index freshness for this project.", "mimeType": "application/json" } @@ -1846,7 +1846,7 @@ The test still won't pass until `handle_resources_read` and the prompt fns exist else { return error_response(id, -32602, "invalid resources/read params: missing uri"); }; - if uri != "clarion://context" { + if uri != "loomweave://context" { return error_response(id, -32602, &format!("unknown resource: {uri}")); } let snapshot_json = self.context_snapshot_json().await; @@ -1855,7 +1855,7 @@ The test still won't pass until `handle_resources_read` and the prompt fns exist &json!({ "contents": [ { - "uri": "clarion://context", + "uri": "loomweave://context", "mimeType": "application/json", "text": snapshot_json } @@ -1876,7 +1876,7 @@ The test still won't pass until `handle_resources_read` and the prompt fns exist Ok(snap) => serde_json::to_string(&snap) .unwrap_or_else(|_| "{\"db_present\":true,\"staleness\":\"unknown\"}".to_owned()), Err(err) => { - tracing::warn!(error = %err, "clarion://context snapshot failed"); + tracing::warn!(error = %err, "loomweave://context snapshot failed"); serde_json::json!({ "db_present": true, "entity_count": 0, @@ -1898,8 +1898,8 @@ fn prompts_list() -> Value { json!({ "prompts": [ { - "name": "clarion-workflow", - "description": "How to use Clarion's MCP tools to navigate this codebase." + "name": "loomweave-workflow", + "description": "How to use Loomweave's MCP tools to navigate this codebase." } ] }) @@ -1910,17 +1910,17 @@ fn prompts_get(id: &Value, params: Option<&Value>) -> Value { .and_then(Value::as_object) .and_then(|p| p.get("name")) .and_then(Value::as_str); - if name != Some("clarion-workflow") { + if name != Some("loomweave-workflow") { return error_response(id, -32602, "unknown prompt"); } result_response( id, &json!({ - "description": "How to use Clarion's MCP tools to navigate this codebase.", + "description": "How to use Loomweave's MCP tools to navigate this codebase.", "messages": [ { "role": "user", - "content": { "type": "text", "text": CLARION_WORKFLOW_SKILL } + "content": { "type": "text", "text": LOOMWEAVE_WORKFLOW_SKILL } } ] }), @@ -1928,21 +1928,21 @@ fn prompts_get(id: &Value, params: Option<&Value>) -> Value { } ``` -Run: `cargo nextest run -p clarion-mcp resources_list` +Run: `cargo nextest run -p loomweave-mcp resources_list` Expected: PASS. - [ ] **Step 5: Commit** ```bash -git add crates/clarion-mcp/src/lib.rs crates/clarion-mcp/Cargo.toml -git commit -m "feat(mcp): resources/list advertises clarion://context" +git add crates/loomweave-mcp/src/lib.rs crates/loomweave-mcp/Cargo.toml +git commit -m "feat(mcp): resources/list advertises loomweave://context" ``` -### Task 5.3: `resources/read` of `clarion://context` returns the snapshot +### Task 5.3: `resources/read` of `loomweave://context` returns the snapshot **Files:** - Modify: none (handler landed in 5.2) -- Test: inline `#[cfg(test)]` in `crates/clarion-mcp/src/lib.rs` +- Test: inline `#[cfg(test)]` in `crates/loomweave-mcp/src/lib.rs` - [ ] **Step 1: Write the failing test** @@ -1952,7 +1952,7 @@ Add to `mod tests`: #[tokio::test] async fn resources_read_returns_context_snapshot_json() { let dir = tempfile::tempdir().unwrap(); - let db = dir.path().join("clarion.db"); + let db = dir.path().join("loomweave.db"); { let mut conn = rusqlite::Connection::open(&db).unwrap(); pragma::apply_write_pragmas(&conn).unwrap(); @@ -1974,7 +1974,7 @@ Add to `mod tests`: "jsonrpc": "2.0", "id": 7, "method": "resources/read", - "params": {"uri": "clarion://context"} + "params": {"uri": "loomweave://context"} })) .await .expect("response"); @@ -1991,7 +1991,7 @@ Add to `mod tests`: #[tokio::test] async fn resources_read_rejects_unknown_uri() { let dir = tempfile::tempdir().unwrap(); - let db = dir.path().join("clarion.db"); + let db = dir.path().join("loomweave.db"); { let mut conn = rusqlite::Connection::open(&db).unwrap(); pragma::apply_write_pragmas(&conn).unwrap(); @@ -2005,7 +2005,7 @@ Add to `mod tests`: "jsonrpc": "2.0", "id": 8, "method": "resources/read", - "params": {"uri": "clarion://nope"} + "params": {"uri": "loomweave://nope"} })) .await .expect("response"); @@ -2015,7 +2015,7 @@ Add to `mod tests`: - [ ] **Step 2: Run test to verify it fails or passes** -Run: `cargo nextest run -p clarion-mcp resources_read` +Run: `cargo nextest run -p loomweave-mcp resources_read` Expected: PASS (handler from 5.2 already serves this). If `staleness` is not `never_analyzed`, the bug is in 5.2's snapshot wiring — fix there. This task locks the read contract. - [ ] **Step 3: (no new implementation)** @@ -2024,14 +2024,14 @@ Skip. - [ ] **Step 4: Re-run to confirm green** -Run: `cargo nextest run -p clarion-mcp resources_read` +Run: `cargo nextest run -p loomweave-mcp resources_read` Expected: PASS. - [ ] **Step 5: Commit** ```bash -git add crates/clarion-mcp/src/lib.rs -git commit -m "test(mcp): lock resources/read clarion://context contract" +git add crates/loomweave-mcp/src/lib.rs +git commit -m "test(mcp): lock resources/read loomweave://context contract" ``` ### Task 5.4: e2e — assert `instructions` and `resources/read` over the wire @@ -2051,7 +2051,7 @@ In `tests/e2e/sprint_2_mcp_surface.sh`, add a new entry to the `requests` Python "jsonrpc": "2.0", "id": "context", "method": "resources/read", - "params": {"uri": "clarion://context"}, + "params": {"uri": "loomweave://context"}, }, ), ``` @@ -2060,7 +2060,7 @@ Then add assertions after the existing `assert responses["initialize"]["result"] ```python init_result = responses["initialize"]["result"] -assert "clarion-workflow" in init_result["instructions"], init_result.get("instructions") +assert "loomweave-workflow" in init_result["instructions"], init_result.get("instructions") assert isinstance(init_result["capabilities"]["resources"], dict), init_result["capabilities"] assert isinstance(init_result["capabilities"]["prompts"], dict), init_result["capabilities"] ``` @@ -2079,7 +2079,7 @@ assert "staleness" in ctx, ctx - [ ] **Step 2: Run the e2e script to verify it (initially) reflects reality** Run: `bash tests/e2e/sprint_2_mcp_surface.sh` -Expected: PASS — the binary now serves `instructions`, the `resources` capability, and `resources/read clarion://context`. (If you run this BEFORE Phase 5 code lands, it FAILs on the `instructions` KeyError — that is the failing-test step; run it after 5.1–5.3 are committed.) +Expected: PASS — the binary now serves `instructions`, the `resources` capability, and `resources/read loomweave://context`. (If you run this BEFORE Phase 5 code lands, it FAILs on the `instructions` KeyError — that is the failing-test step; run it after 5.1–5.3 are committed.) - [ ] **Step 3: (no implementation — covered by Phase 5 Rust tasks)** @@ -2094,7 +2094,7 @@ Expected: PASS, final log line "PASS: MCP stdio surface ...". ```bash git add tests/e2e/sprint_2_mcp_surface.sh -git commit -m "test(e2e): assert MCP initialize instructions and clarion://context read" +git commit -m "test(e2e): assert MCP initialize instructions and loomweave://context read" ``` ### Task 5.5 (OPTIONAL — droppable): prove `prompts/list` + `prompts/get` @@ -2103,7 +2103,7 @@ The handler already landed in 5.2; this task only adds proving tests. **Skip und **Files:** - Modify: none -- Test: inline `#[cfg(test)]` in `crates/clarion-mcp/src/lib.rs` +- Test: inline `#[cfg(test)]` in `crates/loomweave-mcp/src/lib.rs` - [ ] **Step 1: Write the test** @@ -2111,7 +2111,7 @@ The handler already landed in 5.2; this task only adds proving tests. **Skip und #[tokio::test] async fn prompts_get_returns_skill_text() { let dir = tempfile::tempdir().unwrap(); - let db = dir.path().join("clarion.db"); + let db = dir.path().join("loomweave.db"); { let mut conn = rusqlite::Connection::open(&db).unwrap(); pragma::apply_write_pragmas(&conn).unwrap(); @@ -2125,20 +2125,20 @@ The handler already landed in 5.2; this task only adds proving tests. **Skip und "jsonrpc": "2.0", "id": 9, "method": "prompts/get", - "params": {"name": "clarion-workflow"} + "params": {"name": "loomweave-workflow"} })) .await .expect("response"); let text = response["result"]["messages"][0]["content"]["text"] .as_str() .unwrap(); - assert!(text.contains("name: clarion-workflow"), "not the skill text"); + assert!(text.contains("name: loomweave-workflow"), "not the skill text"); } ``` - [ ] **Step 2: Run** -Run: `cargo nextest run -p clarion-mcp prompts_get` +Run: `cargo nextest run -p loomweave-mcp prompts_get` Expected: PASS (handler from 5.2 serves it). - [ ] **Step 3: (no implementation)** @@ -2147,14 +2147,14 @@ Skip. - [ ] **Step 4: Confirm** -Run: `cargo nextest run -p clarion-mcp prompts_get` +Run: `cargo nextest run -p loomweave-mcp prompts_get` Expected: PASS. - [ ] **Step 5: Commit** ```bash -git add crates/clarion-mcp/src/lib.rs -git commit -m "test(mcp): prompts/get serves clarion-workflow skill text" +git add crates/loomweave-mcp/src/lib.rs +git commit -m "test(mcp): prompts/get serves loomweave-workflow skill text" ``` ### Task 5.6: Phase 5 gate (full workspace) @@ -2191,7 +2191,7 @@ git commit -m "style: fmt/clippy after MCP orientation surface" ### Task 6.1: Document the new install/hook/MCP surface **Files:** -- Modify: `docs/operator/getting-started.md` (the `clarion serve` / MCP-client section ~155-185) +- Modify: `docs/operator/getting-started.md` (the `loomweave serve` / MCP-client section ~155-185) - [ ] **Step 1: Add an orientation subsection** @@ -2203,24 +2203,24 @@ After the MCP-client registration block (~line 185, before "## 4. Ask"), add: Give consult-mode agents a head start: ```bash -clarion install --skills --path /tmp/requests-2.32.4 # bundle the clarion-workflow skill -clarion install --hooks --path /tmp/requests-2.32.4 # add a SessionStart snapshot hook -clarion install --all --path /tmp/requests-2.32.4 # .clarion/ init + skills + hooks +loomweave install --skills --path /tmp/requests-2.32.4 # bundle the loomweave-workflow skill +loomweave install --hooks --path /tmp/requests-2.32.4 # add a SessionStart snapshot hook +loomweave install --all --path /tmp/requests-2.32.4 # .loomweave/ init + skills + hooks ``` -`--skills` writes `.claude/skills/clarion-workflow/` and `.agents/skills/clarion-workflow/`. +`--skills` writes `.claude/skills/loomweave-workflow/` and `.agents/skills/loomweave-workflow/`. `--hooks` merges a SessionStart entry into `.claude/settings.json` (existing -hooks are preserved) that runs `clarion hook session-start` — a fail-soft +hooks are preserved) that runs `loomweave hook session-start` — a fail-soft command printing live entity/subsystem/finding counts and index freshness. Over MCP, the same orientation is available without install: the `initialize` -result carries an `instructions` field, the `clarion://context` resource returns -the live snapshot, and the `clarion-workflow` prompt returns the skill text. +result carries an `instructions` field, the `loomweave://context` resource returns +the live snapshot, and the `loomweave-workflow` prompt returns the skill text. ``` - [ ] **Step 2: Verify the doc renders (no test; visual check)** -Run: `grep -n "clarion install --skills" docs/operator/getting-started.md` +Run: `grep -n "loomweave install --skills" docs/operator/getting-started.md` Expected: the new lines are present. - [ ] **Step 3: Commit** @@ -2242,12 +2242,12 @@ These are out-of-scope for this slice (Decision Point e) but must be tracked, no ```bash filigree create-issue \ --title "find_entity has no kind filter" \ - --body "Dogfooding clarion-workflow showed find_entity cannot constrain by entity kind (e.g. only subsystems). Today agents must search a package name and eyeball the result whose kind is 'subsystem'. Add an optional kind filter to find_entity's inputSchema + query. Documented as a gotcha in crates/clarion-cli/assets/skills/clarion-workflow/SKILL.md (lines 73-75). Cites: MCP tool surface in crates/clarion-mcp/src/lib.rs list_tools()." \ + --body "Dogfooding loomweave-workflow showed find_entity cannot constrain by entity kind (e.g. only subsystems). Today agents must search a package name and eyeball the result whose kind is 'subsystem'. Add an optional kind filter to find_entity's inputSchema + query. Documented as a gotcha in crates/loomweave-cli/assets/skills/loomweave-workflow/SKILL.md (lines 73-75). Cites: MCP tool surface in crates/loomweave-mcp/src/lib.rs list_tools()." \ --label release:1.1 --actor "$USER" filigree create-issue \ --title "No module->subsystem reverse lookup" \ - --body "neighborhood does not return an entity's subsystem; membership is only reachable forward via subsystem_members(subsystem_id). Add a reverse lookup (subsystem_for_member is already in clarion-storage query.rs — surface it as an MCP tool or neighborhood field). Documented as a gotcha in the clarion-workflow SKILL.md." \ + --body "neighborhood does not return an entity's subsystem; membership is only reachable forward via subsystem_members(subsystem_id). Add a reverse lookup (subsystem_for_member is already in loomweave-storage query.rs — surface it as an MCP tool or neighborhood field). Documented as a gotcha in the loomweave-workflow SKILL.md." \ --label release:1.1 --actor "$USER" ``` @@ -2277,12 +2277,12 @@ Skip git commit; Filigree manages its own store. - (1) `install --skills` atomic temp→rename, both roots, fingerprint-aware → Phase 1 (Tasks 1.2, 1.3). ✓ - (2) `install --hooks` non-clobbering settings.json merge → Phase 4 (Tasks 4.1, 4.2). ✓ - (3) `install --all` + bare unchanged → Task 1.3 (`InstallComponents::from_flags`) + Task 4.2; existing `install.rs` tests prove bare unchanged. ✓ -- (4) `clarion hook session-start` fail-soft, resync + snapshot + nudge → Phase 3. ✓ -- (5) MCP `instructions` + `prompts`/`resources` capabilities + `prompts/list|get` + `resources/list|read clarion://context` → Phase 5. ✓ +- (4) `loomweave hook session-start` fail-soft, resync + snapshot + nudge → Phase 3. ✓ +- (5) MCP `instructions` + `prompts`/`resources` capabilities + `prompts/list|get` + `resources/list|read loomweave://context` → Phase 5. ✓ - (6) shared snapshot module used by both hook and MCP resource → Phase 2 (`project_snapshot`), consumed in Task 3.2 (hook) and Task 5.2 (`context_snapshot_json`). ✓ - Decision Points (include_str! vs include_dir; dogfood gaps; prompt optional) → top section + Task 6.2. ✓ - e2e wire assertions → Task 5.4. ✓ **Placeholder scan:** No "TBD"/"add error handling"/"similar to Task N". The "wired in Phase 4" comment in Task 1.3 is a real intermediate literal that Task 4.2 replaces with shown code. Tasks 2.2/3.3-style "no new implementation" steps are explicit (logic landed earlier) — not placeholders. -**Type consistency:** `InstallComponents{init_clarion,skills,hooks}` + `from_flags(skills,hooks,all)` consistent across cli.rs/install.rs/main.rs. `install_skill_pack -> SkillInstallReport{copied}` consistent across skill_pack.rs/install.rs/hook.rs. `project_snapshot(conn, project_root) -> ProjectSnapshot{db_present,entity_count,subsystem_count,finding_count,staleness,last_analyzed_at}` + `Staleness{NeverAnalyzed,Stale,Fresh,Unknown}` (serde snake_case) + `missing_db_snapshot()` consistent across snapshot.rs/hook.rs/lib.rs. `merge_session_start_hook(&mut Value)->bool` + `install_session_start_hook(&Path)->Result` + `HOOK_COMMAND` consistent across hooks_settings.rs/install.rs. `HookCommand::SessionStart{path}` consistent across cli.rs/main.rs/hook.rs. `CLARION_WORKFLOW_SKILL`/`SERVER_INSTRUCTIONS`/`resources_list`/`prompts_list`/`prompts_get`/`handle_resources_read`/`context_snapshot_json` all defined in lib.rs Phase 5. ✓ +**Type consistency:** `InstallComponents{init_loomweave,skills,hooks}` + `from_flags(skills,hooks,all)` consistent across cli.rs/install.rs/main.rs. `install_skill_pack -> SkillInstallReport{copied}` consistent across skill_pack.rs/install.rs/hook.rs. `project_snapshot(conn, project_root) -> ProjectSnapshot{db_present,entity_count,subsystem_count,finding_count,staleness,last_analyzed_at}` + `Staleness{NeverAnalyzed,Stale,Fresh,Unknown}` (serde snake_case) + `missing_db_snapshot()` consistent across snapshot.rs/hook.rs/lib.rs. `merge_session_start_hook(&mut Value)->bool` + `install_session_start_hook(&Path)->Result` + `HOOK_COMMAND` consistent across hooks_settings.rs/install.rs. `HookCommand::SessionStart{path}` consistent across cli.rs/main.rs/hook.rs. `LOOMWEAVE_WORKFLOW_SKILL`/`SERVER_INSTRUCTIONS`/`resources_list`/`prompts_list`/`prompts_get`/`handle_resources_read`/`context_snapshot_json` all defined in lib.rs Phase 5. ✓ diff --git a/docs/superpowers/plans/archive/2026-05-30-flow-b-wardline-finding-reconciliation.md b/docs/superpowers/plans/archive/2026-05-30-flow-b-wardline-finding-reconciliation.md index 0e7dfd6f..f218155d 100644 --- a/docs/superpowers/plans/archive/2026-05-30-flow-b-wardline-finding-reconciliation.md +++ b/docs/superpowers/plans/archive/2026-05-30-flow-b-wardline-finding-reconciliation.md @@ -4,35 +4,35 @@ **Goal:** When `issues_for` / `orientation_pack` runs for an entity, surface the Wardline findings Filigree holds for that entity, reconciled by qualname — enrich-only, no new Filigree route. -**Architecture:** A new `wardline_reconcile` module does pure qualname matching (`metadata.wardline.qualname` == the entity_id's segment-3 qualname). The Filigree HTTP client (`filigree.rs`) gains a two-hop read (`GET /api/loom/files?path_prefix=` → Filigree `file_id`, then `GET /api/loom/findings?scan_source=wardline&file_id=`). The two MCP tools call the client, reconcile, and attach a `wardline_findings` section. If Filigree is unreachable the section degrades to `unavailable`; the tool never fails. +**Architecture:** A new `wardline_reconcile` module does pure qualname matching (`metadata.wardline.qualname` == the entity_id's segment-3 qualname). The Filigree HTTP client (`filigree.rs`) gains a two-hop read (`GET /api/weft/files?path_prefix=` → Filigree `file_id`, then `GET /api/weft/findings?scan_source=wardline&file_id=`). The two MCP tools call the client, reconcile, and attach a `wardline_findings` section. If Filigree is unreachable the section degrades to `unavailable`; the tool never fails. -**Tech Stack:** Rust, `reqwest::blocking`, `serde`/`serde_json`, `cargo nextest`. Spec: `docs/superpowers/specs/2026-05-30-clarion-consume-wardline-data-design.md`. Tracked by `clarion-71f995b88a`. +**Tech Stack:** Rust, `reqwest::blocking`, `serde`/`serde_json`, `cargo nextest`. Spec: `docs/superpowers/specs/2026-05-30-loomweave-consume-wardline-data-design.md`. Tracked by `clarion-71f995b88a`. --- ## File Structure -- **Create** `crates/clarion-mcp/src/wardline_reconcile.rs` — pure reconciliation: qualname extraction from an entity_id, qualname extraction from a finding's metadata, the `ResolutionConfidence` enum, and `reconcile_for_entity`. No I/O; fully unit-testable. -- **Modify** `crates/clarion-mcp/src/filigree.rs` — add the `WardlineFinding` / `LoomFileRecord` wire types + parsers, the `loom_files_url` / `loom_findings_url` builders, a private `get_json` helper, and `FiligreeLookup::wardline_findings_for_path` (default `Ok(vec![])`; HTTP client does the two-hop). -- **Modify** `crates/clarion-mcp/src/lib.rs` — register `mod wardline_reconcile`; build the `wardline_findings` section in `tool_issues_for` and `tool_orientation_pack`. -- **Modify** `docs/federation/contracts.md` — pin the two consumed loom read routes. +- **Create** `crates/loomweave-mcp/src/wardline_reconcile.rs` — pure reconciliation: qualname extraction from an entity_id, qualname extraction from a finding's metadata, the `ResolutionConfidence` enum, and `reconcile_for_entity`. No I/O; fully unit-testable. +- **Modify** `crates/loomweave-mcp/src/filigree.rs` — add the `WardlineFinding` / `WeftFileRecord` wire types + parsers, the `weft_files_url` / `weft_findings_url` builders, a private `get_json` helper, and `FiligreeLookup::wardline_findings_for_path` (default `Ok(vec![])`; HTTP client does the two-hop). +- **Modify** `crates/loomweave-mcp/src/lib.rs` — register `mod wardline_reconcile`; build the `wardline_findings` section in `tool_issues_for` and `tool_orientation_pack`. +- **Modify** `docs/federation/contracts.md` — pin the two consumed weft read routes. -Each task is independently committable. Run the full crate gate after the last code task: `cargo fmt --all -- --check`, `cargo clippy -p clarion-mcp --all-targets -- -D warnings`, `cargo nextest run -p clarion-mcp`. +Each task is independently committable. Run the full crate gate after the last code task: `cargo fmt --all -- --check`, `cargo clippy -p loomweave-mcp --all-targets -- -D warnings`, `cargo nextest run -p loomweave-mcp`. --- -## Task 1: Wardline + loom-file wire types and parsers +## Task 1: Wardline + weft-file wire types and parsers **Files:** -- Modify: `crates/clarion-mcp/src/filigree.rs` (add types near the other `Deserialize` structs, ~line 38; tests in the existing `#[cfg(test)] mod tests`) +- Modify: `crates/loomweave-mcp/src/filigree.rs` (add types near the other `Deserialize` structs, ~line 38; tests in the existing `#[cfg(test)] mod tests`) - [ ] **Step 1: Write the failing parse tests** -Add to `mod tests` in `crates/clarion-mcp/src/filigree.rs`: +Add to `mod tests` in `crates/loomweave-mcp/src/filigree.rs`: ```rust #[test] -fn parses_loom_findings_list_envelope() { +fn parses_weft_findings_list_envelope() { let resp = parse_wardline_findings_response( r#"{"items":[ {"finding_id":"f-1","file_id":"file-9","severity":"high","status":"open", @@ -56,8 +56,8 @@ fn parses_loom_findings_list_envelope() { } #[test] -fn parses_loom_files_list_envelope() { - let resp = parse_loom_files_response( +fn parses_weft_files_list_envelope() { + let resp = parse_weft_files_response( r#"{"items":[ {"file_id":"file-9","path":"src/demo.py","language":"python","file_type":"source"}, {"file_id":"file-10","path":"src/demo_helpers.py","language":"python","file_type":"source"} @@ -72,16 +72,16 @@ fn parses_loom_files_list_envelope() { - [ ] **Step 2: Run the tests to verify they fail** -Run: `cargo nextest run -p clarion-mcp filigree::tests::parses_loom` -Expected: FAIL to compile — `parse_wardline_findings_response`, `parse_loom_files_response`, and the types are not defined. +Run: `cargo nextest run -p loomweave-mcp filigree::tests::parses_weft` +Expected: FAIL to compile — `parse_wardline_findings_response`, `parse_weft_files_response`, and the types are not defined. - [ ] **Step 3: Add the types and parsers** -Add to `crates/clarion-mcp/src/filigree.rs` (after `EntityAssociation`, ~line 38). Extra fields in the Filigree rows are ignored by serde, so this reads only the subset Clarion surfaces: +Add to `crates/loomweave-mcp/src/filigree.rs` (after `EntityAssociation`, ~line 38). Extra fields in the Filigree rows are ignored by serde, so this reads only the subset Loomweave surfaces: ```rust -/// One Wardline finding as Clarion surfaces it — the subset of Filigree's -/// `ScanFindingLoom` (`GET /api/loom/findings`) used for read-time +/// One Wardline finding as Loomweave surfaces it — the subset of Filigree's +/// `ScanFindingWeft` (`GET /api/weft/findings`) used for read-time /// reconciliation. Unknown fields are ignored so Filigree can grow the row. #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub struct WardlineFinding { @@ -111,18 +111,18 @@ pub struct WardlineFindingsResponse { pub items: Vec, } -/// One row of `GET /api/loom/files` — only the fields needed to map a path to +/// One row of `GET /api/weft/files` — only the fields needed to map a path to /// Filigree's `file_id`. #[derive(Debug, Clone, PartialEq, Deserialize)] -pub struct LoomFileRecord { +pub struct WeftFileRecord { pub file_id: String, pub path: String, } #[derive(Debug, Clone, Deserialize)] -pub struct LoomFilesResponse { +pub struct WeftFilesResponse { #[serde(default)] - pub items: Vec, + pub items: Vec, } pub fn parse_wardline_findings_response( @@ -131,21 +131,21 @@ pub fn parse_wardline_findings_response( serde_json::from_str(body).map_err(FiligreeContractError::from) } -pub fn parse_loom_files_response(body: &str) -> Result { +pub fn parse_weft_files_response(body: &str) -> Result { serde_json::from_str(body).map_err(FiligreeContractError::from) } ``` - [ ] **Step 4: Run the tests to verify they pass** -Run: `cargo nextest run -p clarion-mcp filigree::tests::parses_loom` +Run: `cargo nextest run -p loomweave-mcp filigree::tests::parses_weft` Expected: PASS (2 tests). - [ ] **Step 5: Commit** ```bash -git add crates/clarion-mcp/src/filigree.rs -git commit -m "feat(mcp): Wardline finding + loom-file wire types (Flow B, clarion-71f995b88a)" +git add crates/loomweave-mcp/src/filigree.rs +git commit -m "feat(mcp): Wardline finding + weft-file wire types (Flow B, clarion-71f995b88a)" ``` --- @@ -153,20 +153,20 @@ git commit -m "feat(mcp): Wardline finding + loom-file wire types (Flow B, clari ## Task 2: Pure qualname reconciliation module **Files:** -- Create: `crates/clarion-mcp/src/wardline_reconcile.rs` -- Modify: `crates/clarion-mcp/src/lib.rs` (add `mod wardline_reconcile;` next to the other `mod` declarations near the top) +- Create: `crates/loomweave-mcp/src/wardline_reconcile.rs` +- Modify: `crates/loomweave-mcp/src/lib.rs` (add `mod wardline_reconcile;` next to the other `mod` declarations near the top) - [ ] **Step 1: Write the failing tests** -Create `crates/clarion-mcp/src/wardline_reconcile.rs` with only the tests first: +Create `crates/loomweave-mcp/src/wardline_reconcile.rs` with only the tests first: ```rust -//! Reconcile Wardline findings to Clarion entities by qualname (Flow B). +//! Reconcile Wardline findings to Loomweave entities by qualname (Flow B). //! //! `metadata.wardline.qualname` is the pre-composed dotted name, which for a //! function/method entity is byte-identical to the entity_id's segment-3 //! `canonical_qualified_name` (proven by `fixtures/entity_id.json`). Matching is -//! therefore a local string compare against Clarion's own catalog — no oracle. +//! therefore a local string compare against Loomweave's own catalog — no oracle. #[cfg(test)] mod tests { @@ -232,17 +232,17 @@ mod tests { - [ ] **Step 2: Run the tests to verify they fail** -Run: `cargo nextest run -p clarion-mcp wardline_reconcile` +Run: `cargo nextest run -p loomweave-mcp wardline_reconcile` Expected: FAIL to compile — module not registered, symbols undefined. - [ ] **Step 3: Implement the module** -Add `mod wardline_reconcile;` to `crates/clarion-mcp/src/lib.rs` (with the other module declarations), then prepend the implementation above the `#[cfg(test)]` block in `wardline_reconcile.rs`: +Add `mod wardline_reconcile;` to `crates/loomweave-mcp/src/lib.rs` (with the other module declarations), then prepend the implementation above the `#[cfg(test)]` block in `wardline_reconcile.rs`: ```rust use crate::filigree::WardlineFinding; -/// A Wardline finding's resolution against a Clarion entity. v1 produces only +/// A Wardline finding's resolution against a Loomweave entity. v1 produces only /// `Exact` (byte-equal qualname) or `None`. `Heuristic` is reserved for a future /// best-effort normalization pass and is never returned yet — kept in the enum /// so the wire shape is stable when it lands. @@ -316,13 +316,13 @@ pub fn reconcile_for_entity(entity_id: &str, findings: Vec) -> - [ ] **Step 4: Run the tests to verify they pass** -Run: `cargo nextest run -p clarion-mcp wardline_reconcile` +Run: `cargo nextest run -p loomweave-mcp wardline_reconcile` Expected: PASS (4 tests). - [ ] **Step 5: Commit** ```bash -git add crates/clarion-mcp/src/wardline_reconcile.rs crates/clarion-mcp/src/lib.rs +git add crates/loomweave-mcp/src/wardline_reconcile.rs crates/loomweave-mcp/src/lib.rs git commit -m "feat(mcp): pure Wardline qualname reconciliation module (Flow B)" ``` @@ -331,22 +331,22 @@ git commit -m "feat(mcp): pure Wardline qualname reconciliation module (Flow B)" ## Task 3: Filigree client two-hop fetch **Files:** -- Modify: `crates/clarion-mcp/src/filigree.rs` (URL builders near `entity_associations_url` ~line 286; `get_json` helper + trait method + HTTP impl; mock-server test in `mod tests`) +- Modify: `crates/loomweave-mcp/src/filigree.rs` (URL builders near `entity_associations_url` ~line 286; `get_json` helper + trait method + HTTP impl; mock-server test in `mod tests`) - [ ] **Step 1: Write the failing URL-builder + mock-server tests** -Add to `mod tests` in `crates/clarion-mcp/src/filigree.rs`: +Add to `mod tests` in `crates/loomweave-mcp/src/filigree.rs`: ```rust #[test] -fn builds_loom_url_builders_with_encoding() { +fn builds_weft_url_builders_with_encoding() { assert_eq!( - loom_files_url("http://127.0.0.1:8542/", "wardline", "src/demo.py"), - "http://127.0.0.1:8542/api/loom/files?scan_source=wardline&path_prefix=src%2Fdemo.py" + weft_files_url("http://127.0.0.1:8542/", "wardline", "src/demo.py"), + "http://127.0.0.1:8542/api/weft/files?scan_source=wardline&path_prefix=src%2Fdemo.py" ); assert_eq!( - loom_findings_url("http://127.0.0.1:8542/", "wardline", "file-9"), - "http://127.0.0.1:8542/api/loom/findings?scan_source=wardline&file_id=file-9" + weft_findings_url("http://127.0.0.1:8542/", "wardline", "file-9"), + "http://127.0.0.1:8542/api/weft/findings?scan_source=wardline&file_id=file-9" ); } @@ -355,21 +355,21 @@ fn wardline_findings_for_path_does_two_hops_and_exact_path_filter() { let listener = TcpListener::bind("127.0.0.1:0").expect("bind test server"); let addr = listener.local_addr().expect("local addr"); let handle = std::thread::spawn(move || { - // Hop 1: GET /api/loom/files — path_prefix matches two files; the + // Hop 1: GET /api/weft/files — path_prefix matches two files; the // exact-path filter must pick file-9, not the helpers file. let (mut s1, _) = listener.accept().expect("accept files"); let mut buf = [0_u8; 4096]; let n = s1.read(&mut buf).expect("read files req"); let req = String::from_utf8_lossy(&buf[..n]); - assert!(req.contains("GET /api/loom/files?scan_source=wardline&path_prefix=src%2Fdemo.py HTTP/1.1")); + assert!(req.contains("GET /api/weft/files?scan_source=wardline&path_prefix=src%2Fdemo.py HTTP/1.1")); let body = r#"{"items":[{"file_id":"file-9","path":"src/demo.py","language":"python","file_type":"source"},{"file_id":"file-10","path":"src/demo.py.bak","language":"python","file_type":"source"}],"has_more":false}"#; write!(s1, "HTTP/1.1 200 OK\r\ncontent-length: {}\r\n\r\n{}", body.len(), body).unwrap(); - // Hop 2: GET /api/loom/findings for file-9. + // Hop 2: GET /api/weft/findings for file-9. let (mut s2, _) = listener.accept().expect("accept findings"); let n = s2.read(&mut buf).expect("read findings req"); let req = String::from_utf8_lossy(&buf[..n]); - assert!(req.contains("GET /api/loom/findings?scan_source=wardline&file_id=file-9 HTTP/1.1")); + assert!(req.contains("GET /api/weft/findings?scan_source=wardline&file_id=file-9 HTTP/1.1")); let body = r#"{"items":[{"finding_id":"f-1","file_id":"file-9","severity":"high","status":"open","scan_source":"wardline","rule_id":"WLN-TAINT-001","message":"sink","suggestion":"","scan_run_id":"r-1","line_start":12,"line_end":12,"fingerprint":"fp","issue_id":null,"seen_count":1,"metadata":{"wardline":{"qualname":"demo.Foo.bar"}},"data_warnings":[]}],"has_more":false}"#; write!(s2, "HTTP/1.1 200 OK\r\ncontent-length: {}\r\n\r\n{}", body.len(), body).unwrap(); }); @@ -385,26 +385,26 @@ fn wardline_findings_for_path_does_two_hops_and_exact_path_filter() { - [ ] **Step 2: Run the tests to verify they fail** -Run: `cargo nextest run -p clarion-mcp filigree::tests::wardline_findings_for_path filigree::tests::builds_loom_url` -Expected: FAIL to compile — `loom_files_url`, `loom_findings_url`, and `wardline_findings_for_path` undefined. +Run: `cargo nextest run -p loomweave-mcp filigree::tests::wardline_findings_for_path filigree::tests::builds_weft_url` +Expected: FAIL to compile — `weft_files_url`, `weft_findings_url`, and `wardline_findings_for_path` undefined. - [ ] **Step 3: Add URL builders** -Add to `crates/clarion-mcp/src/filigree.rs` near `entity_associations_url`: +Add to `crates/loomweave-mcp/src/filigree.rs` near `entity_associations_url`: ```rust -pub fn loom_files_url(base_url: &str, scan_source: &str, path_prefix: &str) -> String { +pub fn weft_files_url(base_url: &str, scan_source: &str, path_prefix: &str) -> String { format!( - "{}/api/loom/files?scan_source={}&path_prefix={}", + "{}/api/weft/files?scan_source={}&path_prefix={}", base_url.trim_end_matches('/'), percent_encode_query_value(scan_source), percent_encode_query_value(path_prefix) ) } -pub fn loom_findings_url(base_url: &str, scan_source: &str, file_id: &str) -> String { +pub fn weft_findings_url(base_url: &str, scan_source: &str, file_id: &str) -> String { format!( - "{}/api/loom/findings?scan_source={}&file_id={}", + "{}/api/weft/findings?scan_source={}&file_id={}", base_url.trim_end_matches('/'), percent_encode_query_value(scan_source), percent_encode_query_value(file_id) @@ -470,29 +470,29 @@ Add to `impl FiligreeLookup for FiligreeHttpClient`: ) -> Result, FiligreeClientError> { // Hop 1: path -> Filigree file_id. path_prefix is a prefix filter, so // take only the row whose path is byte-exact. - let files: LoomFilesResponse = - self.get_json(&loom_files_url(&self.base_url, "wardline", path))?; + let files: WeftFilesResponse = + self.get_json(&weft_files_url(&self.base_url, "wardline", path))?; let Some(file_id) = files.items.into_iter().find(|f| f.path == path).map(|f| f.file_id) else { return Ok(Vec::new()); }; // Hop 2: file_id -> wardline findings. let findings: WardlineFindingsResponse = - self.get_json(&loom_findings_url(&self.base_url, "wardline", &file_id))?; + self.get_json(&weft_findings_url(&self.base_url, "wardline", &file_id))?; Ok(findings.items) } ``` - [ ] **Step 6: Run the tests to verify they pass** -Run: `cargo nextest run -p clarion-mcp filigree::tests::wardline_findings_for_path filigree::tests::builds_loom_url` +Run: `cargo nextest run -p loomweave-mcp filigree::tests::wardline_findings_for_path filigree::tests::builds_weft_url` Expected: PASS (2 tests). - [ ] **Step 7: Commit** ```bash -git add crates/clarion-mcp/src/filigree.rs -git commit -m "feat(mcp): Filigree two-hop wardline_findings_for_path (loom files + findings)" +git add crates/loomweave-mcp/src/filigree.rs +git commit -m "feat(mcp): Filigree two-hop wardline_findings_for_path (weft files + findings)" ``` --- @@ -500,7 +500,7 @@ git commit -m "feat(mcp): Filigree two-hop wardline_findings_for_path (loom file ## Task 4: Wire the `wardline_findings` section into `issues_for` **Files:** -- Modify: `crates/clarion-mcp/src/lib.rs` (`tool_issues_for`, ~line 1007–1126; add a helper `wardline_section_for_entity`; integration test in the lib.rs test module) +- Modify: `crates/loomweave-mcp/src/lib.rs` (`tool_issues_for`, ~line 1007–1126; add a helper `wardline_section_for_entity`; integration test in the lib.rs test module) - [ ] **Step 1: Write the failing integration tests** @@ -589,12 +589,12 @@ impl crate::filigree::FiligreeLookup for ErroringWardline { - [ ] **Step 2: Run the tests to verify they fail** -Run: `cargo nextest run -p clarion-mcp issues_for_attaches_exact_wardline_findings issues_for_degrades_when_wardline_fetch_errors` +Run: `cargo nextest run -p loomweave-mcp issues_for_attaches_exact_wardline_findings issues_for_degrades_when_wardline_fetch_errors` Expected: FAIL — `out["wardline_findings"]` is null (section not built yet). - [ ] **Step 3: Add the section helper** -Add a free function to `crates/clarion-mcp/src/lib.rs` (near the other envelope helpers, e.g. by `issues_unavailable`): +Add a free function to `crates/loomweave-mcp/src/lib.rs` (near the other envelope helpers, e.g. by `issues_unavailable`): ```rust /// Build the `wardline_findings` enrich section for one entity. Enrich-only: @@ -679,13 +679,13 @@ Note: if `read` exposes the requested id under a different field name than `requ - [ ] **Step 5: Run the tests to verify they pass** -Run: `cargo nextest run -p clarion-mcp issues_for_attaches_exact_wardline_findings issues_for_degrades_when_wardline_fetch_errors` +Run: `cargo nextest run -p loomweave-mcp issues_for_attaches_exact_wardline_findings issues_for_degrades_when_wardline_fetch_errors` Expected: PASS (2 tests). - [ ] **Step 6: Commit** ```bash -git add crates/clarion-mcp/src/lib.rs +git add crates/loomweave-mcp/src/lib.rs git commit -m "feat(mcp): attach reconciled wardline_findings section to issues_for (Flow B)" ``` @@ -694,7 +694,7 @@ git commit -m "feat(mcp): attach reconciled wardline_findings section to issues_ ## Task 5: Wire the section into `orientation_pack` **Files:** -- Modify: `crates/clarion-mcp/src/lib.rs` (`tool_orientation_pack`; integration test) +- Modify: `crates/loomweave-mcp/src/lib.rs` (`tool_orientation_pack`; integration test) - [ ] **Step 1: Write the failing test** @@ -714,7 +714,7 @@ Reuse the `FakeWardline` / `wf` helpers from Task 4. `orientation_pack_test_serv - [ ] **Step 2: Run the test to verify it fails** -Run: `cargo nextest run -p clarion-mcp orientation_pack_includes_wardline_findings` +Run: `cargo nextest run -p loomweave-mcp orientation_pack_includes_wardline_findings` Expected: FAIL — `wardline_findings` key absent from the pack. - [ ] **Step 3: Attach the section in `tool_orientation_pack`** @@ -743,22 +743,22 @@ Adapt `primary_entity` / `packet` to the actual local variable names in `tool_or - [ ] **Step 4: Run the test to verify it passes** -Run: `cargo nextest run -p clarion-mcp orientation_pack_includes_wardline_findings` +Run: `cargo nextest run -p loomweave-mcp orientation_pack_includes_wardline_findings` Expected: PASS. - [ ] **Step 5: Run the full crate gate** ```bash cargo fmt --all -- --check -cargo clippy -p clarion-mcp --all-targets -- -D warnings -cargo nextest run -p clarion-mcp +cargo clippy -p loomweave-mcp --all-targets -- -D warnings +cargo nextest run -p loomweave-mcp ``` Expected: all green. - [ ] **Step 6: Commit** ```bash -git add crates/clarion-mcp/src/lib.rs +git add crates/loomweave-mcp/src/lib.rs git commit -m "feat(mcp): attach reconciled wardline_findings section to orientation_pack (Flow B)" ``` @@ -777,43 +777,43 @@ Append after the existing "Consumed Filigree route: issue detail (enrichment)" s ## Consumed Filigree route: Wardline findings (read-time reconciliation) Flow B (read-time Wardline finding reconciliation) consumes two existing Filigree -*loom* read routes — no new route is requested. Both are enrich-only: if either +*weft* read routes — no new route is requested. Both are enrich-only: if either is unreachable the `wardline_findings` section degrades to `result_kind: "unavailable"` and the tool returns normally. -1. `GET /api/loom/files?scan_source=wardline&path_prefix=` → unified - `ListResponse[FileRecordLoom]` (`{items, has_more, next_offset?}`). Clarion +1. `GET /api/weft/files?scan_source=wardline&path_prefix=` → unified + `ListResponse[FileRecordWeft]` (`{items, has_more, next_offset?}`). Loomweave takes the item whose `path` is byte-exact (the filter is a prefix) to obtain - Filigree's `file_id`. Pinned by Filigree `tests/fixtures/contracts/loom/files.json`. -2. `GET /api/loom/findings?scan_source=wardline&file_id=` → - `ListResponse[ScanFindingLoom]`. Clarion reads `rule_id`, `message`, + Filigree's `file_id`. Pinned by Filigree `tests/fixtures/contracts/weft/files.json`. +2. `GET /api/weft/findings?scan_source=wardline&file_id=` → + `ListResponse[ScanFindingWeft]`. Loomweave reads `rule_id`, `message`, `severity`, `status`, `line_start/line_end`, `fingerprint`, and `metadata` (the reconciliation key `metadata.wardline.qualname`). Pinned by Filigree - `tests/fixtures/contracts/loom/findings.json`. + `tests/fixtures/contracts/weft/findings.json`. Reconciliation: `metadata.wardline.qualname` is matched byte-exact against the entity_id's segment-3 `canonical_qualified_name` (`python:function:`), per the §"Wardline qualname normalization" contract. A match is `resolution_confidence: exact`; an unresolved qualname is `none`. (`heuristic` is reserved.) `POST /api/v1/files:resolve` is **not** used here — it is a route -Clarion *exposes*, not one it consumes. +Loomweave *exposes*, not one it consumes. ```` - [ ] **Step 2: Commit** ```bash git add docs/federation/contracts.md -git commit -m "docs(federation): pin the two consumed loom routes for Flow B reconciliation" +git commit -m "docs(federation): pin the two consumed weft routes for Flow B reconciliation" ``` --- ## Self-Review -**Spec coverage** (against `2026-05-30-clarion-consume-wardline-data-design.md`): +**Spec coverage** (against `2026-05-30-loomweave-consume-wardline-data-design.md`): - §3 read-time lazy reconciliation → Tasks 2 (match), 4/5 (attach). ✓ - §3 `resolution_confidence` tiers → Task 2 (`Exact`/`None`; `Heuristic` reserved, documented). ✓ -- §4 two-hop via existing loom routes → Task 3. ✓ +- §4 two-hop via existing weft routes → Task 3. ✓ - §5 enrich-only / no fabrication / omitted count → Tasks 2 (omitted), 4 (degrade test + no_matches). ✓ - §6 hermetic tests → injected `FakeWardline`/`ErroringWardline` (Tasks 4/5), mock TcpListener (Task 3). ✓ - §10.3 pin consumed routes in contracts.md → Task 6. ✓ diff --git a/docs/superpowers/plans/archive/2026-05-31-clarion-core-errors.md b/docs/superpowers/plans/archive/2026-05-31-loomweave-core-errors.md similarity index 79% rename from docs/superpowers/plans/archive/2026-05-31-clarion-core-errors.md rename to docs/superpowers/plans/archive/2026-05-31-loomweave-core-errors.md index 996984ae..aaa2fe2d 100644 --- a/docs/superpowers/plans/archive/2026-05-31-clarion-core-errors.md +++ b/docs/superpowers/plans/archive/2026-05-31-loomweave-core-errors.md @@ -1,12 +1,12 @@ -# clarion-core::errors Shared Error Vocabulary — Implementation Plan +# loomweave-core::errors Shared Error Vocabulary — Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. -**Goal:** Make `clarion-core::errors` the single typed source of truth for both error-code wire vocabularies (HTTP federation + MCP tool envelope), killing the drift smell (V11-ARCH-01) without changing any wire bytes. +**Goal:** Make `loomweave-core::errors` the single typed source of truth for both error-code wire vocabularies (HTTP federation + MCP tool envelope), killing the drift smell (V11-ARCH-01) without changing any wire bytes. -**Architecture:** Co-locate two *separate* typed enums — `HttpErrorCode` (moved verbatim from `http_read.rs`) and a new `McpErrorCode` (replacing ~47 bare kebab string literals) — in one `clarion-core` module, with drift tests pinning every wire string. Spellings stay per-surface; nothing is merged. HTTP status stays per-call-site. See `docs/superpowers/specs/2026-05-31-clarion-core-errors-design.md`. +**Architecture:** Co-locate two *separate* typed enums — `HttpErrorCode` (moved verbatim from `http_read.rs`) and a new `McpErrorCode` (replacing ~47 bare kebab string literals) — in one `loomweave-core` module, with drift tests pinning every wire string. Spellings stay per-surface; nothing is merged. HTTP status stays per-call-site. See `docs/superpowers/specs/2026-05-31-loomweave-core-errors-design.md`. -**Tech Stack:** Rust workspace; `serde` (already a `clarion-core` dep); `thiserror`; `cargo nextest`; `clippy -D warnings`. +**Tech Stack:** Rust workspace; `serde` (already a `loomweave-core` dep); `thiserror`; `cargo nextest`; `clippy -D warnings`. **Project gate (every commit should leave this green; Task 6 verifies the whole thing):** ```bash @@ -25,42 +25,42 @@ Note: shell startup on this machine is slow; expect Bash output to batch. ## File map -- **Create:** `crates/clarion-core/src/errors.rs` — both enums, `as_str()` on each, module rustdoc (incl. the MCP→HTTP narrowing table), drift tests. -- **Modify:** `crates/clarion-core/src/lib.rs` — `pub mod errors;` + facade re-export. -- **Modify:** `crates/clarion-cli/src/http_read.rs` — delete local `enum ErrorCode`; alias to `clarion_core::HttpErrorCode`. -- **Modify:** `crates/clarion-mcp/src/lib.rs` — retype `tool_error_envelope*` + `InferredDispatchFailure.code`; convert ~47 call sites; two `== ""` comparisons; `token_ceiling_envelope` literal. -- **Create:** `docs/clarion/adr/ADR-037-shared-error-vocabulary.md`; **Modify:** ADR index + `docs/federation/contracts.md` (additive pointer). +- **Create:** `crates/loomweave-core/src/errors.rs` — both enums, `as_str()` on each, module rustdoc (incl. the MCP→HTTP narrowing table), drift tests. +- **Modify:** `crates/loomweave-core/src/lib.rs` — `pub mod errors;` + facade re-export. +- **Modify:** `crates/loomweave-cli/src/http_read.rs` — delete local `enum ErrorCode`; alias to `loomweave_core::HttpErrorCode`. +- **Modify:** `crates/loomweave-mcp/src/lib.rs` — retype `tool_error_envelope*` + `InferredDispatchFailure.code`; convert ~47 call sites; two `== ""` comparisons; `token_ceiling_envelope` literal. +- **Create:** `docs/loomweave/adr/ADR-037-shared-error-vocabulary.md`; **Modify:** ADR index + `docs/federation/contracts.md` (additive pointer). --- -## Task 1: Create `clarion-core::errors` with both enums + drift tests +## Task 1: Create `loomweave-core::errors` with both enums + drift tests **Files:** -- Create: `crates/clarion-core/src/errors.rs` -- Modify: `crates/clarion-core/src/lib.rs:9-13` (add module + re-export) +- Create: `crates/loomweave-core/src/errors.rs` +- Modify: `crates/loomweave-core/src/lib.rs:9-13` (add module + re-export) - [ ] **Step 1: Create `errors.rs` with both enums, `as_str()`, rustdoc, and drift tests** -Create `crates/clarion-core/src/errors.rs` with exactly this content: +Create `crates/loomweave-core/src/errors.rs` with exactly this content: ```rust -//! Shared error-code vocabularies for Clarion's two structured wire surfaces. +//! Shared error-code vocabularies for Loomweave's two structured wire surfaces. //! -//! Clarion emits machine-routable error codes on two **independent** surfaces. +//! Loomweave emits machine-routable error codes on two **independent** surfaces. //! This module is the single typed source of truth for both, so they cannot //! silently drift and so the MCP side gets compiler-checked codes instead of //! bare string literals. The two vocabularies are deliberately **not merged**: //! they serve different transports, different consumers, and different //! granularities (see the narrowing table below). HTTP status is intentionally //! chosen per endpoint and is therefore *not* derivable from the code — see -//! `clarion-cli`'s `classify_read_error` and ADR-037. +//! `loomweave-cli`'s `classify_read_error` and ADR-037. //! //! # Surfaces //! -//! * [`HttpErrorCode`] — the federation HTTP read API (`crates/clarion-cli`). +//! * [`HttpErrorCode`] — the federation HTTP read API (`crates/loomweave-cli`). //! SCREAMING_SNAKE on the wire; frozen contract in `docs/federation/contracts.md` //! and ADR-034; switched on by Filigree / Wardline clients. -//! * [`McpErrorCode`] — the MCP tool-error envelope (`crates/clarion-mcp`). +//! * [`McpErrorCode`] — the MCP tool-error envelope (`crates/loomweave-mcp`). //! kebab-case on the wire; consumed by consult-mode agents; pinned by tests. //! //! # MCP → HTTP narrowing relationship (documentation only) @@ -223,7 +223,7 @@ mod tests { #[test] fn mcp_error_code_wire_strings_are_pinned() { // These kebab spellings are stable on the MCP wire (pinned by - // clarion-mcp/tests/storage_tools.rs and relied on by consult agents). + // loomweave-mcp/tests/storage_tools.rs and relied on by consult agents). assert_eq!(McpErrorCode::InvalidPath.as_str(), "invalid-path"); assert_eq!(McpErrorCode::EntityNotFound.as_str(), "entity-not-found"); assert_eq!(McpErrorCode::StorageError.as_str(), "storage-error"); @@ -248,7 +248,7 @@ mod tests { - [ ] **Step 2: Wire the module into `lib.rs`** -In `crates/clarion-core/src/lib.rs`, add `pub mod errors;` to the module list (after `pub mod entity_id;`, keeping alphabetical-ish order) and add the facade re-export. The module list near line 9 becomes: +In `crates/loomweave-core/src/lib.rs`, add `pub mod errors;` to the module list (after `pub mod entity_id;`, keeping alphabetical-ish order) and add the facade re-export. The module list near line 9 becomes: ```rust pub mod entity_id; @@ -267,7 +267,7 @@ pub use errors::{HttpErrorCode, McpErrorCode}; Run: ```bash -cargo nextest run -p clarion-core errors::tests +cargo nextest run -p loomweave-core errors::tests ``` Expected: 3 tests pass (`http_error_code_as_str_matches_serialize_output`, `http_error_code_wire_strings_are_pinned`, `mcp_error_code_wire_strings_are_pinned`). @@ -275,15 +275,15 @@ Expected: 3 tests pass (`http_error_code_as_str_matches_serialize_output`, `http Run: ```bash -cargo clippy -p clarion-core --all-targets --all-features -- -D warnings +cargo clippy -p loomweave-core --all-targets --all-features -- -D warnings ``` Expected: no warnings, no errors. (The enums are `pub` and re-exported, so unused-by-consumers is not flagged.) - [ ] **Step 5: Commit** ```bash -git add crates/clarion-core/src/errors.rs crates/clarion-core/src/lib.rs -git commit -m "feat(core): add clarion-core::errors with HttpErrorCode + McpErrorCode +git add crates/loomweave-core/src/errors.rs crates/loomweave-core/src/lib.rs +git commit -m "feat(core): add loomweave-core::errors with HttpErrorCode + McpErrorCode Single typed source of truth for both wire error vocabularies; drift tests pin every wire string. Consumers migrate in follow-ups (V11-ARCH-01). @@ -293,14 +293,14 @@ Co-Authored-By: Claude Opus 4.8 (1M context) " --- -## Task 2: Migrate the HTTP read API to `clarion_core::HttpErrorCode` +## Task 2: Migrate the HTTP read API to `loomweave_core::HttpErrorCode` **Files:** -- Modify: `crates/clarion-cli/src/http_read.rs:728-746` (delete local enum), add a `use` alias. +- Modify: `crates/loomweave-cli/src/http_read.rs:728-746` (delete local enum), add a `use` alias. - [ ] **Step 1: Delete the local enum and alias to the shared one** -In `crates/clarion-cli/src/http_read.rs`, delete the entire local definition (lines 728-746): +In `crates/loomweave-cli/src/http_read.rs`, delete the entire local definition (lines 728-746): ```rust #[derive(Debug, Copy, Clone, Serialize)] @@ -312,10 +312,10 @@ enum ErrorCode { } ``` -Add, near the top of the file with the other `use` items (e.g. beside the existing `clarion_core` / `clarion_storage` imports): +Add, near the top of the file with the other `use` items (e.g. beside the existing `loomweave_core` / `loomweave_storage` imports): ```rust -use clarion_core::HttpErrorCode as ErrorCode; +use loomweave_core::HttpErrorCode as ErrorCode; ``` All existing `ErrorCode::Variant` references (44 of them) compile unchanged through the alias. The `#[derive(Serialize)]` and SCREAMING_SNAKE rename now live on the shared type, so `ErrorResponse` / `BatchErrorItem` serialize identically. @@ -324,7 +324,7 @@ All existing `ErrorCode::Variant` references (44 of them) compile unchanged thro Run: ```bash -cargo build -p clarion-cli --bins +cargo build -p loomweave-cli --bins ``` Expected: builds. If the compiler reports an unused `Serialize` import in `http_read.rs` (the local enum was its only user there), remove that now-unused import to satisfy `-D warnings`; if other types in the file still derive `Serialize`, leave it. @@ -332,16 +332,16 @@ Expected: builds. If the compiler reports an unused `Serialize` import in `http_ Run: ```bash -cargo clippy -p clarion-cli --all-targets --all-features -- -D warnings -cargo nextest run -p clarion-cli +cargo clippy -p loomweave-cli --all-targets --all-features -- -D warnings +cargo nextest run -p loomweave-cli ``` Expected: clippy clean; all existing http_read tests pass (they assert the SCREAMING_SNAKE strings, which are unchanged). - [ ] **Step 4: Commit** ```bash -git add crates/clarion-cli/src/http_read.rs -git commit -m "refactor(cli): use clarion_core::HttpErrorCode in http_read +git add crates/loomweave-cli/src/http_read.rs +git commit -m "refactor(cli): use loomweave_core::HttpErrorCode in http_read Removes the duplicate local ErrorCode enum; wire output unchanged (V11-ARCH-01). @@ -353,16 +353,16 @@ Co-Authored-By: Claude Opus 4.8 (1M context) " ## Task 3: Migrate the MCP tool-error envelope to `McpErrorCode` **Files:** -- Modify: `crates/clarion-mcp/src/lib.rs` — envelope fns, `InferredDispatchFailure`, ~47 call sites, two string comparisons, `token_ceiling_envelope`. +- Modify: `crates/loomweave-mcp/src/lib.rs` — envelope fns, `InferredDispatchFailure`, ~47 call sites, two string comparisons, `token_ceiling_envelope`. This task is atomic: changing the `code` parameter type forces every call site in the same commit. Work through it in order, then build once. - [ ] **Step 1: Import the shared enum** -In `crates/clarion-mcp/src/lib.rs`, add to the `clarion_core` import group near the top: +In `crates/loomweave-mcp/src/lib.rs`, add to the `loomweave_core` import group near the top: ```rust -use clarion_core::McpErrorCode; +use loomweave_core::McpErrorCode; ``` - [ ] **Step 2: Retype the envelope builders to take `McpErrorCode`** @@ -474,7 +474,7 @@ In `token_ceiling_envelope`, change the hand-built `"code": "token-ceiling-excee "retryable": false }, ``` -Leave the **diagnostics** `"code": "CLA-LLM-TOKEN-CEILING-EXCEEDED"` line unchanged — that is a finding subcode, not a tool-error code. +Leave the **diagnostics** `"code": "LMWV-LLM-TOKEN-CEILING-EXCEEDED"` line unchanged — that is a finding subcode, not a tool-error code. - [ ] **Step 6: Convert every `tool_error_envelope(...)` / `InferredDispatchFailure::new(...)` call site to a variant** @@ -501,18 +501,18 @@ Replace each bare string literal in the first argument with its variant per this | `"inferred-dispatch-cancelled"` | `McpErrorCode::InferredDispatchCancelled` | | `"inferred-dispatch-timeout"` | `McpErrorCode::InferredDispatchTimeout` | -**Do NOT touch** these — they are not error codes (success-envelope `reason`/`kind`/fingerprint fields or finding subcodes): `"filigree-disabled"`, `"filigree-unreachable"`, `"filigree-client-error"` (the `issues_unavailable` `reason` arg), `"summary-scope-deferred"`, `"structural-fallback"`, `"guidance-empty"`, and any `"CLA-*"` diagnostic codes. +**Do NOT touch** these — they are not error codes (success-envelope `reason`/`kind`/fingerprint fields or finding subcodes): `"filigree-disabled"`, `"filigree-unreachable"`, `"filigree-client-error"` (the `issues_unavailable` `reason` arg), `"summary-scope-deferred"`, `"structural-fallback"`, `"guidance-empty"`, and any `"LMWV-*"` diagnostic codes. Find the call sites: ```bash -grep -n "tool_error_envelope\|tool_error_envelope_with_diagnostics\|InferredDispatchFailure::new" crates/clarion-mcp/src/lib.rs +grep -n "tool_error_envelope\|tool_error_envelope_with_diagnostics\|InferredDispatchFailure::new" crates/loomweave-mcp/src/lib.rs ``` - [ ] **Step 7: Build the crate (this is the gate that proves all call sites were converted)** Run: ```bash -cargo build -p clarion-mcp +cargo build -p loomweave-mcp ``` Expected: builds. Any remaining bare-string call site fails as a type error (`expected McpErrorCode, found &str`) naming the exact line — fix it using the table above and rebuild. @@ -520,16 +520,16 @@ Expected: builds. Any remaining bare-string call site fails as a type error (`ex Run: ```bash -cargo clippy -p clarion-mcp --all-targets --all-features -- -D warnings -cargo nextest run -p clarion-mcp +cargo clippy -p loomweave-mcp --all-targets --all-features -- -D warnings +cargo nextest run -p loomweave-mcp ``` -Expected: clippy clean; all tests pass, including the pinned wire-string assertions in `crates/clarion-mcp/tests/storage_tools.rs` (`not-a-subsystem`, `llm-disabled`, `content-drift`, `token-ceiling-exceeded`, `not-found`, `llm-invalid-json`) — unchanged because `as_str()` reproduces each literal exactly. +Expected: clippy clean; all tests pass, including the pinned wire-string assertions in `crates/loomweave-mcp/tests/storage_tools.rs` (`not-a-subsystem`, `llm-disabled`, `content-drift`, `token-ceiling-exceeded`, `not-found`, `llm-invalid-json`) — unchanged because `as_str()` reproduces each literal exactly. - [ ] **Step 9: Commit** ```bash -git add crates/clarion-mcp/src/lib.rs -git commit -m "refactor(mcp): type tool-error codes with clarion_core::McpErrorCode +git add crates/loomweave-mcp/src/lib.rs +git commit -m "refactor(mcp): type tool-error codes with loomweave_core::McpErrorCode Replaces ~47 bare kebab string literals with compiler-checked variants; wire output byte-identical (V11-ARCH-01). @@ -542,40 +542,40 @@ Co-Authored-By: Claude Opus 4.8 (1M context) " ## Task 4: ADR-037 + contracts.md pointer **Files:** -- Create: `docs/clarion/adr/ADR-037-shared-error-vocabulary.md` -- Modify: ADR index (`docs/clarion/adr/README.md` if present; otherwise `docs/clarion/1.0/README.md` ADR list) +- Create: `docs/loomweave/adr/ADR-037-shared-error-vocabulary.md` +- Modify: ADR index (`docs/loomweave/adr/README.md` if present; otherwise `docs/loomweave/1.0/README.md` ADR list) - Modify: `docs/federation/contracts.md` (additive pointer near the error-envelope section, ~line 95) - [ ] **Step 1: Inspect an existing ADR for the house format** Run: ```bash -sed -n '1,40p' docs/clarion/adr/ADR-034-federation-http-read-api-hardening.md -ls docs/clarion/adr/ | grep -i readme +sed -n '1,40p' docs/loomweave/adr/ADR-034-federation-http-read-api-hardening.md +ls docs/loomweave/adr/ | grep -i readme ``` Match that header/status/section style in the next step. - [ ] **Step 2: Write ADR-037** -Create `docs/clarion/adr/ADR-037-shared-error-vocabulary.md` following the house format observed in Step 1. Required content: +Create `docs/loomweave/adr/ADR-037-shared-error-vocabulary.md` following the house format observed in Step 1. Required content: - **Status:** Accepted (2026-05-31). - **Context:** Two independent error-code wire surfaces (HTTP federation, MCP tool envelope) with hand-maintained taxonomies; the MCP side had no type at all (~47 bare kebab literals); drift risk between code and docs. Cite V11-ARCH-01 and ticket clarion-b57c6bc49f. -- **Decision:** Co-locate two *separate* typed enums (`HttpErrorCode`, `McpErrorCode`) in `clarion-core::errors` as the single source of truth; keep each surface's wire spelling (SCREAMING_SNAKE / kebab); do **not** merge the vocabularies; HTTP status stays per-endpoint (a total code→status map is impossible under the "same code, different status by endpoint" contract — ADR-034). Drift tests pin every wire string at the definition site. +- **Decision:** Co-locate two *separate* typed enums (`HttpErrorCode`, `McpErrorCode`) in `loomweave-core::errors` as the single source of truth; keep each surface's wire spelling (SCREAMING_SNAKE / kebab); do **not** merge the vocabularies; HTTP status stays per-endpoint (a total code→status map is impossible under the "same code, different status by endpoint" contract — ADR-034). Drift tests pin every wire string at the definition site. - **Alternatives rejected:** Merge to one SCREAMING_SNAKE enum + migrate MCP (breaks the frozen HTTP contract *and* pinned MCP strings, couples disjoint vocabularies, no drift benefit over co-location). `status_code()` on the enum (impossible per ADR-034). A `to_http()` narrowing method (dead code; documented as a table instead). - **Consequences:** Single typed home; MCP codes compiler-checked; wire bytes unchanged; ADR-034 / contracts.md wire contract untouched. - Note that this **partially relates to ADR-034** (shares the HTTP error envelope) without superseding it. - [ ] **Step 3: Add ADR-037 to the index** -Add a one-line entry for ADR-037 to whichever index Step 1 found (the ADR `README.md`, or the ADR list in `docs/clarion/1.0/README.md`), matching the existing line format. If `CLAUDE.md`'s "28 Accepted" count enumerates ADR ranges, leave that prose alone (out of scope) unless it lists ADR-037's range explicitly. +Add a one-line entry for ADR-037 to whichever index Step 1 found (the ADR `README.md`, or the ADR list in `docs/loomweave/1.0/README.md`), matching the existing line format. If `CLAUDE.md`'s "28 Accepted" count enumerates ADR ranges, leave that prose alone (out of scope) unless it lists ADR-037's range explicitly. - [ ] **Step 4: Add the additive pointer to contracts.md** In `docs/federation/contracts.md`, just after the closed-enum sentence (~lines 95-104, "Clients must switch on `code`…"), add one sentence (wire unchanged): ```markdown -> The `code` enum is defined canonically in Rust as `clarion_core::errors::HttpErrorCode` +> The `code` enum is defined canonically in Rust as `loomweave_core::errors::HttpErrorCode` > (single source of truth shared with the MCP tool-error vocabulary; see ADR-037). > The wire spelling on this surface is unchanged. ``` @@ -584,14 +584,14 @@ In `docs/federation/contracts.md`, just after the closed-enum sentence (~lines 9 Run: ```bash -RUSTDOCFLAGS="-D warnings" cargo doc -p clarion-core --no-deps --all-features +RUSTDOCFLAGS="-D warnings" cargo doc -p loomweave-core --no-deps --all-features ``` Expected: builds with no warnings (catches any broken intra-doc link in the `errors.rs` rustdoc). - [ ] **Step 6: Commit** ```bash -git add docs/clarion/adr/ADR-037-shared-error-vocabulary.md docs/federation/contracts.md docs/clarion/adr/README.md docs/clarion/1.0/README.md +git add docs/loomweave/adr/ADR-037-shared-error-vocabulary.md docs/federation/contracts.md docs/loomweave/adr/README.md docs/loomweave/1.0/README.md git commit -m "docs(adr): ADR-037 shared error vocabulary; contracts.md pointer (V11-ARCH-01) Co-Authored-By: Claude Opus 4.8 (1M context) " @@ -624,8 +624,8 @@ git add -A && git commit -m "style: cargo fmt after error-vocabulary extraction" Run: ```bash -grep -nE 'tool_error_envelope(_with_diagnostics)?\(\s*"' crates/clarion-mcp/src/lib.rs -grep -nE 'InferredDispatchFailure::new\(\s*"' crates/clarion-mcp/src/lib.rs +grep -nE 'tool_error_envelope(_with_diagnostics)?\(\s*"' crates/loomweave-mcp/src/lib.rs +grep -nE 'InferredDispatchFailure::new\(\s*"' crates/loomweave-mcp/src/lib.rs ``` Expected: no matches (every first argument is now a `McpErrorCode` variant). @@ -633,7 +633,7 @@ Expected: no matches (every first argument is now a `McpErrorCode` variant). ## Done criteria -- [ ] `HttpErrorCode` + `McpErrorCode` live in `clarion-core::errors`, re-exported at the crate root. +- [ ] `HttpErrorCode` + `McpErrorCode` live in `loomweave-core::errors`, re-exported at the crate root. - [ ] `http_read.rs` aliases the shared `HttpErrorCode`; no local error enum remains. - [ ] Zero bare error-code string literals at MCP `tool_error_envelope*` / `InferredDispatchFailure::new` call sites. - [ ] Drift tests pin every wire string; existing HTTP + 6 pinned MCP wire tests pass unchanged. diff --git a/docs/superpowers/prompts/2026-06-02-wave-0-execution.md b/docs/superpowers/prompts/2026-06-02-wave-0-execution.md index 0aa786f5..a609d9ed 100644 --- a/docs/superpowers/prompts/2026-06-02-wave-0-execution.md +++ b/docs/superpowers/prompts/2026-06-02-wave-0-execution.md @@ -2,26 +2,26 @@ **Date:** 2026-06-02 **Use:** Drop the fenced prompt below into an agent to plan and execute **Wave 0** (WS2 HTTP -linkages + WS3 prior-index retention) of the Clarion first-class program. +linkages + WS3 prior-index retention) of the Loomweave first-class program. **Gate:** None — Wave 0 is autonomous and un-gated. Start anytime. -**Source of truth:** `docs/superpowers/plans/2026-06-02-clarion-integrated-delivery-plan.md` -Phase 1 (T1.1–T1.7); `docs/superpowers/specs/2026-06-02-clarion-first-class-program-design.md` §4. +**Source of truth:** `docs/superpowers/plans/2026-06-02-loomweave-integrated-delivery-plan.md` +Phase 1 (T1.1–T1.7); `docs/superpowers/specs/2026-06-02-loomweave-first-class-program-design.md` §4. **Companion:** [`2026-06-02-wave-1-execution.md`](./2026-06-02-wave-1-execution.md) (the next wave; gated on SEI lock). --- ``` -You are implementing **Wave 0** of the Clarion "road to first-class" program, in the -Clarion repo at /home/john/clarion. Wave 0 is autonomous, un-gated, and on the suite +You are implementing **Wave 0** of the Loomweave "road to first-class" program, in the +Loomweave repo at /home/john/loomweave. Wave 0 is autonomous, un-gated, and on the suite critical path: completing it lets the suite-wide SEI identity standard lock. Your job is to PLAN and EXECUTE it end-to-end — real code, real tests, all CI gates green. ## Read these first (authoritative, in order) -1. docs/superpowers/specs/2026-06-02-clarion-first-class-program-design.md — the program +1. docs/superpowers/specs/2026-06-02-loomweave-first-class-program-design.md — the program map. You are doing **Wave 0** only (§4). Read §1–§4 and the §5 invariants. -2. docs/superpowers/plans/2026-06-02-clarion-integrated-delivery-plan.md — your task +2. docs/superpowers/plans/2026-06-02-loomweave-integrated-delivery-plan.md — your task source. Wave 0 = **Phase 1 tasks T1.1 through T1.7**. (T1.0, ADR-038, is already DONE.) -3. docs/clarion/adr/ADR-038-sei-token-and-signature.md — the locked SEI decisions. Do NOT +3. docs/loomweave/adr/ADR-038-sei-token-and-signature.md — the locked SEI decisions. Do NOT relitigate these; they constrain you but you are not implementing them in Wave 0. 4. CLAUDE.md — the repo's CI gates, ADR/immutability rules, and Filigree workflow. @@ -29,7 +29,7 @@ PLAN and EXECUTE it end-to-end — real code, real tests, all CI gates green. - **WS2 — HTTP linkages** (plan T1.5–T1.7): add `callers`/`callees` (+ batch) to the HTTP read API with pagination + confidence-tier filtering, and a `linkages: { http: true }` capability flag. These wrap the EXISTING storage queries - `clarion-storage/src/query.rs::call_edges_targeting` (callers) and `call_edges_from` + `loomweave-storage/src/query.rs::call_edges_targeting` (callers) and `call_edges_from` (callees). Routes are HMAC-protected, same as the `/api/v1/files` routes. - **WS3 — prior-index retention, side table only** (plan T1.1–T1.4): migration `0004_sei_prior_index.sql` creating `sei_prior_index(locator, body_hash, signature, @@ -79,7 +79,7 @@ Track the work in Filigree per CLAUDE.md (use the atomic start-work/start-next-w confidence-filtered, HMAC-gated, and tested against known fixtures. - `_capabilities` reports `linkages: { http: true }`. - `sei_prior_index` exists (migration 0004) and is correctly repopulated after every - successful `clarion analyze` (stale rows from the prior run removed), with a regression + successful `loomweave analyze` (stale rows from the prior run removed), with a regression test proving the snapshot equals the current run's entity set. - `docs/federation/contracts.md` pins the new linkage routes + capability flag. - All CI gates green. diff --git a/docs/superpowers/prompts/2026-06-02-wave-1-execution.md b/docs/superpowers/prompts/2026-06-02-wave-1-execution.md index 3457c34c..72f27820 100644 --- a/docs/superpowers/prompts/2026-06-02-wave-1-execution.md +++ b/docs/superpowers/prompts/2026-06-02-wave-1-execution.md @@ -2,20 +2,20 @@ **Date:** 2026-06-02 **Use:** Drop the fenced prompt below into an agent to plan and execute **Wave 1** (WS1 — SEI -authority) of the Clarion first-class program. +authority) of the Loomweave first-class program. **Gate:** ⛔ Gated. Requires (a) Wave 0 complete + merged, and (b) **SEI lock confirmed** (program decision D1 — a suite event). The prompt forces a confirm-or-stop gate check first. -**Source of truth:** `docs/superpowers/plans/2026-06-02-clarion-integrated-delivery-plan.md` +**Source of truth:** `docs/superpowers/plans/2026-06-02-loomweave-integrated-delivery-plan.md` Phase 2 (T2.0–T2.6) + the conformance oracle + the hard-cutover backfill; -`docs/clarion/adr/ADR-038-sei-token-and-signature.md` (locked decisions); -`/home/john/wardline/docs/superpowers/specs/2026-06-01-loom-stable-entity-identity-conformance.md` (the SEI standard). +`docs/loomweave/adr/ADR-038-sei-token-and-signature.md` (locked decisions); +`/home/john/wardline/docs/superpowers/specs/2026-06-01-weft-stable-entity-identity-conformance.md` (the SEI standard). **Companion:** [`2026-06-02-wave-0-execution.md`](./2026-06-02-wave-0-execution.md) (the prerequisite wave). --- ``` -You are implementing **Wave 1** of the Clarion "road to first-class" program, in the -Clarion repo at /home/john/clarion. Wave 1 is **WS1 — SEI authority**: the suite-wide +You are implementing **Wave 1** of the Loomweave "road to first-class" program, in the +Loomweave repo at /home/john/loomweave. Wave 1 is **WS1 — SEI authority**: the suite-wide stable-entity-identity engine. It is the heaviest single workstream and the one that, once shipped, makes every cross-tool binding survive a rename. Your job is to PLAN and EXECUTE it — real code, real tests, all CI gates green, and the SEI conformance oracle passing. @@ -26,8 +26,8 @@ SEI-shaped persistence: 1. **Wave 0 is complete and merged.** WS3's `sei_prior_index` table exists and is populated after every run (the matcher consumes it); WS2's HTTP linkages are live. Verify in the code, not just the plan. -2. **SEI lock is confirmed** (program decision D1 — a SUITE event, not Clarion's alone: - all four subsystems reported + the §8 oracle encodes the resolutions). Clarion's shape +2. **SEI lock is confirmed** (program decision D1 — a SUITE event, not Loomweave's alone: + all four subsystems reported + the §8 oracle encodes the resolutions). Loomweave's shape obligation is already discharged (ADR-038), but the suite may still adjust the shape in response to another subsystem's emerging requirement until lock. **If lock is not yet confirmed, STOP and ask the owner.** You may do shape-independent prep (test scaffolds, @@ -35,17 +35,17 @@ SEI-shaped persistence: matcher, or the wire contract until lock is confirmed. ## Read these first (authoritative, in order) -1. /home/john/wardline/docs/superpowers/specs/2026-06-01-loom-stable-entity-identity-conformance.md +1. /home/john/wardline/docs/superpowers/specs/2026-06-01-weft-stable-entity-identity-conformance.md — the SEI standard. Read §1–§8 closely: §3 matcher, §4 wire contract, §5 your obligations, §7 migration, §8 the conformance oracle you must pass. -2. docs/clarion/adr/ADR-038-sei-token-and-signature.md — the LOCKED Clarion decisions +2. docs/loomweave/adr/ADR-038-sei-token-and-signature.md — the LOCKED Loomweave decisions (token, signature, persistence, reserved namespace). Implement these as written; do NOT re-decide them. -3. docs/superpowers/plans/2026-06-02-clarion-integrated-delivery-plan.md — your task +3. docs/superpowers/plans/2026-06-02-loomweave-integrated-delivery-plan.md — your task source. Wave 1 = **Phase 2 tasks T2.0 through T2.6**, plus the conformance oracle and the hard-cutover backfill. Read the "SEI persistence model" section and REQ-C-02 / the peer-review correction notes — they explain WHY the shape is what it is. -4. docs/superpowers/specs/2026-06-02-clarion-first-class-program-design.md §5 invariants. +4. docs/superpowers/specs/2026-06-02-loomweave-first-class-program-design.md §5 invariants. 5. CLAUDE.md — CI gates, ADR immutability, Filigree workflow. ## Scope — WS1 SEI authority only @@ -67,7 +67,7 @@ SEI-shaped persistence: - **Python plugin**: emit the signature JSON the manifest declares (`signature_schemas` + `signature_schema_version`) so `entities.signature` is populated. Python CI gates apply. - **Conformance oracle** (SEI spec §8): build/run the fixtures — identity round-trip + - opacity, rename, move, ambiguous (fail-closed), delete, capability-absent. Clarion must + opacity, rename, move, ambiguous (fail-closed), delete, capability-absent. Loomweave must pass all. - **Hard-cutover backfill**: build + test the idempotent, resumable backfill that re-keys existing bindings locator→SEI. The actual coordinated cross-tool release is owner-gated @@ -75,7 +75,7 @@ SEI-shaped persistence: scheduling, do NOT fire it unilaterally. ## LOCKED decisions (ADR-038) — implement exactly; do NOT re-derive -- **Token:** `clarion:eid:`, +- **Token:** `loomweave:eid:`, where `mint_run_id` is the minting run's UUID. **NOT** `first_seen_commit` (it is never populated — a token keyed on it collides on locator reuse). SEI allocation is STATEFUL; reproducibility comes from `sei_bindings`, not from re-deriving the token. Add a test that @@ -89,12 +89,12 @@ SEI-shaped persistence: - **Signature:** plain non-unique `entities.signature TEXT`, plugin-declared versioned JSON, compared by string equality. Near-redundant for the v1 deterministic move; carried for spec-conformance + the fuzzy future. -- **REQ-F-02:** `resolve(locator)` rejects any input with the reserved `clarion:eid:` prefix +- **REQ-F-02:** `resolve(locator)` rejects any input with the reserved `loomweave:eid:` prefix ("not a valid locator") — NOT by colon count (an SEI has the same two colons). Reserve the - `clarion:eid:` namespace; no plugin locator may occupy it. + `loomweave:eid:` namespace; no plugin locator may occupy it. - **REQ-C-05:** git-rename behind the typed `GitRenameSource` interface so legis can supply it later without touching the model. -- **REQ-L-01:** lineage is append-only — no UPDATE path, no Clarion-side hash-chain in v1. +- **REQ-L-01:** lineage is append-only — no UPDATE path, no Loomweave-side hash-chain in v1. - **No binding keyed on a locator on ANY surface** — MCP and HTTP both carry SEI. No MCP locator exception. - **Fail-closed:** when the matcher cannot PROVE sameness, mint a new SEI and mark the old @@ -128,7 +128,7 @@ bodies. Close as you land each. - Every alive entity has an `alive` `sei_bindings` row after analysis. - Matcher handles rename / move / ambiguous(fail-closed) / orphan per the test suite; a back-to-back unchanged re-run carries (never re-mints) every SEI. -- HTTP `resolve`/`resolve_sei`/`lineage` live, with the REQ-F-02 `clarion:eid:` rejection; +- HTTP `resolve`/`resolve_sei`/`lineage` live, with the REQ-F-02 `loomweave:eid:` rejection; `_capabilities` reports `sei: { supported: true, version: 1 }`. - MCP responses carry SEI via the binding join (no locator-keyed bindings anywhere). - The §8 conformance oracle passes. diff --git a/docs/superpowers/prompts/2026-06-02-wave-2-execution.md b/docs/superpowers/prompts/2026-06-02-wave-2-execution.md index b6582a7b..1757a12b 100644 --- a/docs/superpowers/prompts/2026-06-02-wave-2-execution.md +++ b/docs/superpowers/prompts/2026-06-02-wave-2-execution.md @@ -2,31 +2,31 @@ **Date:** 2026-06-02 **Use:** Drop the fenced prompt below into an agent to plan and execute **Wave 2** (WS4 dossier -participation + the deferred incremental-analysis skip) of the Clarion first-class program. +participation + the deferred incremental-analysis skip) of the Loomweave first-class program. This is the wave that **closes core paradise**. -**Gate:** Gated on Clarion's own WS1 (SEI authority) + WS2 (HTTP linkages) — **both internal, +**Gate:** Gated on Loomweave's own WS1 (SEI authority) + WS2 (HTTP linkages) — **both internal, no sibling wait.** The prompt forces a confirm-or-stop gate check first. -**Source of truth:** `docs/superpowers/plans/2026-06-02-clarion-integrated-delivery-plan.md` +**Source of truth:** `docs/superpowers/plans/2026-06-02-loomweave-integrated-delivery-plan.md` Phase 3 (T3.1 incremental, T3.2 dossier participation); -`docs/superpowers/specs/2026-06-02-clarion-first-class-program-design.md` §4 (Wave 2) + D3. +`docs/superpowers/specs/2026-06-02-loomweave-first-class-program-design.md` §4 (Wave 2) + D3. **Companion:** [`2026-06-02-wave-1-execution.md`](./2026-06-02-wave-1-execution.md) (the prerequisite wave). --- ``` -You are implementing **Wave 2** of the Clarion "road to first-class" program, in the -Clarion repo at /home/john/clarion. Wave 2 closes the suite's CORE PARADISE: when it lands, +You are implementing **Wave 2** of the Loomweave "road to first-class" program, in the +Loomweave repo at /home/john/loomweave. Wave 2 closes the suite's CORE PARADISE: when it lands, `dossier(entity)` returns a complete, freshness-stamped, SEI-keyed view of a function that stays correct after the function is renamed. Your job is to PLAN and EXECUTE it — real code, real tests, all CI gates green. -Two workstreams, and a crucial framing: **Clarion does NOT build the dossier.** The dossier -is assembled by the consumer (Wardline). Clarion's job in Wave 2 is to (a) guarantee every +Two workstreams, and a crucial framing: **Loomweave does NOT build the dossier.** The dossier +is assembled by the consumer (Wardline). Loomweave's job in Wave 2 is to (a) guarantee every slice the assembler needs is reachable over HTTP and pin that contract, and (b) ship the incremental-analysis skip that was deferred from Wave 1. ## ⛔ GATE CHECK — do this FIRST -Confirm BOTH before building (both are internal Clarion gates — no SEI lock, no sibling): +Confirm BOTH before building (both are internal Loomweave gates — no SEI lock, no sibling): 1. **Wave 1 (WS1 SEI authority) is complete and merged.** `resolve`/`resolve_sei` are live, every alive entity has an `alive` `sei_bindings` row, the conformance oracle passes. 2. **Wave 0 (WS2 HTTP linkages + WS3 prior-index) is complete and merged.** `callers`/ @@ -34,21 +34,21 @@ Confirm BOTH before building (both are internal Clarion gates — no SEI lock, n Verify in the code, not just the plan. If either is missing, STOP and ask the owner. ## Read these first (authoritative, in order) -1. docs/superpowers/specs/2026-06-02-clarion-first-class-program-design.md — §4 (Wave 2), +1. docs/superpowers/specs/2026-06-02-loomweave-first-class-program-design.md — §4 (Wave 2), the §5 invariants, and decision D3 (why incremental-skip lands here, not in Wave 1). -2. docs/superpowers/plans/2026-06-02-clarion-integrated-delivery-plan.md — Phase 3, tasks +2. docs/superpowers/plans/2026-06-02-loomweave-integrated-delivery-plan.md — Phase 3, tasks **T3.1 (incremental analysis)** and **T3.2 (dossier participation)**. -3. /home/john/wardline/docs/superpowers/specs/2026-06-01-wardline-loom-entity-dossier-design.md - — the dossier the assembler builds. Read it to know EXACTLY which Clarion surfaces it +3. /home/john/wardline/docs/superpowers/specs/2026-06-01-wardline-weft-entity-dossier-design.md + — the dossier the assembler builds. Read it to know EXACTLY which Loomweave surfaces it calls and what shape it expects back. You are serving this consumer, not replacing it. -4. docs/clarion/adr/ADR-038-sei-token-and-signature.md — the SEI shape (already implemented +4. docs/loomweave/adr/ADR-038-sei-token-and-signature.md — the SEI shape (already implemented in Wave 1; do not re-decide). 5. CLAUDE.md — CI gates, ADR immutability, Filigree workflow. ## Scope — two workstreams ### WS4 — dossier participation (plan T3.2) — mostly a contract, not heavy code -- Write the participation spec `docs/superpowers/specs/2026-06-02-clarion-dossier-participation.md` - naming the EXACT Clarion surface the dossier assembler calls and what it returns: +- Write the participation spec `docs/superpowers/specs/2026-06-02-loomweave-dossier-participation.md` + naming the EXACT Loomweave surface the dossier assembler calls and what it returns: `resolve(locator)` → SEI + two-axis freshness; `callers`/`callees` over HTTP (structural linkages); file context (`GET /api/v1/files/...`); Filigree associations (open work). - **Verify the whole surface is reachable over HTTP.** The assembler is an HTTP client (e.g. @@ -75,9 +75,9 @@ Confirm BOTH before building (both are internal Clarion gates — no SEI lock, n and are NOT orphaned. ## Hard boundaries — do NOT -- Do NOT build the dossier assembler or a Clarion-owned dossier envelope. Clarion contributes +- Do NOT build the dossier assembler or a Loomweave-owned dossier envelope. Loomweave contributes its slice; the consumer (Wardline) composes. Do NOT aggregate Wardline taint facts or - Filigree issues into a Clarion object — Clarion serves, it does not assemble. + Filigree issues into a Loomweave object — Loomweave serves, it does not assemble. - Do NOT re-decide ADR-038 or change the SEI shape. Do NOT add an `entities.sei` column. - Do NOT edit any Accepted ADR body. Do NOT touch archived docs. - Do NOT start the parallel band (WS5 MCP catalogue, WS6 guidance, WS7 multi-language, WS8 @@ -92,7 +92,7 @@ Confirm BOTH before building (both are internal Clarion gates — no SEI lock, n warnings, deny). Python gates only if you touch the plugin (you likely will not in Wave 2). - Hold the §5 invariants: opt-in (incremental skip must not change semantics, only speed), fail-closed (the orphan guard is the no-false-green discipline), enrich-only (the dossier - surface is additive; Clarion is not load-bearing for the assembler's own semantics). + surface is additive; Loomweave is not load-bearing for the assembler's own semantics). ## Filigree Track per CLAUDE.md (atomic start-work verbs, `--actor` your identity). One issue for WS4 and @@ -105,7 +105,7 @@ close as you land each. - The dossier participation spec is written and every endpoint it depends on is reachable over HTTP and pinned in `contracts.md` (or a remaining gap is explicitly surfaced with a recommendation). -- `dossier(entity)` is achievable by the Wardline assembler using only Clarion's HTTP surface +- `dossier(entity)` is achievable by the Wardline assembler using only Loomweave's HTTP surface — demonstrate the full set of calls succeeds against a renamed-function fixture (SEI carried, facts not orphaned, freshness stamped). - All CI gates green. diff --git a/docs/superpowers/prompts/2026-06-02-wave-3-execution.md b/docs/superpowers/prompts/2026-06-02-wave-3-execution.md index 1a1bdcba..8282b14e 100644 --- a/docs/superpowers/prompts/2026-06-02-wave-3-execution.md +++ b/docs/superpowers/prompts/2026-06-02-wave-3-execution.md @@ -4,30 +4,30 @@ **Use:** Drop the fenced prompt below into an agent to plan and execute **Wave 3 — WS9 `legis` governance consumption**, the post-core-paradise wave that closes **governed paradise**. **Position:** Wave 3 = WS9, the suite track's gated terminus (governed paradise). It is **thin on -Clarion's side**: Clarion is already the identity authority, so this wave mostly exposes what Wave 1 +Loomweave's side**: Loomweave is already the identity authority, so this wave mostly exposes what Wave 1 built and swaps one provider. (The standalone-first-class workstreams take the later wave numbers 4–8; this WS9 prompt keeps Wave 3 because it was dispatched under that number.) **Gate:** ⛔ Gated on (a) Wave 2 complete (core paradise) **and** (b) **`legis` actually exists** (it is design-ready, not implemented). Until `legis` ships, this prompt is forward-staged — the gate check blocks. **Source of truth:** program §2 (WS9), roadmap §2.4, SEI spec §5/§6 (consumer obligations + the -git-rename provider hook), `loom.md` (enrich-only federation). +git-rename provider hook), `weft.md` (enrich-only federation). --- ``` -You are implementing **Wave 3 — WS9: `legis` governance consumption** of the Clarion -first-class program, in the Clarion repo at /home/john/clarion. This wave closes GOVERNED +You are implementing **Wave 3 — WS9: `legis` governance consumption** of the Loomweave +first-class program, in the Loomweave repo at /home/john/loomweave. This wave closes GOVERNED PARADISE: `legis` adds IRAP-grade governance (attestations, sign-offs, custody, audit -lineage) keyed to Clarion's stable identity, as an OPT-IN layer invisible to a solo project. +lineage) keyed to Loomweave's stable identity, as an OPT-IN layer invisible to a solo project. -Read this framing first, because it bounds the whole wave: **this is thin on Clarion's -side.** Clarion is ALREADY the identity authority (Wave 1). Governance attestations key on -SEI that already exists; Clarion's lineage endpoint already exists. Clarion does NOT build a +Read this framing first, because it bounds the whole wave: **this is thin on Loomweave's +side.** Loomweave is ALREADY the identity authority (Wave 1). Governance attestations key on +SEI that already exists; Loomweave's lineage endpoint already exists. Loomweave does NOT build a new identity surface, does NOT govern or adjudicate trust, and does NOT re-establish lineage -integrity — `legis` does those at its own boundary. Your job is to make Clarion's existing +integrity — `legis` does those at its own boundary. Your job is to make Loomweave's existing surfaces consumable by `legis`, swap one provider, and pin the contract. If you find -yourself building a large new Clarion subsystem, you have misread the scope — stop. +yourself building a large new Loomweave subsystem, you have misread the scope — stop. ## ⛔ GATE CHECK — do this FIRST Confirm BOTH before any work: @@ -39,39 +39,39 @@ Confirm BOTH before any work: contract (a doc), but do NOT build provider integration against a subsystem that isn't there. ## Read these first -1. docs/superpowers/specs/2026-06-02-clarion-first-class-program-design.md — §2 (WS9), §5 +1. docs/superpowers/specs/2026-06-02-loomweave-first-class-program-design.md — §2 (WS9), §5 invariants, §6 (D-WS5b-1 is unrelated; D1/D4 may matter). -2. /home/john/wardline/docs/superpowers/specs/2026-06-01-loom-stable-entity-identity-conformance.md +2. /home/john/wardline/docs/superpowers/specs/2026-06-01-weft-stable-entity-identity-conformance.md §5 (legis obligations) + §6 (legis as the git-rename PROVIDER the §3 matcher consumes). -3. /home/john/wardline/docs/superpowers/specs/2026-06-01-loom-goal-state-case-study.md §1.5 +3. /home/john/wardline/docs/superpowers/specs/2026-06-01-weft-goal-state-case-study.md §1.5 (graded enforcement / custody axiom) — the model `legis` governs under. 4. /home/john/legis/docs/federation/ (its SEI-consumer posture + charter), if present. -5. docs/clarion/adr/ADR-038-sei-token-and-signature.md (the SEI surfaces `legis` consumes); +5. docs/loomweave/adr/ADR-038-sei-token-and-signature.md (the SEI surfaces `legis` consumes); CLAUDE.md (gates, ADR immutability, Filigree). -## Scope — Clarion's thin slice of WS9 -- **Audit spine (read-only consumption).** Verify `legis` can read Clarion's SEI + lineage as +## Scope — Loomweave's thin slice of WS9 +- **Audit spine (read-only consumption).** Verify `legis` can read Loomweave's SEI + lineage as its audit trail over the EXISTING Wave-1 surface (`resolve_sei`, `lineage`). Fill only genuine gaps `legis` surfaces (e.g. a batch lineage read), additively. Pin the consumption contract in - docs/federation/. `legis` re-establishes integrity at ITS boundary — do NOT build a Clarion-side + docs/federation/. `legis` re-establishes integrity at ITS boundary — do NOT build a Loomweave-side lineage hash-chain or signature (REQ-L-01 / the custody axiom; signed lineage is North Star). - **Git-rename provider swap (REQ-C-05).** The Wave-1 matcher consumes "a git-rename signal" behind the typed `GitRenameSource` interface; v1 used `ShellGitRenameSource`. `legis` owns the git interface, so add a `LegisGitRenameSource` impl behind the SAME interface — no change to the SEI model. `ShellGitRenameSource` remains the fallback when `legis` is absent (enrich-only). -- **Trust vocabulary, carried verbatim.** Clarion continues to carry `declared_tier` / - `wardline_groups` exactly as Wardline emits them. Clarion does NOT adjudicate trust (Wardline +- **Trust vocabulary, carried verbatim.** Loomweave continues to carry `declared_tier` / + `wardline_groups` exactly as Wardline emits them. Loomweave does NOT adjudicate trust (Wardline analyses, `legis` governs — one judge, not two). Participate in, do not lead, any suite trust-vocabulary convergence. ## Hard boundaries — do NOT -- Do NOT govern, adjudicate, or re-judge trust. Do NOT add a Clarion policy/attestation engine — - attestations live in `legis`, keyed on Clarion's SEI. -- Do NOT build a new identity surface — Clarion is already the authority. -- Do NOT build a Clarion-side lineage hash-chain / tamper-evidence in v1 (legis's boundary owns it). -- Do NOT make `legis` required for any Clarion semantics. Clarion MUST work fully with `legis` +- Do NOT govern, adjudicate, or re-judge trust. Do NOT add a Loomweave policy/attestation engine — + attestations live in `legis`, keyed on Loomweave's SEI. +- Do NOT build a new identity surface — Loomweave is already the authority. +- Do NOT build a Loomweave-side lineage hash-chain / tamper-evidence in v1 (legis's boundary owns it). +- Do NOT make `legis` required for any Loomweave semantics. Loomweave MUST work fully with `legis` absent — `LegisGitRenameSource` degrades to `ShellGitRenameSource`; the audit surface is just - unconsumed. Enrich-only (loom.md §5) — verify Clarion solo + Clarion-without-legis both intact. + unconsumed. Enrich-only (weft.md §5) — verify Loomweave solo + Loomweave-without-legis both intact. - Do NOT edit any Accepted ADR body. Do NOT touch archived docs. - Do NOT start unrelated workstreams (WS5/WS5b/WS6/WS7/WS8 are the parallel band, separate cycles). @@ -81,20 +81,20 @@ Confirm BOTH before any work: against) and for the enrich-only degrade path (legis-absent → shell fallback). - Verify ground truth: confirm the Wave-1 `GitRenameSource` trait and lineage surfaces are as the plan describes before integrating. -- All ADR-023 Rust gates green. Federation surfaces stay additive and pass the loom.md §5 test. +- All ADR-023 Rust gates green. Federation surfaces stay additive and pass the weft.md §5 test. ## Filigree Track per CLAUDE.md (atomic start-work, `--actor`). Issues for: audit-spine contract, the git-rename provider swap, trust-vocabulary carriage. Cite WS9, SEI spec §5/§6, REQ-C-05, REQ-L-01. ## Definition of done (Wave 3 / WS9) -- `legis` reads Clarion's SEI + lineage as its audit spine over a pinned, contract-documented - surface (only additive gaps filled; no Clarion-side integrity machinery). +- `legis` reads Loomweave's SEI + lineage as its audit spine over a pinned, contract-documented + surface (only additive gaps filled; no Loomweave-side integrity machinery). - `LegisGitRenameSource` supplies the matcher's git-rename signal behind the typed interface; `ShellGitRenameSource` remains the fallback; tests prove identical `GitRename` output and clean degrade when `legis` is absent. -- Clarion carries `declared_tier`/`wardline_groups` verbatim; no trust adjudication added. -- Clarion solo + Clarion-without-legis both fully functional (enrich-only verified). +- Loomweave carries `declared_tier`/`wardline_groups` verbatim; no trust adjudication added. +- Loomweave solo + Loomweave-without-legis both fully functional (enrich-only verified). - All CI gates green. When done, request a code review and state plainly that GOVERNED paradise is reached as an diff --git a/docs/superpowers/prompts/2026-06-02-wave-4-execution.md b/docs/superpowers/prompts/2026-06-02-wave-4-execution.md index df730825..996944e8 100644 --- a/docs/superpowers/prompts/2026-06-02-wave-4-execution.md +++ b/docs/superpowers/prompts/2026-06-02-wave-4-execution.md @@ -5,7 +5,7 @@ catalogue completion**, the first standalone-first-class wave. **Position:** Wave 4. **Ungated** — runs concurrently with the suite waves (0–3); does not wait for core paradise. Committed slot, not "as capacity allows." -**Source of truth:** `docs/superpowers/specs/2026-06-02-clarion-ws5-mcp-catalogue-design.md` (the +**Source of truth:** `docs/superpowers/specs/2026-06-02-loomweave-ws5-mcp-catalogue-design.md` (the design — read it fully); program §2/§4 (WS5 = Wave 4). **Companion:** Wave 5 (WS5b — `search_semantic` + `find_dead_code`) extends this surface; those two tools are **NOT** in Wave 4. @@ -13,15 +13,15 @@ tools are **NOT** in Wave 4. --- ``` -You are implementing **Wave 4 — WS5: MCP catalogue completion** of the Clarion first-class -program, in the Clarion repo at /home/john/clarion. WS5 completes the consult-mode MCP +You are implementing **Wave 4 — WS5: MCP catalogue completion** of the Loomweave first-class +program, in the Loomweave repo at /home/john/loomweave. WS5 completes the consult-mode MCP surface — the tools agents actually reach for — as a **stateless** catalogue. Your job is to PLAN and EXECUTE it: real code, real tests, all CI gates green. ## Position & gate Wave 4 is **ungated** — start anytime; it runs concurrently with the suite waves (0–3). There is no hard gate. Two things to confirm before building (verify, don't assume): -1. **Ground-truth the current MCP surface.** WS5 EXTENDS a live surface — `crates/clarion-mcp/` +1. **Ground-truth the current MCP surface.** WS5 EXTENDS a live surface — `crates/loomweave-mcp/` already ships ~19 stateless tools (entity_at, find_entity, callers_of, neighborhood, summary, metadata, source_for_entity, orientation_pack, issues_for, subsystem_*, …). Read the actual registration before adding tools; do not duplicate what exists. @@ -31,12 +31,12 @@ is no hard gate. Two things to confirm before building (verify, don't assume): Design the field in now; do not block on Wave 1. ## Read these first -1. docs/superpowers/specs/2026-06-02-clarion-ws5-mcp-catalogue-design.md — THE design. Read all +1. docs/superpowers/specs/2026-06-02-loomweave-ws5-mcp-catalogue-design.md — THE design. Read all of it: §1 stateless decision, §2 current-surface inventory, §3 the tool catalogue, §4 SEI, §5 bounds, §6 the WS5/WS6 boundary, §7 the Wave-5 split-out. -2. docs/clarion/1.0/system-design.md §8 — the INTENDED catalogue. Its cursor/session model is +2. docs/loomweave/1.0/system-design.md §8 — the INTENDED catalogue. Its cursor/session model is **ratified-away** (see below); read it to know what NOT to build. -3. docs/clarion/adr/ADR-030-on-demand-summary-scope.md (on-demand posture), ADR-028 (edge +3. docs/loomweave/adr/ADR-030-on-demand-summary-scope.md (on-demand posture), ADR-028 (edge confidence tiers), ADR-038 (the SEI field). CLAUDE.md (CI gates, Filigree, ADR immutability). ## Scope — the WS5 tools (stateless; explicit IDs/scopes; bounded; SEI-carrying) @@ -77,7 +77,7 @@ is no hard gate. Two things to confirm before building (verify, don't assume): - Do NOT build the cursor/session model (see above) — stateless only. - Do NOT build `search_semantic` or `find_dead_code` — those are **Wave 5 (WS5b)**, they need infrastructure beyond a catalog query. (`…-ws5b-advanced-queries-plan.md`.) -- Do NOT build guidance AUTHORING — `propose_guidance`, `promote_guidance`, the `clarion guidance` +- Do NOT build guidance AUTHORING — `propose_guidance`, `promote_guidance`, the `loomweave guidance` CLI, staleness review are **WS6 (Wave 6)**. WS5 owns guidance READ (`guidance_for`) only. - Do NOT add analyze-time precompute. Do NOT edit Accepted ADRs. Do NOT touch archived docs. @@ -102,7 +102,7 @@ ADR-028/030. Close as you land each. including empty-result honesty where a categorisation signal is absent. - Faceted search + the cheap shortcuts ship; `search_semantic` + `find_dead_code` are explicitly left to Wave 5 (not built here, not silently dropped). -- The `clarion-workflow` skill / MCP tool docs describe the new tools (stateless, SEI-carrying); +- The `loomweave-workflow` skill / MCP tool docs describe the new tools (stateless, SEI-carrying); the §8-cursor-model reconciliation note is filed as a doc task. - All CI gates green. diff --git a/docs/superpowers/prompts/2026-06-02-wave-5-execution.md b/docs/superpowers/prompts/2026-06-02-wave-5-execution.md index 308cc876..2b61dcba 100644 --- a/docs/superpowers/prompts/2026-06-02-wave-5-execution.md +++ b/docs/superpowers/prompts/2026-06-02-wave-5-execution.md @@ -6,14 +6,14 @@ MCP queries** — the two tools split out of WS5 (`search_semantic`, `find_dead_ **scheduled, not deferred**; this is their committed delivery. **Position:** Wave 5. Ungated/concurrent, **soft-gated on WS5 (Wave 4)** (it extends that surface and reuses its categorisations). **Part A is gated on owner-decision D-WS5b-1** (embedding provider). -**Source of truth:** `docs/superpowers/plans/2026-06-02-clarion-ws5b-advanced-queries-plan.md` (the +**Source of truth:** `docs/superpowers/plans/2026-06-02-loomweave-ws5b-advanced-queries-plan.md` (the plan — read it fully); WS5 design spec §7 (why these split out); program §4 (WS5b = Wave 5). --- ``` -You are implementing **Wave 5 — WS5b: advanced MCP queries** of the Clarion first-class -program, in the Clarion repo at /home/john/clarion. WS5b delivers the two tools split out of +You are implementing **Wave 5 — WS5b: advanced MCP queries** of the Loomweave first-class +program, in the Loomweave repo at /home/john/loomweave. WS5b delivers the two tools split out of WS5: `search_semantic` (Part A) and `find_dead_code` (Part B). They were split out because they need infrastructure beyond a catalog query — and they are SCHEDULED, not deferred. Your job is to PLAN and EXECUTE them: real code, real tests, all CI gates green. @@ -34,12 +34,12 @@ Part A needs an owner decision (D-WS5b-1) and new infrastructure. `sei_bindings` exist, populated after). Not gated on Wave 1. ## Read these first -1. docs/superpowers/plans/2026-06-02-clarion-ws5b-advanced-queries-plan.md — THE plan. Read all +1. docs/superpowers/plans/2026-06-02-loomweave-ws5b-advanced-queries-plan.md — THE plan. Read all of it: Part A design (A.1) + the D-WS5b-1 decision (A.2) + tasks (A.3); Part B design (B.1) + tasks (B.2); sequencing + DoD. -2. docs/superpowers/specs/2026-06-02-clarion-ws5-mcp-catalogue-design.md §7 (the split-out) — the +2. docs/superpowers/specs/2026-06-02-loomweave-ws5-mcp-catalogue-design.md §7 (the split-out) — the surface these plug into. -3. docs/clarion/adr/ADR-005-clarion-dir-tracking.md (the git-committable `.clarion/` posture the +3. docs/loomweave/adr/ADR-005-loomweave-dir-tracking.md (the git-committable `.loomweave/` posture the embedding sidecar must respect — do NOT bloat the committed DB), ADR-030 (on-demand posture), ADR-028 (edge tiers), ADR-038 (SEI field). CLAUDE.md (gates, Filigree, ADR immutability). @@ -50,7 +50,7 @@ Part A needs an owner decision (D-WS5b-1) and new infrastructure. harmful error. Reachability is CONSERVATIVE: count `resolved` AND `ambiguous` AND `inferred` edges as reachable; treat dynamic-dispatch/reflection signals as barriers that keep targets live. Better to under-report dead code than over-report it. -- **Heuristic, honestly labelled.** Results are Findings (`CLA-FACT-DEAD-CODE-CANDIDATE`, +- **Heuristic, honestly labelled.** Results are Findings (`LMWV-FACT-DEAD-CODE-CANDIDATE`, `confidence < 1`, `confidence_basis: heuristic`) and the MCP tool returns the same with a confidence + the reason reach could not be proven. Never presented as certain. Framework-magic entry kinds (decorated handlers, plugin hooks) are excluded by a documented exclusion list. @@ -64,10 +64,10 @@ Part A needs an owner decision (D-WS5b-1) and new infrastructure. may make a hosted service REQUIRED. When off, `search_semantic` returns an honest `"semantic search not enabled"` — never a faked/empty-as-if-complete result. - **`EmbeddingProvider` trait** mirroring `LlmProvider` (`embed`/`dimensions`/`model_id`); a - `RecordingEmbeddingProvider` for deterministic tests. Config under `clarion.yaml: semantic_search:`. + `RecordingEmbeddingProvider` for deterministic tests. Config under `loomweave.yaml: semantic_search:`. - **Storage (LOAD-BEARING — protects the committable DB).** Vectors are hundreds of MB at the - elspeth scale and MUST NOT bloat `.clarion/clarion.db`. They live in a SEPARATE, git-ignored - sidecar `.clarion/embeddings.db`, keyed by `(entity_id, content_hash, model_id)` so they + elspeth scale and MUST NOT bloat `.loomweave/loomweave.db`. They live in a SEPARATE, git-ignored + sidecar `.loomweave/embeddings.db`, keyed by `(entity_id, content_hash, model_id)` so they invalidate like the summary cache. Add the sidecar to the gitignore DEFAULTS in code (install.rs `GITIGNORE_CONTENTS`); textual export excludes it. - **Query.** v1 = a bounded EXACT cosine scan over the (scope-filtered) candidate set, capped + @@ -90,10 +90,10 @@ Part A needs an owner decision (D-WS5b-1) and new infrastructure. Record: opt-in posture, the `EmbeddingProvider` trait, the sidecar-storage decision (it EXTENDS ADR-005's tracked-vs-excluded list — reference ADR-005, do NOT edit its immutable body), policy- engine cost governance, and the D-WS5b-1 provider choice. Register in the ADR index. Glossary - check: `search_semantic`/embeddings are Clarion-local (no cross-product term) — confirm + skip. + check: `search_semantic`/embeddings are Loomweave-local (no cross-product term) — confirm + skip. ## Hard boundaries — do NOT -- Do NOT bloat the committed `.clarion/clarion.db` with vectors — sidecar only. +- Do NOT bloat the committed `.loomweave/loomweave.db` with vectors — sidecar only. - Do NOT make semantic search required — opt-in + honest degrade (local-first). - Do NOT build an ANN vector backend in v1 — exact scan; ANN is a logged follow-on. - Part B: do NOT over-report dead code (fail toward "live"); do NOT present heuristic results as @@ -117,7 +117,7 @@ plan task IDs (A.T*/B.T*), D-WS5b-1, ADR-039, ADR-005/028/030. Close as you land ## Definition of done (Wave 5 / WS5b) - **Part B:** `find_dead_code` ships on-demand + conservative (fails toward "live"); heuristic - results labelled with confidence; `CLA-FACT-DEAD-CODE-CANDIDATE` emitted; fixtures prove no + results labelled with confidence; `LMWV-FACT-DEAD-CODE-CANDIDATE` emitted; fixtures prove no live-code false-positives on reflection / ambiguous edges. - **Part A:** `search_semantic` ships opt-in (off by default, honest degrade); embeddings in the git-ignored sidecar (committed DB unbloated); cost policy-governed; results carry `sei` + score, diff --git a/docs/superpowers/prompts/README.md b/docs/superpowers/prompts/README.md index 434617bf..b44430b8 100644 --- a/docs/superpowers/prompts/README.md +++ b/docs/superpowers/prompts/README.md @@ -1,11 +1,11 @@ # Execution prompts -Drop-in agent prompts for executing the waves of the Clarion "road to first-class" program. +Drop-in agent prompts for executing the waves of the Loomweave "road to first-class" program. Each prompt is self-contained: paste the fenced block into an agent running in this repo. -**Program context:** [`../specs/2026-06-02-clarion-first-class-program-design.md`](../specs/2026-06-02-clarion-first-class-program-design.md) +**Program context:** [`../specs/2026-06-02-loomweave-first-class-program-design.md`](../specs/2026-06-02-loomweave-first-class-program-design.md) (workstreams, dependency graph, wave sequencing §4). -**Task source:** [`../plans/2026-06-02-clarion-integrated-delivery-plan.md`](../plans/2026-06-02-clarion-integrated-delivery-plan.md). +**Task source:** [`../plans/2026-06-02-loomweave-integrated-delivery-plan.md`](../plans/2026-06-02-loomweave-integrated-delivery-plan.md). **Wave numbers are committed dispatch slots — once a wave is dispatched, its number is fixed and never re-used or renumbered.** Waves 0–3 are spent (Wave 0 executing; Wave 3 executed). The @@ -30,7 +30,7 @@ dependency order.) 1. **Wave 0** — autonomous, executing now. Completing it lets SEI lock. 2. **Wave 1** — gated on Wave 0 + SEI lock; the prompt forces a confirm-or-stop gate check. -3. **Wave 2** — closes the dossier (core paradise); gated only on Clarion's own WS1 + WS2. +3. **Wave 2** — closes the dossier (core paradise); gated only on Loomweave's own WS1 + WS2. 4. **Wave 3 (WS9)** — governed paradise; gated on Wave 2 + `legis` *existing*; forward-staged. 5. **Waves 4–8 (standalone first-class)** — ungated, concurrent with the suite waves, committed order: WS5 → WS5b → WS6 → WS7 → WS8. **WS5b is Wave 5** — a committed slot, not "someday." diff --git a/docs/superpowers/specs/2026-05-30-clarion-wardline-taint-store-design.md b/docs/superpowers/specs/2026-05-30-loomweave-wardline-taint-store-design.md similarity index 71% rename from docs/superpowers/specs/2026-05-30-clarion-wardline-taint-store-design.md rename to docs/superpowers/specs/2026-05-30-loomweave-wardline-taint-store-design.md index cb6f8657..339cbd52 100644 --- a/docs/superpowers/specs/2026-05-30-clarion-wardline-taint-store-design.md +++ b/docs/superpowers/specs/2026-05-30-loomweave-wardline-taint-store-design.md @@ -1,14 +1,14 @@ -# Clarion as Wardline taint-fact store (SP9) — design +# Loomweave as Wardline taint-fact store (SP9) — design **Date:** 2026-05-30 **Status:** approved (brainstorm). Deliverable: this spec + an outward-facing contract response to Wardline + tracked `release:1.1` issues. -**Request being answered:** `wardline/docs/integration/2026-05-30-wardline-clarion-taint-store-requirements.md` +**Request being answered:** `wardline/docs/integration/2026-05-30-wardline-loomweave-taint-store-requirements.md` (Wardline SP9 taint-store requirements). **Builds on:** ADR-011 (writer-actor concurrency), ADR-014 (HTTP read API), ADR-018 (identity reconciliation / qualname divergence), ADR-034 (HTTP read-API hardening / HMAC), the federation contract `docs/federation/contracts.md`, and -`loom.md` §3–§5 (federation axiom). +`weft.md` §3–§5 (federation axiom). --- @@ -16,68 +16,68 @@ hardening / HMAC), the federation contract `docs/federation/contracts.md`, and Wardline's SP9 wants to make `explain_taint` (and later overlay-scan + the full N-hop chain) into **cheap queries against a persistent per-entity taint/provenance -store that Clarion holds**, keyed by Clarion entity, instead of re-running the +store that Loomweave holds**, keyed by Loomweave entity, instead of re-running the analysis on every call. Wardline computes the taint facts during `wardline scan` -and writes them to Clarion; later reads become graph lookups. The store does not -exist yet. Wardline asks Clarion for five capabilities (A–G in the request): batch +and writes them to Loomweave; later reads become graph lookups. The store does not +exist yet. Wardline asks Loomweave for five capabilities (A–G in the request): batch qualname→entity resolution, per-entity taint-fact upsert, per-entity fetch (single + batch), a freshness/staleness contract, entity lifecycle handling, an HTTP transport, and per-project isolation. -This document is Clarion's design response: the federation verdict, the answers to -the seven decisions Wardline requested, the Clarion-side architecture, and the +This document is Loomweave's design response: the federation verdict, the answers to +the seven decisions Wardline requested, the Loomweave-side architecture, and the issue breakdown. -## 2. Federation verdict (loom.md §3–§5) — passes; ADR, not asterisk +## 2. Federation verdict (weft.md §3–§5) — passes; ADR, not asterisk The integration is **enrich-only and additive**, and passes the §5 failure test: -- **Solo-useful (both).** Clarion's briefings, queries, and catalog work with the - taint store empty — it is optional enrichment on Clarion's *own* entities. +- **Solo-useful (both).** Loomweave's briefings, queries, and catalog work with the + taint store empty — it is optional enrichment on Loomweave's *own* entities. Wardline guarantees (request §6) that the SP8 stateless re-run remains its - permanent fallback; it boots and answers `explain_taint` with Clarion absent, + permanent fallback; it boots and answers `explain_taint` with Loomweave absent, unreachable, or stale. Neither product requires the other. -- **Pairwise-composable.** `(Wardline, Clarion)` composes directly: Clarion stores +- **Pairwise-composable.** `(Wardline, Loomweave)` composes directly: Loomweave stores Wardline's taint facts and serves them back cheaply. - **Not semantic coupling.** `wardline_json` is stored **verbatim and opaque** — - Clarion never parses, validates, or depends on its contents. All taint + Loomweave never parses, validates, or depends on its contents. All taint semantics (including the single-successor chain walk) stay Wardline-side. -The one real risk is the **"no Loom store" rule (§6)**: a *generic* "any sibling -writes opaque blobs keyed by entity" API would turn Clarion into the shared +The one real risk is the **"no Weft store" rule (§6)**: a *generic* "any sibling +writes opaque blobs keyed by entity" API would turn Loomweave into the shared system-of-record the doctrine forbids. The guard is to keep the surface **Wardline-specific** — a `wardline`-named table and `wardline`-scoped routes, the same naming discipline as the ADR-018 asterisk — **not** a general `sibling_json` bus. Because this **passes** the failure test (rather than accepting a violation), it -is recorded as a **new ADR**, *not* a new `loom.md` §5 asterisk. The load-bearing +is recorded as a **new ADR**, *not* a new `weft.md` §5 asterisk. The load-bearing sentence the ADR must carry: *this is not a precedent for a general-purpose cross-product blob store; the next sibling that wants per-entity persistence gets its own named, justified surface or it does not get one.* -## 3. The seven decisions (Clarion's answers) +## 3. The seven decisions (Loomweave's answers) -| # | Decision | Clarion's answer | +| # | Decision | Loomweave's answer | |---|---|---| -| 1 | Key by `EntityId` or accept qualname? | **Qualname-keyed API.** Clarion resolves `qualname → EntityId` internally using the local-catalog reconciliation it already owns (the same mechanism as Flow B, `clarion-ca2d26ffbe`). **Writes require `exact` resolution**; `heuristic`/`none` are returned in `unresolved_qualnames` and never written (a heuristic *write* would silently attach a fact to the wrong entity). Reads may surface a `heuristic` match. | +| 1 | Key by `EntityId` or accept qualname? | **Qualname-keyed API.** Loomweave resolves `qualname → EntityId` internally using the local-catalog reconciliation it already owns (the same mechanism as Flow B, `clarion-ca2d26ffbe`). **Writes require `exact` resolution**; `heuristic`/`none` are returned in `unresolved_qualnames` and never written (a heuristic *write* would silently attach a fact to the wrong entity). Reads may surface a `heuristic` match. | | 2 | Store `scan_id`/generation per fact? | **Yes — as a real column** in the dedicated table (cheap, no blob parsing), for observability and to enable an *optional future* prune-by-scan. **Correctness does not depend on it** — it rests on the freshness gate (#3) + per-entity replace. | -| 3 | Content-hash definition + per-entity exposure | **`blake3` of the containing file's raw bytes, hex-encoded, whole-file** — Clarion's existing definition (`clarion-storage::query` derives it lazily as `file_content_hash`, `blake3::hash(fs::read(path))`). **Not** sha256, **not** LF-normalized. File-granular, which matches Wardline's "conservatively re-stale all of a file's functions on any edit." Wardline adopts Clarion's definition as the single source of truth, as offered in the request. Returned per entity on fetch as `current_content_hash`. | -| 4 | Lifecycle: cascade vs per-scan prune? | **Neither is the correctness mechanism.** Clarion's entity lifecycle is **wipe-and-rerun** (`clarion-storage::query` documents "v0.1 assumes a wipe-and-rerun analyze workflow"); there is no incremental entity delete to cascade off. The **§D freshness gate is the universal safety net**: deleted, renamed, and edited entities all surface to Wardline as hash-mismatch-or-`exists:false` → Wardline recomputes and re-writes. No cascade and no mandatory prune call are required. An optional `prune-by-scan` may be added later via the `scan_id` column. | -| 5 | HTTP+JSON surface, or local-only? | **HTTP+JSON**, new routes on `clarion serve`, HMAC-gated (ADR-034 inbound auth), stdlib-`urllib`-callable, reached via a `--clarion-url` analog. This makes Clarion's HTTP API **read+write** for the first time (it is read-only today); the ADR covers that shift. | -| 6 | Per-project isolation + the `project` handle | **Yes.** Clarion's DB is per-project (`.clarion/` under the project root); one `serve` instance serves exactly one project. The request's `project` field is accepted as a **guard** (it must match the served project; mismatch → error) rather than as a selector. | +| 3 | Content-hash definition + per-entity exposure | **`blake3` of the containing file's raw bytes, hex-encoded, whole-file** — Loomweave's existing definition (`loomweave-storage::query` derives it lazily as `file_content_hash`, `blake3::hash(fs::read(path))`). **Not** sha256, **not** LF-normalized. File-granular, which matches Wardline's "conservatively re-stale all of a file's functions on any edit." Wardline adopts Loomweave's definition as the single source of truth, as offered in the request. Returned per entity on fetch as `current_content_hash`. | +| 4 | Lifecycle: cascade vs per-scan prune? | **Neither is the correctness mechanism.** Loomweave's entity lifecycle is **wipe-and-rerun** (`loomweave-storage::query` documents "v0.1 assumes a wipe-and-rerun analyze workflow"); there is no incremental entity delete to cascade off. The **§D freshness gate is the universal safety net**: deleted, renamed, and edited entities all surface to Wardline as hash-mismatch-or-`exists:false` → Wardline recomputes and re-writes. No cascade and no mandatory prune call are required. An optional `prune-by-scan` may be added later via the `scan_id` column. | +| 5 | HTTP+JSON surface, or local-only? | **HTTP+JSON**, new routes on `loomweave serve`, HMAC-gated (ADR-034 inbound auth), stdlib-`urllib`-callable, reached via a `--loomweave-url` analog. This makes Loomweave's HTTP API **read+write** for the first time (it is read-only today); the ADR covers that shift. | +| 6 | Per-project isolation + the `project` handle | **Yes.** Loomweave's DB is per-project (`.loomweave/` under the project root); one `serve` instance serves exactly one project. The request's `project` field is accepted as a **guard** (it must match the served project; mismatch → error) rather than as a selector. | | 7 | Timeline | Driven by the issue sequencing in §8. | ## 4. Architecture -### 4.1 Write path — optional writer-actor on `clarion serve` +### 4.1 Write path — optional writer-actor on `loomweave serve` -`clarion serve` today opens only the `ReaderPool`; writes go through the ADR-011 -writer-actor, held by `clarion analyze`. SP9 gives `serve` an **optional +`loomweave serve` today opens only the `ReaderPool`; writes go through the ADR-011 +writer-actor, held by `loomweave analyze`. SP9 gives `serve` an **optional writer-actor**, constructed only when the taint-store write API is enabled in -config (default off). Cross-process contention with a concurrent `clarion analyze` +config (default off). Cross-process contention with a concurrent `loomweave analyze` on the same DB is handled by the existing `PRAGMA busy_timeout=5000` plus the -`clarion-storage::retry` capped-backoff layer; if a write still cannot land, it +`loomweave-storage::retry` capped-backoff layer; if a write still cannot land, it fails as a retryable error and Wardline degrades to the SP8 re-run (enrich-only). Operationally, a write-enabled `serve` and a concurrent `analyze` are not expected to write the same DB at the same time; this is documented, not enforced beyond the @@ -98,12 +98,12 @@ CREATE TABLE wardline_taint_facts ( **Why a dedicated table, not the schema-reserved `wardline` column on `entities`:** -1. **No clobber.** `clarion analyze` builds every `EntityRecord` with +1. **No clobber.** `loomweave analyze` builds every `EntityRecord` with `wardline_json: None` and the `entities` UPSERT sets `wardline = excluded.wardline`, so any taint fact written into that column would be wiped on the next re-analyze. A separate table is never touched by the entity UPSERT. -2. **Clean ownership.** Wardline's data lives in a Wardline-named table; Clarion's - `entities` table stays Clarion-owned. This makes the "Wardline-specific, not a +2. **Clean ownership.** Wardline's data lives in a Wardline-named table; Loomweave's + `entities` table stays Loomweave-owned. This makes the "Wardline-specific, not a generic blob bus" federation guard structural. 3. **Queryable metadata.** `scan_id` and `content_hash_at_compute` are real columns (no parsing of the opaque blob), enabling observability and an optional future @@ -146,7 +146,7 @@ pre-designed, and bundling it into the store core would balloon the work. ## 5. Error handling (enrich-only, no fabrication) - **Write contention / Filigree-style outage** → retryable error; Wardline retries - or falls back to SP8. Clarion never corrupts or partially-merges two scans for + or falls back to SP8. Loomweave never corrupts or partially-merges two scans for the same entity (per-entity replace is atomic at the row level). - **Unresolved qualname on write** → not an error; returned in `unresolved_qualnames` so Wardline can fall back rather than guess. @@ -154,8 +154,8 @@ pre-designed, and bundling it into the store core would balloon the work. it as stale and recomputes. No fabrication. - **`project` guard mismatch** → reject the request (wrong project); never serve cross-project data. -- **Opaque blob** → Clarion stores and returns `wardline_json` verbatim; a future - `wardline-taint-2` schema needs no Clarion change. +- **Opaque blob** → Loomweave stores and returns `wardline_json` verbatim; a future + `wardline-taint-2` schema needs no Loomweave change. ## 6. What Wardline guarantees in return (recorded, from request §6) @@ -182,7 +182,7 @@ reciprocal half of the enrich-only contract. ## 8. Issue breakdown (`release:1.1`, umbrella + children) -1. **W.0 — ADR.** "Clarion as Wardline taint-fact store (read+write HTTP surface)": +1. **W.0 — ADR.** "Loomweave as Wardline taint-fact store (read+write HTTP surface)": the federation verdict (§2), the read-only→read+write shift, the writer-actor concurrency posture, and the *not-a-general-blob-store* guard. *Blocks the rest.* 2. **W.1 — Storage.** Migration `0002` + `wardline_taint_facts` + writer-actor @@ -205,11 +205,11 @@ W.1 is the spine; W.2 ‖ W.3 ‖ W.4 fan out from it; W.5 documents what shippe - **No general-purpose blob store.** The surface is Wardline-specific by design (§2). Other siblings do not get this API by extension. -- **No Clarion parsing of `wardline_json`.** The chain walk and all taint semantics +- **No Loomweave parsing of `wardline_json`.** The chain walk and all taint semantics stay Wardline-side. - **No replacement of the SP8 stateless re-run.** It is the permanent standalone fallback; SP9 is a layered optimization, never a hard dependency. -- **No overlay-scan / full N-hop chain as Clarion features.** The store backs them; +- **No overlay-scan / full N-hop chain as Loomweave features.** The store backs them; they get their own Wardline specs. - **No ADR-029 issue↔entity bindings.** Adjacent, separate feature (request §9). - **No mandatory lifecycle cascade/prune machinery** beyond the freshness gate @@ -217,7 +217,7 @@ W.1 is the spine; W.2 ‖ W.3 ‖ W.4 fan out from it; W.5 documents what shippe ## 10. References -- Wardline request: `wardline/docs/integration/2026-05-30-wardline-clarion-taint-store-requirements.md`. +- Wardline request: `wardline/docs/integration/2026-05-30-wardline-loomweave-taint-store-requirements.md`. - ADR-011 (writer-actor), ADR-014 (HTTP read API), ADR-018 (identity reconciliation), ADR-034 (HMAC inbound auth). - `docs/federation/contracts.md` — read/consume contract surface. diff --git a/docs/superpowers/specs/2026-06-01-clarion-roadmap-to-first-class.md b/docs/superpowers/specs/2026-06-01-loomweave-roadmap-to-first-class.md similarity index 73% rename from docs/superpowers/specs/2026-06-01-clarion-roadmap-to-first-class.md rename to docs/superpowers/specs/2026-06-01-loomweave-roadmap-to-first-class.md index 2ba3fa4c..81844b07 100644 --- a/docs/superpowers/specs/2026-06-01-clarion-roadmap-to-first-class.md +++ b/docs/superpowers/specs/2026-06-01-loomweave-roadmap-to-first-class.md @@ -1,18 +1,18 @@ -# Clarion — the road to first-class (roadmap & final form) +# Loomweave — the road to first-class (roadmap & final form) **Date:** 2026-06-01 -**Status:** Living reference (roadmap; companion to the Loom goal-state case study) -**Scope:** Clarion's **final form** as a first-class, enterprise-capable code-intelligence -engine — and the staged path to it — given the Loom operating model and invariants settled +**Status:** Living reference (roadmap; companion to the Weft goal-state case study) +**Scope:** Loomweave's **final form** as a first-class, enterprise-capable code-intelligence +engine — and the staged path to it — given the Weft operating model and invariants settled across the 2026-06-01 design sessions. Sibling to -`2026-06-01-loom-goal-state-case-study.md` (the suite umbrella) and -`2026-06-01-loom-stable-entity-identity-conformance.md` (the SEI keystone, where Clarion's +`2026-06-01-weft-goal-state-case-study.md` (the suite umbrella) and +`2026-06-01-weft-stable-entity-identity-conformance.md` (the SEI keystone, where Loomweave's conformance obligations and pre-lock requirements are logged — see also Appendix A). > **The thesis filter governs every line of this roadmap.** "Bring it to enterprise > level" means enterprise *capability* delivered as **opt-in layers** — never enterprise > *weight* in the base. The zero-dependency base stays zero-dependency. An agent or team -> that only runs `clarion analyze` and `clarion serve` for MCP navigation pays nothing for +> that only runs `loomweave analyze` and `loomweave serve` for MCP navigation pays nothing for > SEI infrastructure, governance attestations, or the full dossier. Those are layers they > switch on. @@ -20,7 +20,7 @@ conformance obligations and pre-lock requirements are logged — see also Append ## 0. The final form, in one sentence -> Clarion becomes the **best code-intelligence engine in the Loom suite** — the sole +> Loomweave becomes the **best code-intelligence engine in the Weft suite** — the sole > identity authority, the catalog every agent and tool keys its facts against, the source > of one-call structural mastery — with a **refactor-stable entity identity** (SEI) that > makes every cross-tool binding survive the operations developers actually perform, and @@ -35,9 +35,9 @@ deliberately corrects that imbalance by leading with the first. --- -## 1. Half 1 — code intelligence quality (the foundation; mostly Clarion-autonomous) +## 1. Half 1 — code intelligence quality (the foundation; mostly Loomweave-autonomous) -This is where "first-class" starts. A Clarion that is a perfect SEI authority but serves +This is where "first-class" starts. A Loomweave that is a perfect SEI authority but serves a thin MCP surface or a shallow entity catalog is not first-class. None of this half is gated on a sibling tool. @@ -66,9 +66,9 @@ without the CLI authoring workflow, staleness-driven review prompts, or the `propose_guidance → observation → promote` lifecycle fully wired into the MCP surface. First-class means: -- CLI `clarion guidance create/edit/show/list/promote` complete +- CLI `loomweave guidance create/edit/show/list/promote` complete - Wardline-derived guidance auto-generation stable and tested against real Wardline output -- `CLA-FACT-GUIDANCE-CHURN-STALE` and `CLA-FACT-GUIDANCE-ORPHAN` signals visible to +- `LMWV-FACT-GUIDANCE-CHURN-STALE` and `LMWV-FACT-GUIDANCE-ORPHAN` signals visible to operators in a reviewable form - Guidance import/export for team sharing @@ -83,12 +83,12 @@ core changes. This requires: implemented but not externally documented) - Plugin distribution via GitHub Release assets working end-to-end (ADR-033 baseline is specified; needs validation with a real third-party plugin) -- `clarion-plugin-fixture` contract exported as the conformance harness new plugin +- `loomweave-plugin-fixture` contract exported as the conformance harness new plugin authors run ### 1.4 Analysis pipeline: resumability and incremental runs -At v1.0, `clarion analyze` is wipe-and-rerun except at phase-boundary checkpoints. For +At v1.0, `loomweave analyze` is wipe-and-rerun except at phase-boundary checkpoints. For large codebases (elspeth target: ~1,100 files, 9-phase pipeline) this is an operational burden. First-class means: @@ -103,7 +103,7 @@ burden. First-class means: ### 1.5 Operational quality -- `clarion doctor` (shipped in v1.1) is the right model; extend it to cover DB health, +- `loomweave doctor` (shipped in v1.1) is the right model; extend it to cover DB health, plugin availability, and configuration validation - Cost estimation accuracy: target the ±50% NFR-COST-03 bound with a validated elspeth run; close known overestimation paths (subsystem synthesis cost, prompt-cache hit rates) @@ -112,21 +112,21 @@ burden. First-class means: --- -## 2. Half 2 — first-class Loom citizen (the layers; mostly gated) +## 2. Half 2 — first-class Weft citizen (the layers; mostly gated) ### 2.1 SEI authority — identity minting, retention, matching, and resolution -*(Clarion-autonomous for groundwork; full SEI gated on lock — see Appendix A)* +*(Loomweave-autonomous for groundwork; full SEI gated on lock — see Appendix A)* -Clarion is the sole identity authority for the suite. Every cross-tool binding — Wardline +Loomweave is the sole identity authority for the suite. Every cross-tool binding — Wardline taint facts, Filigree issue associations, `legis` governance attestations — keys on an SEI -that Clarion mints and resolves. Everyone else is a consumer. This is the heaviest Clarion +that Loomweave mints and resolves. Everyone else is a consumer. This is the heaviest Loomweave obligation in the second half. **Shape-independent groundwork (safe to start before lock):** -- **Prior-index state retention.** Today `clarion analyze` is wipe-and-rerun with no - retained prior snapshot (`clarion-mcp/src/index_diff.rs`). The matcher needs to diff +- **Prior-index state retention.** Today `loomweave analyze` is wipe-and-rerun with no + retained prior snapshot (`loomweave-mcp/src/index_diff.rs`). The matcher needs to diff against the previous run's SEI↔locator+body-hash+signature map. A lightweight side table retaining the prior-run map is the prerequisite for everything else in this section — sequence it first, since it also unblocks file-level incremental analysis @@ -134,7 +134,7 @@ obligation in the second half. **After SEI lock:** -- **SEI minting.** On first SEI-aware run, mint a `clarion:eid:` for every entity +- **SEI minting.** On first SEI-aware run, mint a `loomweave:eid:` for every entity and persist it. The entity ID format (today `{plugin}:{kind}:{qualname}`) is now the *locator*; SEI is the identity. - **Deterministic re-binding matcher.** Three cases: locator unchanged (carry SEI), @@ -157,27 +157,27 @@ obligation in the second half. ### 2.2 HTTP linkages — serving callers/callees over HTTP -*(Clarion-autonomous; co-equal with SEI for the combination matrix)* +*(Loomweave-autonomous; co-equal with SEI for the combination matrix)* -Today callers/callees are **MCP-only**. The Wardline + Clarion dossier -(`2026-06-01-wardline-loom-entity-dossier-design.md`) requires linkages over HTTP so +Today callers/callees are **MCP-only**. The Wardline + Loomweave dossier +(`2026-06-01-wardline-weft-entity-dossier-design.md`) requires linkages over HTTP so Wardline (and any other HTTP consumer) can pull structural context without running a full -MCP session. This is gated on nothing except Clarion shipping it. +MCP session. This is gated on nothing except Loomweave shipping it. - Add `GET /api/v1/entities/{id}/callers` and `.../callees` (and the batch variants) to the existing HTTP read API (ADR-034 defines the transport; these are new endpoints on an existing surface) - Pagination and confidence-tier filtering consistent with the MCP `callers_of` tool - `_capabilities` advertising `linkages: { http: true }` so consumers can detect - pre-linkage Clarion and degrade rather than crash + pre-linkage Loomweave and degrade rather than crash - Once shipped, **this and SEI together close the dossier gate** for the - Wardline + Clarion combination (goal-state checklist item 3) + Wardline + Loomweave combination (goal-state checklist item 3) -### 2.3 The dossier — Clarion's participation in the one-call mastery read +### 2.3 The dossier — Loomweave's participation in the one-call mastery read -*(Gated on Clarion SEI + Clarion HTTP linkages)* +*(Gated on Loomweave SEI + Loomweave HTTP linkages)* -When SEI and HTTP linkages are both live, Clarion's contribution to the dossier becomes: +When SEI and HTTP linkages are both live, Loomweave's contribution to the dossier becomes: - Structural context: entity metadata, containment chain, subsystem membership - Linkages: callers, callees (both keyed on SEI, served over HTTP) @@ -186,7 +186,7 @@ When SEI and HTTP linkages are both live, Clarion's contribution to the dossier - Two-axis freshness status: SEI alive/orphaned + content_hash fresh/stale Wardline contributes taint posture; Filigree contributes open work. The three pieces -combine in the dossier envelope; Clarion is not the dossier assembler — it is the +combine in the dossier envelope; Loomweave is not the dossier assembler — it is the structural and identity contributor. ### 2.4 Trust vocabulary convergence and `legis` governance @@ -194,42 +194,42 @@ structural and identity contributor. *(Gated on `legis` existing)* The suite converges on **one** trust vocabulary — Wardline's grammar delivering -elspeth's effects (custody, fabrication test, fail-closed boundaries) in Loom's own -terms, not elspeth's `tier1/2/3` naming. Clarion's role: carry `declared_tier` and +elspeth's effects (custody, fabrication test, fail-closed boundaries) in Weft's own +terms, not elspeth's `tier1/2/3` naming. Loomweave's role: carry `declared_tier` and `wardline_groups` on entities as it does today; the vocabulary these values come from -converges via the suite's trust-vocabulary pass (that pass is not Clarion's to lead). +converges via the suite's trust-vocabulary pass (that pass is not Loomweave's to lead). -When `legis` ships, governance attestations key on SEI — Clarion is already the identity -authority, so no new Clarion surface is required. Clarion's `lineage` endpoint is the -audit spine `legis` reads. This is a consumer relationship: `legis` consumes Clarion's -SEI and lineage; Clarion gains nothing new to build. +When `legis` ships, governance attestations key on SEI — Loomweave is already the identity +authority, so no new Loomweave surface is required. Loomweave's `lineage` endpoint is the +audit spine `legis` reads. This is a consumer relationship: `legis` consumes Loomweave's +SEI and lineage; Loomweave gains nothing new to build. --- ## 3. Staging — by capability milestone and dependency gate -| # | Milestone | Gate | Clarion's position | +| # | Milestone | Gate | Loomweave's position | |---|---|---|---| | 1 | **MCP surface completion** — full tool catalogue, exploration shortcuts, guidance MCP lifecycle | none (autonomous) | owns it end-to-end; highest-leverage unblocked item | | 2 | **Guidance system maturity** — CLI workflow, Wardline-derived guidance, staleness signals | none (autonomous) | owns it end-to-end | | 3 | **Prior-index state retention** — retained SEI↔locator+body-hash+signature map across re-index | none (autonomous; shape-independent SEI groundwork) | owns it; also unblocks §1.4 incremental analysis | | 4 | **HTTP linkages** — callers/callees over HTTP read API | none (autonomous) | owns it; co-equal SEI gate for dossier | | 5 | **SEI authority** — minting, matcher, lineage, wire contract, migration | **SEI lock** (§0.3 of SEI spec) | owns it; gated on lock, not on siblings | -| 6 | **Dossier participation** — structural + identity contribution to the one-call read | Clarion SEI **+** HTTP linkages (§4 above; internal gates only) | closes when 4 + 5 done | +| 6 | **Dossier participation** — structural + identity contribution to the one-call read | Loomweave SEI **+** HTTP linkages (§4 above; internal gates only) | closes when 4 + 5 done | | 7 | **Multi-language plugin** — second-language plugin, manifest protocol published | none (autonomous; milestone 1 sets the protocol) | owns it | -| 8 | **Governance consumption** — SEI lineage as legis audit spine, attestation compatibility | `legis` exists | thin on Clarion's side; waits on sibling | +| 8 | **Governance consumption** — SEI lineage as legis audit spine, attestation compatibility | `legis` exists | thin on Loomweave's side; waits on sibling | -**Honest gating picture.** Milestones 1–5 are Clarion's to finish alone. Milestones 6 -closes with internal gates only (no sibling dependency beyond what Clarion builds). +**Honest gating picture.** Milestones 1–5 are Loomweave's to finish alone. Milestones 6 +closes with internal gates only (no sibling dependency beyond what Loomweave builds). Milestone 7 is autonomous. Milestone 8 waits on `legis`. The suite's dossier gate -(Wardline milestone 4) waits on Clarion milestones 4 and 5 — and those two are both -**autonomous Clarion work**. +(Wardline milestone 4) waits on Loomweave milestones 4 and 5 — and those two are both +**autonomous Loomweave work**. --- ## 4. North Star — any entity, any language, any producer -The general form of Clarion's identity: SEI is a property of the *authored thing*, not +The general form of Loomweave's identity: SEI is a property of the *authored thing*, not of any one language's qualname. At goal state, any entity — function, class, module, commit, CI artefact, infrastructure resource — describable by any plugin can carry a SEI and participate in the combination matrix. The Python plugin is the validating @@ -244,26 +244,26 @@ ships the deterministic subset; fuzzy is the upper bound the design is compatibl ## 5. The throughline -Every item above is an **opt-in layer**. The base is: `clarion analyze` builds the catalog; -`clarion serve` makes it queryable. That is the zero-cost entry point and it must stay +Every item above is an **opt-in layer**. The base is: `loomweave analyze` builds the catalog; +`loomweave serve` makes it queryable. That is the zero-cost entry point and it must stay that way. SEI infrastructure, HTTP linkages, the dossier envelope, governance audit — all are switches a user flips by connecting sibling tools or expanding configuration. A solo user running MCP navigation over a Python project never pays for suite infrastructure they did not turn on. -That is enterprise/first-class on Clarion's terms: the richest code-intelligence view +That is enterprise/first-class on Loomweave's terms: the richest code-intelligence view in the suite, delivered without forcing anyone into the suite. --- ## Appendix A — SEI Conformance Position (pre-lock requirements intake) -**Status:** Clarion is the SEI authority. Its §5 obligations are accepted as stated. -This appendix records Clarion's **pre-lock requirements** per SEI spec §0.3 and §0.5. +**Status:** Loomweave is the SEI authority. Its §5 obligations are accepted as stated. +This appendix records Loomweave's **pre-lock requirements** per SEI spec §0.3 and §0.5. ### Confirmed obligations (§5) -| Obligation | Clarion's position | +| Obligation | Loomweave's position | |---|---| | Mint + persist SEI | Confirmed; requires SEI lock first | | Retain prior-index state (§3.1) | Confirmed; shape-independent groundwork starts now | @@ -276,13 +276,13 @@ This appendix records Clarion's **pre-lock requirements** per SEI spec §0.3 and **REQ-C-01 — "Signature" needs a formal definition. → RESOLVED in ADR-038 (2026-06-02).** The matcher's move case ("identical body hash and identical signature at a new module") -depends on a "signature" that Clarion does not currently store as a discrete field. +depends on a "signature" that Loomweave does not currently store as a discrete field. **Resolution:** a plugin-declared, versioned JSON object stored verbatim in a plain (non-unique) `entities.signature TEXT`, compared by string equality; `null` where comparison is not meaningful. Caveat recorded in ADR-038: signature is near-redundant for the *v1 deterministic* move case (byte-identical body already implies identical signature) — it is carried for spec-conformance and as the load-bearing input to the North-Star fuzzy matcher. See the integrated delivery plan -(`docs/superpowers/plans/2026-06-02-clarion-integrated-delivery-plan.md`) REQ-C-01. +(`docs/superpowers/plans/2026-06-02-loomweave-integrated-delivery-plan.md`) REQ-C-01. **REQ-C-02 — SEI token scheme. → RESOLVED in ADR-038 (2026-06-02), correcting this entry.** This entry originally preferred a content-addressed `blake3(locator-at-birth)`. **A peer review @@ -291,7 +291,7 @@ against the code found that wrong:** (1) the entity field it would key on (`firs collision-on-reuse flaw; and (2) the "make the token a pure function to preserve determinism" frame is itself the error — **SEI allocation is stateful** (the matcher carries-or-mints against the persisted binding store), so reproducibility of the SEI *value* comes from the `sei_bindings` table, -not from re-deriving the token. **Resolution:** `clarion:eid:` where `mint_run_id` is the minting run's UUID — collision-free under locator reuse (a reused locator is only ever minted, in a later run), no time/RNG component. The byte-identical-run guarantee covers entity/edge/finding *state*, explicitly **not** identity values. @@ -299,41 +299,41 @@ See the integrated delivery plan REQ-C-02 and ADR-038. **REQ-C-03 — Prior-index retention scope to be scoped as a side table, not a full snapshot.** The matcher needs "the prior SEI↔locator + body-hash + signature map." This is a -narrow, keyed map — not a full DB snapshot or a full prior `clarion.db`. The right +narrow, keyed map — not a full DB snapshot or a full prior `loomweave.db`. The right storage is a lightweight side table (migration M4 or M5) that persists across re-index -and is cleared only on explicit `--force` reinit. Clarion records this to prevent the +and is cleared only on explicit `--force` reinit. Loomweave records this to prevent the requirement from being interpreted as "keep the full prior DB around," which would -significantly change storage cost and the git-committable posture. **Clarion flags this +significantly change storage cost and the git-committable posture. **Loomweave flags this as a design constraint, not a blocker.** **REQ-C-04 — MCP surface treatment needs a conformance obligation.** -SEI spec §4 defines the wire contract as HTTP endpoints. Clarion's MCP tools +SEI spec §4 defines the wire contract as HTTP endpoints. Loomweave's MCP tools (`find_entity`, `entity_at`, `callers_of`, `neighborhood`, etc.) also return entity ids today. After SEI, the question is: should MCP responses carry SEI, locator, or both? -If a consumer (e.g. a Wardline MCP-mode session) receives a locator from Clarion's MCP +If a consumer (e.g. a Wardline MCP-mode session) receives a locator from Loomweave's MCP surface and uses it as a cross-tool binding key, the SEI invariant is broken regardless -of what the HTTP surface does. **Clarion requests that §5 be extended to cover the MCP +of what the HTTP surface does. **Loomweave requests that §5 be extended to cover the MCP surface, or that the spec explicitly limits the conformance obligation to the HTTP surface and documents the MCP locator exception.** **REQ-C-05 — Git-rename signal: source and surface.** -v1 sources git-rename detection in Clarion (shell/libgit2). The analysis pipeline +v1 sources git-rename detection in Loomweave (shell/libgit2). The analysis pipeline currently reads git metadata for `first_seen_commit`/`last_seen_commit` via `git log --follow`-equivalent calls during Phase 1.5. Extending this to detect renames is feasible but requires new code surface. If `legis` eventually owns the git interface and supplies this signal, the matcher should consume "a git-rename signal -interface" from the start rather than `Clarion::git_rename()` directly. **Clarion +interface" from the start rather than `Loomweave::git_rename()` directly. **Loomweave notes this as an interface boundary to define cleanly in v1, even if `legis` is not yet built.** -### Non-requirements (Clarion's scope boundary) +### Non-requirements (Loomweave's scope boundary) -Clarion is **not** the dossier assembler. The dossier envelope is produced by the -consumer (Wardline in the current design) by calling Clarion's HTTP surface. Clarion +Loomweave is **not** the dossier assembler. The dossier envelope is produced by the +consumer (Wardline in the current design) by calling Loomweave's HTTP surface. Loomweave does not aggregate Wardline taint facts or Filigree issues — it contributes its slice and the consumer composes. This is consistent with the enrichment-not-load-bearing -axiom and Clarion's existing enrich-only posture. +axiom and Loomweave's existing enrich-only posture. -Clarion is **not** the trust vocabulary convergence lead. Wardline owns the grammar; -`legis` governs it. Clarion carries `declared_tier` and `wardline_groups` verbatim and -updates them when Wardline's schema updates. Clarion does not adjudicate trust. +Loomweave is **not** the trust vocabulary convergence lead. Wardline owns the grammar; +`legis` governs it. Loomweave carries `declared_tier` and `wardline_groups` verbatim and +updates them when Wardline's schema updates. Loomweave does not adjudicate trust. diff --git a/docs/superpowers/specs/2026-06-02-clarion-dossier-participation.md b/docs/superpowers/specs/2026-06-02-loomweave-dossier-participation.md similarity index 72% rename from docs/superpowers/specs/2026-06-02-clarion-dossier-participation.md rename to docs/superpowers/specs/2026-06-02-loomweave-dossier-participation.md index 0485a40a..31349446 100644 --- a/docs/superpowers/specs/2026-06-02-clarion-dossier-participation.md +++ b/docs/superpowers/specs/2026-06-02-loomweave-dossier-participation.md @@ -1,46 +1,46 @@ -# Clarion — dossier participation surface (WS4) +# Loomweave — dossier participation surface (WS4) **Date:** 2026-06-02 **Status:** Specified + implemented (Wave 2 / WS4) -**Scope:** Name and pin the EXACT Clarion HTTP surface the cross-tool entity +**Scope:** Name and pin the EXACT Loomweave HTTP surface the cross-tool entity **dossier assembler** (Wardline) reads, so a complete, freshness-stamped, -SEI-keyed view of an entity is buildable over Clarion's HTTP API alone — and +SEI-keyed view of an entity is buildable over Loomweave's HTTP API alone — and **stays correct after the entity is renamed**. This is the wave that closes the suite's **core paradise**. **Authorities:** -- Program design `2026-06-02-clarion-first-class-program-design.md` §4 (Wave 2), §5 invariants, D3. -- Integrated delivery plan `2026-06-02-clarion-integrated-delivery-plan.md` Phase 3 (T3.2). -- Wardline dossier design `/home/john/wardline/docs/superpowers/specs/2026-06-01-wardline-loom-entity-dossier-design.md` — the consumer. +- Program design `2026-06-02-loomweave-first-class-program-design.md` §4 (Wave 2), §5 invariants, D3. +- Integrated delivery plan `2026-06-02-loomweave-integrated-delivery-plan.md` Phase 3 (T3.2). +- Wardline dossier design `/home/john/wardline/docs/superpowers/specs/2026-06-01-wardline-weft-entity-dossier-design.md` — the consumer. - ADR-038 (SEI token + signature). Federation pin: `docs/federation/contracts.md` §Dossier participation surface. --- -## 1. Framing: Clarion serves slices, it does not assemble +## 1. Framing: Loomweave serves slices, it does not assemble -The dossier is **assembled by Wardline** (`core/dossier.py`); Clarion and Filigree -are *read sources*. Clarion's WS4 obligation is exactly two things: +The dossier is **assembled by Wardline** (`core/dossier.py`); Loomweave and Filigree +are *read sources*. Loomweave's WS4 obligation is exactly two things: 1. **Guarantee** every slice the assembler needs is reachable over HTTP (the assembler is an HTTP client; it has no MCP session). 2. **Pin** that surface so the contract is explicit and cannot silently regress. -Clarion does **not** build a dossier envelope, aggregate Wardline taint facts, or -proxy Filigree issues. That separation is the `loom.md` §5 enrich-only line and a +Loomweave does **not** build a dossier envelope, aggregate Wardline taint facts, or +proxy Filigree issues. That separation is the `weft.md` §5 enrich-only line and a hard boundary of this wave: a sibling may add information to another product's view but must never become the assembler for it. ## 2. The surface (each slice verified HTTP-reachable + pinned) -| Dossier section (Wardline envelope §5) | Clarion endpoint | Returns | Origin | +| Dossier section (Wardline envelope §5) | Loomweave endpoint | Returns | Origin | |---|---|---|---| | `identity` (entity_id, content_hash) — **content axis** | `POST /api/v1/identity/resolve` | `{ sei, current_locator, content_hash, alive }` | Wave 1 | | `identity` (alive/orphaned + rename lineage) — **identity axis** | `GET /api/v1/identity/sei/:sei`, `GET /api/v1/identity/lineage/:sei` | `{ alive, current_locator?, content_hash?, lineage? }` | Wave 1 | | `linkages.callers` / `linkages.callees` | `GET /api/v1/entities/:id/callers` · `…/callees` (+ `:batch-get`) | `{ entity_id, callers\|callees:[{entity_id,confidence,call_site_count}], total, truncated }` | Wave 0 | | file context | `GET /api/v1/files?path=&language=` (+ `:resolve`, `/batch`) | `{ entity_id, content_hash, canonical_path, language, … }` | pre-1.0 | -| `work` (Filigree associations) | **Filigree's own** `GET /api/entity-associations?entity_id=…` (ADR-029) | bound-issue rows | **not Clarion** — §4 | +| `work` (Filigree associations) | **Filigree's own** `GET /api/entity-associations?entity_id=…` (ADR-029) | bound-issue rows | **not Loomweave** — §4 | -All Clarion `/api/v1/*` routes share one auth posture (HMAC `X-Loom-Component` +All Loomweave `/api/v1/*` routes share one auth posture (HMAC `X-Weft-Component` preferred; loopback exempt) and one error envelope (`{ error, code, details? }`), already pinned in `contracts.md`. `linkages.http: true` and `sei: { supported: true, version: 1 }` in `GET /api/v1/_capabilities` let the @@ -49,7 +49,7 @@ assembler gate on capability rather than probe. ## 3. Two-axis freshness (the no-false-green property) The dossier must reason on a typed freshness contract, never eyeball staleness. -Clarion serves **two independent axes**; neither is inferred from the other: +Loomweave serves **two independent axes**; neither is inferred from the other: - **Content axis** — `resolve(locator).content_hash` is the entity's current whole-file/body blake3. The assembler compares its stored fact's write-time hash @@ -61,33 +61,33 @@ Clarion serves **two independent axes**; neither is inferred from the other: `orphaned` — surfaced honestly, never silently treated as clean. This is what closes the dossier's ORPHAN gap (Wardline design §6.1, §10.2): the -keystone refactor-stable identity is exactly Clarion's SEI, and a renamed function +keystone refactor-stable identity is exactly Loomweave's SEI, and a renamed function now yields a complete dossier with its facts intact rather than an empty section. ## 4. Filigree associations — the resolved "gap" (decision, not omission) The Wardline dossier reads its `work` section **directly from Filigree's own** `GET /api/entity-associations?entity_id=…` (ADR-029, frozen), comparing -`content_hash_at_attach` itself to set the `DRIFT` verdict. Clarion's `issues_for` +`content_hash_at_attach` itself to set the `DRIFT` verdict. Loomweave's `issues_for` is MCP-only, but that is **not** a dossier gap: -- Adding a Clarion HTTP endpoint that serves Filigree associations would make - Clarion a **proxy/aggregator** for a sibling's data — a direct violation of the - enrich-only axiom (`loom.md` §5: semantic/initialization/pipeline coupling) and - the Wave 2 hard boundary ("do NOT aggregate Filigree issues into a Clarion +- Adding a Loomweave HTTP endpoint that serves Filigree associations would make + Loomweave a **proxy/aggregator** for a sibling's data — a direct violation of the + enrich-only axiom (`weft.md` §5: semantic/initialization/pipeline coupling) and + the Wave 2 hard boundary ("do NOT aggregate Filigree issues into a Loomweave object"). - The join is already federation-correct: all three tools key on **one identity**. - Clarion's WS4 contribution to `work` is precisely the **join key** — the SEI from + Loomweave's WS4 contribution to `work` is precisely the **join key** — the SEI from `resolve` — which Filigree associations (and Wardline taint facts) bind on. -**Recommendation:** the assembler reads Filigree directly and keys on Clarion's -SEI. No Clarion endpoint is added. (If a future consumer genuinely needs -associations over Clarion's HTTP, that is a new ADR with an enrich-only +**Recommendation:** the assembler reads Filigree directly and keys on Loomweave's +SEI. No Loomweave endpoint is added. (If a future consumer genuinely needs +associations over Loomweave's HTTP, that is a new ADR with an enrich-only justification, not a silent fill.) ## 5. `scc_peers` — named, decided, not silently dropped -The Wardline envelope lists `scc_peers[]` under `linkages`. Clarion exposes +The Wardline envelope lists `scc_peers[]` under `linkages`. Loomweave exposes subsystem **clustering** (`subsystem_members` / `subsystem_of`, MCP-only), which is **not** strongly-connected-component membership — serving it under `scc_peers` would be a semantic mismatch. The dossier already degrades gracefully on partial @@ -102,7 +102,7 @@ follow-up, not a blocker. ## 6. Conformance / proof - Each slice is independently pinned and tested (files, callers/callees, identity - resolve/sei/lineage) in `contracts.md` + `crates/clarion-cli/tests/serve.rs` and + resolve/sei/lineage) in `contracts.md` + `crates/loomweave-cli/tests/serve.rs` and `http_read.rs`. - The **composition** is proven end-to-end against a renamed-function fixture by `serve_http_dossier_participation_surface_serves_a_renamed_function` @@ -117,10 +117,10 @@ follow-up, not a blocker. - [x] Every depended-on endpoint HTTP-reachable and pinned in `contracts.md` (or the gap surfaced with a recommendation — Filigree-direct §4, scc_peers §5). - [x] Two-axis freshness explicit (content via `resolve`, identity via `resolve_sei`). -- [x] `dossier(entity)` achievable over Clarion's HTTP surface for a renamed +- [x] `dossier(entity)` achievable over Loomweave's HTTP surface for a renamed function — demonstrated by the serve e2e. -**Core paradise (Clarion's half):** a rename/move of a function preserves its +**Core paradise (Loomweave's half):** a rename/move of a function preserves its SEI-keyed identity and structural linkages over HTTP, with honest two-axis freshness — the assembler composes the rest. WS4 closes here; it does not enter the parallel band (WS5–WS8) or WS9. diff --git a/docs/superpowers/specs/2026-06-02-clarion-first-class-program-design.md b/docs/superpowers/specs/2026-06-02-loomweave-first-class-program-design.md similarity index 85% rename from docs/superpowers/specs/2026-06-02-clarion-first-class-program-design.md rename to docs/superpowers/specs/2026-06-02-loomweave-first-class-program-design.md index ecdd97e6..2f4dc640 100644 --- a/docs/superpowers/specs/2026-06-02-clarion-first-class-program-design.md +++ b/docs/superpowers/specs/2026-06-02-loomweave-first-class-program-design.md @@ -1,24 +1,24 @@ -# Clarion → First-Class — Program Specification +# Loomweave → First-Class — Program Specification **Date:** 2026-06-02 **Status:** Program-level design (governs the next several spec→plan cycles) -**Scope:** Decomposes the entire Clarion "road to first-class" effort into discrete workstreams, +**Scope:** Decomposes the entire Loomweave "road to first-class" effort into discrete workstreams, names each one's dependency gate and the artifact that already covers it, and sequences them into execution waves. This is a **program map**, not a design of any single workstream — each workstream gets its own spec→plan cycle when its wave opens. **Inputs / authorities:** -- `2026-06-01-clarion-roadmap-to-first-class.md` — the final-form target (both halves) -- `/home/john/wardline/docs/superpowers/specs/2026-06-02-clarion-priority-brief.md` — suite execution order -- `/home/john/wardline/docs/superpowers/specs/2026-06-01-loom-stable-entity-identity-conformance.md` — SEI standard (canonical) -- `2026-06-02-clarion-integrated-delivery-plan.md` — task-level plan for the critical-path workstreams -- `docs/clarion/adr/ADR-038-sei-token-and-signature.md` — the two locked SEI decisions +- `2026-06-01-loomweave-roadmap-to-first-class.md` — the final-form target (both halves) +- `/home/john/wardline/docs/superpowers/specs/2026-06-02-loomweave-priority-brief.md` — suite execution order +- `/home/john/wardline/docs/superpowers/specs/2026-06-01-weft-stable-entity-identity-conformance.md` — SEI standard (canonical) +- `2026-06-02-loomweave-integrated-delivery-plan.md` — task-level plan for the critical-path workstreams +- `docs/loomweave/adr/ADR-038-sei-token-and-signature.md` — the two locked SEI decisions --- ## 1. Purpose & the two reconciled orderings -Clarion is the **long pole** of suite "core paradise" (the one-call dossier that survives a rename), +Loomweave is the **long pole** of suite "core paradise" (the one-call dossier that survives a rename), and — uniquely — every one of its blockers is its own autonomous work, not a cross-tool negotiation. This program exists because the outstanding work is a **program, not a single plan**: 9 workstreams with different dependency gates, of which only two (SEI authority, HTTP linkages) are on the suite @@ -26,7 +26,7 @@ critical path and only one (SEI authority) is gated on an external event (SEI lo Two orderings are in tension, and this program reconciles them rather than picking a winner: -- **The roadmap's ordering** ("two co-equal halves; lead with code-intelligence"): a Clarion that is +- **The roadmap's ordering** ("two co-equal halves; lead with code-intelligence"): a Loomweave that is a perfect SEI authority but serves a thin MCP surface is **not** first-class. The code-intelligence half is the foundation of standalone value. - **The priority brief's ordering** ("suite-critical-path first"): each suite-unlocking item @@ -57,7 +57,7 @@ now?" means its design can be locked without waiting on an external event. - **Covered by:** ADR-038 (decisions) + integrated delivery plan Phase 2. **Specifiable now?** The shape is locked (ADR-038); *implementation* waits for SEI lock. The prior-index prerequisite lives in WS3 and is shippable now. -- **Cross-product coordination:** the cutover is a single scheduled release across Clarion/Filigree/ +- **Cross-product coordination:** the cutover is a single scheduled release across Loomweave/Filigree/ Wardline (single-owner release control — SEI spec §7.1). This is the only workstream with a cross-tool release dependency. @@ -84,10 +84,10 @@ now?" means its design can be locked without waiting on an external event. ### WS4 — Dossier participation -- **Half:** Suite-integration. **Gate:** WS1 + WS2 (both internal Clarion gates — no sibling wait). **Owns:** Clarion's slice of the dossier. -- **Scope:** document and pin the exact Clarion surface the dossier *assembler* (Wardline) calls: +- **Half:** Suite-integration. **Gate:** WS1 + WS2 (both internal Loomweave gates — no sibling wait). **Owns:** Loomweave's slice of the dossier. +- **Scope:** document and pin the exact Loomweave surface the dossier *assembler* (Wardline) calls: `resolve(locator)` → SEI, linkages over HTTP, file context, Filigree associations. Two-axis freshness - (SEI alive/orphaned + content fresh/stale). **Clarion is not the assembler** — it contributes a slice; + (SEI alive/orphaned + content fresh/stale). **Loomweave is not the assembler** — it contributes a slice; the consumer composes. - **Covered by:** integrated delivery plan Phase 3 (T3.2). **Specifiable now?** The contract can be drafted now; it *closes* only when WS1 + WS2 ship. @@ -99,7 +99,7 @@ now?" means its design can be locked without waiting on an external event. cursor/session model is ratified-away as never-built): read-side inspection (`guidance_for`, `findings_for`, `wardline_for`), faceted search, the exploration-elimination shortcuts, and `emit_observation`. Ground truth corrects the roadmap's "8 of ~35" — 19 tools already ship. -- **Covered by:** **DESIGNED** — `docs/superpowers/specs/2026-06-02-clarion-ws5-mcp-catalogue-design.md`. +- **Covered by:** **DESIGNED** — `docs/superpowers/specs/2026-06-02-loomweave-ws5-mcp-catalogue-design.md`. **Specifiable now?** Done; ready for an implementation plan. ### WS5b — Advanced MCP queries (semantic search + reachability) @@ -110,15 +110,15 @@ now?" means its design can be locked without waiting on an external event. heuristic findings that fail toward "live"). Split from WS5 because they need infrastructure beyond a catalog query — **scheduled, not deferred.** - **Covered by:** **DESIGNED + PLANNED** — - `docs/superpowers/plans/2026-06-02-clarion-ws5b-advanced-queries-plan.md`. One open owner-decision + `docs/superpowers/plans/2026-06-02-loomweave-ws5b-advanced-queries-plan.md`. One open owner-decision (D-WS5b-1, embedding provider). **Specifiable now?** Done; Part B can start immediately, Part A after D-WS5b-1. ### WS6 — Guidance maturity - **Half:** Code-intelligence. **Gate:** none (autonomous). **Owns:** the LLM-context-enrichment mechanism. -- **Scope:** the `clarion guidance` CLI (create/edit/show/list/promote); Wardline-derived guidance - auto-generation tested against real Wardline output; staleness signals (`CLA-FACT-GUIDANCE-CHURN-STALE`, +- **Scope:** the `loomweave guidance` CLI (create/edit/show/list/promote); Wardline-derived guidance + auto-generation tested against real Wardline output; staleness signals (`LMWV-FACT-GUIDANCE-CHURN-STALE`, `-ORPHAN`) surfaced reviewably; the `propose_guidance → observation → promote` anti-poisoning lifecycle; guidance import/export. - **Covered by:** roadmap §1.2 (listed, **not designed**) + integrated plan parallel track (MCP-P4). @@ -129,7 +129,7 @@ now?" means its design can be locked without waiting on an external event. - **Half:** Code-intelligence. **Gate:** none (autonomous). **Owns:** the plugin contract's generality. - **Scope:** stabilise + **publish** the plugin manifest protocol as an external spec (today implemented, not documented); validate GitHub-Release plugin distribution (ADR-033) with a real third-party plugin; - export `clarion-plugin-fixture` as the conformance harness new authors run; build a second-language + export `loomweave-plugin-fixture` as the conformance harness new authors run; build a second-language plugin (TS/Go/Rust — customer-demand-driven). North-Star: any entity, any language, *other producers* not a core rewrite. - **Covered by:** roadmap §1.3 (listed, **not designed**). **Specifiable now?** Yes — but see owner-decision @@ -138,7 +138,7 @@ now?" means its design can be locked without waiting on an external event. ### WS8 — Operational quality - **Half:** Code-intelligence. **Gate:** none (autonomous). **Owns:** operator-facing robustness. -- **Scope:** extend `clarion doctor` (shipped v1.1) to DB health, plugin availability, config validation; +- **Scope:** extend `loomweave doctor` (shipped v1.1) to DB health, plugin availability, config validation; validate cost-estimate accuracy against the ±50% NFR-COST-03 bound on a real elspeth run; surface summary-cache semantic-staleness (`stale_semantic: true`) before operators act on stale briefings. - **Covered by:** roadmap §1.5 — **explicitly cut from the integrated delivery plan as off-critical-path; @@ -147,11 +147,11 @@ now?" means its design can be locked without waiting on an external event. ### WS9 — `legis` governance consumption -- **Half:** Suite-integration. **Gate:** `legis` exists (external). **Owns:** nothing new — thin on Clarion's side. -- **Scope:** governance attestations key on SEI (Clarion is already the authority — no new surface); - Clarion's `lineage` endpoint is the audit spine `legis` reads; carry `declared_tier`/`wardline_groups` - verbatim (Clarion does **not** adjudicate trust — Wardline analyses, `legis` governs). Trust-vocabulary - convergence is a suite pass Clarion does not lead. +- **Half:** Suite-integration. **Gate:** `legis` exists (external). **Owns:** nothing new — thin on Loomweave's side. +- **Scope:** governance attestations key on SEI (Loomweave is already the authority — no new surface); + Loomweave's `lineage` endpoint is the audit spine `legis` reads; carry `declared_tier`/`wardline_groups` + verbatim (Loomweave does **not** adjudicate trust — Wardline analyses, `legis` governs). Trust-vocabulary + convergence is a suite pass Loomweave does not lead. - **Covered by:** roadmap §2.4. **Specifiable now?** Partially — the consumption contract can be sketched, but it is gated on `legis` shipping and is the lowest-priority workstream. @@ -184,10 +184,10 @@ WS1 → WS9 (governance keys on SEI); SEI lock gates WS1; WS5–WS8 depend on no Critical observations: - **WS3 is the keystone prerequisite.** It gates WS1 (matcher needs prior state) and powers WS4-adjacent incremental analysis. It is autonomous and must be sequenced first. -- **WS1 is the only externally-gated critical-path item** (SEI lock). Clarion's *shape* obligation for +- **WS1 is the only externally-gated critical-path item** (SEI lock). Loomweave's *shape* obligation for lock is already discharged (ADR-038); lock now waits on the other three subsystems + the oracle. -- **WS4 has only internal gates** — the dossier closes when Clarion finishes WS1 + WS2, with no sibling - wait. The suite's dossier gate (Wardline's milestone 4) therefore reduces to "Clarion ships WS1+WS2." +- **WS4 has only internal gates** — the dossier closes when Loomweave finishes WS1 + WS2, with no sibling + wait. The suite's dossier gate (Wardline's milestone 4) therefore reduces to "Loomweave ships WS1+WS2." - **WS5–WS8 are entirely ungated** and parallelisable against the critical path. --- @@ -223,7 +223,7 @@ thin MCP surface is *not* first-class), which is why it gets committed waves, no **So: WS5b is Wave 5.** It is delivered right after WS5 (Wave 4), concurrently with the suite track — a committed slot, not "someday." Its plan exists -(`../plans/2026-06-02-clarion-ws5b-advanced-queries-plan.md`); its one open item is D-WS5b-1 +(`../plans/2026-06-02-loomweave-ws5b-advanced-queries-plan.md`); its one open item is D-WS5b-1 (embedding provider), which gates only its Part A. **Readiness (the honest next-action per wave, not a deferral):** @@ -240,17 +240,17 @@ is floated. Remaining authoring: execution prompts for Waves 6–8 (Waves 0–5 ## 5. Cross-cutting invariants -Every workstream inherits these (from the priority brief §4–§5, the SEI standard, and `loom.md`): +Every workstream inherits these (from the priority brief §4–§5, the SEI standard, and `weft.md`): -1. **Opt-in layers, never weight in the base.** `clarion analyze` + `clarion serve` (MCP) stay zero-cost; +1. **Opt-in layers, never weight in the base.** `loomweave analyze` + `loomweave serve` (MCP) stay zero-cost; SEI infra, dossier, governance are switches. A solo Python user pays for nothing they didn't enable. -2. **Opacity.** SEI is opaque; only `resolve`/`resolve_sei` interpret it. Nothing parses `clarion:eid:…`. +2. **Opacity.** SEI is opaque; only `resolve`/`resolve_sei` interpret it. Nothing parses `loomweave:eid:…`. 3. **No binding keyed on a locator, on any surface** (HTTP *and* MCP carry SEI once WS1 ships). No MCP locator exception. 4. **Fail-closed / no false-green.** Unprovable match → mint + orphan; unknown/orphan/stale surfaced honestly, never silently patched. -5. **Enrich-only federation.** Clarion is not the dossier assembler, not the trust-vocabulary lead, not a - shared store. Each cross-product surface passes the `loom.md` §5 failure test. +5. **Enrich-only federation.** Loomweave is not the dossier assembler, not the trust-vocabulary lead, not a + shared store. Each cross-product surface passes the `weft.md` §5 failure test. 6. **Conformance proven, not assumed.** WS1 passes the SEI oracle; no grandfathering. 7. **Each workstream is a unit.** One purpose, well-defined interface, independently testable and specifiable. A workstream that can't be specced without dragging in another's internals has the wrong @@ -262,7 +262,7 @@ Every workstream inherits these (from the priority brief §4–§5, the SEI stan These are not blockers for Wave 0 but should be resolved before the waves that depend on them: -- **D1 — SEI lock timing (suite event, not Clarion's to call alone).** Clarion's shape obligation is +- **D1 — SEI lock timing (suite event, not Loomweave's to call alone).** Loomweave's shape obligation is discharged (ADR-038). Lock waits on Filigree/`legis`/Wardline reporting + the oracle encoding the resolutions. *Decision needed:* when to convene lock. *Affects:* Wave 1 start. - **D2 — Second-language plugin (WS7).** TypeScript, Go, or Rust, prioritised by customer demand @@ -274,7 +274,7 @@ These are not blockers for Wave 0 but should be resolved before the waves that d the side table in Wave 0; land incremental-skip behaviour with WS1 so the orphan-guard is co-designed. - **D4 — Hash-granularity harmonisation (SEI spec §2 note).** Filigree's `content_hash_at_attach` (entity-body) vs. Wardline's taint-fact freshness (whole-file) are two freshness granularities. The SEI - standard flags this as adjacent, out-of-scope work. *Decision needed:* whether Clarion drives a + standard flags this as adjacent, out-of-scope work. *Decision needed:* whether Loomweave drives a suite-wide reconciliation and in which wave. Recommendation: defer to a post-paradise suite pass; name it now so it isn't silently inherited. - **D5 — Guidance lifecycle depth (WS6).** How much of `propose → observation → promote` and the @@ -293,11 +293,11 @@ The program is complete when the roadmap's goal-state checklist is met: - [ ] **WS1+WS2+WS3+WS4 → core paradise:** a rename/move of a function preserves every Wardline fact and Filigree association on it (or surfaces an honest orphan); `dossier(entity)` returns a complete, - freshness-stamped, SEI-keyed envelope; Clarion serves linkages over HTTP. + freshness-stamped, SEI-keyed envelope; Loomweave serves linkages over HTTP. - [ ] **WS5+WS6+WS7+WS8 → standalone first-class:** full MCP catalogue, mature guidance, a published - plugin protocol with a second-language plugin, and operator-grade robustness — Clarion is the best + plugin protocol with a second-language plugin, and operator-grade robustness — Loomweave is the best code-intelligence engine in the suite *on its own*, not only as a citizen. - [ ] **WS9 → governed paradise:** `legis` keys attestations on SEI and reads lineage as its audit spine; trust vocabulary converged suite-wide. Opt-in; invisible to a solo project. -- [ ] Every cross-product surface is demonstrably an instance of the `loom.md` §2 custody axiom and passes +- [ ] Every cross-product surface is demonstrably an instance of the `weft.md` §2 custody axiom and passes the §5 failure test. diff --git a/docs/superpowers/specs/2026-06-02-clarion-ws5-mcp-catalogue-design.md b/docs/superpowers/specs/2026-06-02-loomweave-ws5-mcp-catalogue-design.md similarity index 91% rename from docs/superpowers/specs/2026-06-02-clarion-ws5-mcp-catalogue-design.md rename to docs/superpowers/specs/2026-06-02-loomweave-ws5-mcp-catalogue-design.md index c0ced384..bcc4110d 100644 --- a/docs/superpowers/specs/2026-06-02-clarion-ws5-mcp-catalogue-design.md +++ b/docs/superpowers/specs/2026-06-02-loomweave-ws5-mcp-catalogue-design.md @@ -2,19 +2,19 @@ **Date:** 2026-06-02 **Status:** Design (ready for an implementation plan) -**Workstream:** WS5 of the Clarion first-class program -(`2026-06-02-clarion-first-class-program-design.md` §2). Parallel band — autonomous, ungated. +**Workstream:** WS5 of the Loomweave first-class program +(`2026-06-02-loomweave-first-class-program-design.md` §2). Parallel band — autonomous, ungated. **Scope:** Complete the consult-mode MCP surface — the tools agents actually reach for — as a **stateless** catalogue, retiring the never-built cursor/session model. Faceted search and the exploration-elimination shortcuts; defers semantic search and dead-code analysis. **Inputs / authorities:** -- `docs/clarion/1.0/system-design.md` §8 (MCP Consult Surface) — the *intended* catalogue; its +- `docs/loomweave/1.0/system-design.md` §8 (MCP Consult Surface) — the *intended* catalogue; its cursor/session model is **ratified-away here** (see §1). -- `docs/clarion/1.0/requirements.md` REQ-MCP-*, NFR-PERF-02/03. -- `docs/clarion/adr/ADR-030-on-demand-summary-scope.md` — the on-demand posture this inherits. -- `docs/clarion/adr/ADR-038-sei-token-and-signature.md` — SEI carried in responses. -- Ground truth: `crates/clarion-mcp/` (the live 19-tool stateless surface). +- `docs/loomweave/1.0/requirements.md` REQ-MCP-*, NFR-PERF-02/03. +- `docs/loomweave/adr/ADR-030-on-demand-summary-scope.md` — the on-demand posture this inherits. +- `docs/loomweave/adr/ADR-038-sei-token-and-signature.md` — SEI carried in responses. +- Ground truth: `crates/loomweave-mcp/` (the live 19-tool stateless surface). --- @@ -22,7 +22,7 @@ exploration-elimination shortcuts; defers semantic search and dead-code analysis `system-design.md` §8 describes a **cursor-based session model** as central: `goto(id)` sets a server-held "here", and `summary`/`neighbors`/`callers` default to that cursor, with breadcrumbs -and a scope-lens. **That model was never built.** Ground truth (`crates/clarion-mcp/src/`): the +and a scope-lens. **That model was never built.** Ground truth (`crates/loomweave-mcp/src/`): the shipped surface is **19 stateless tools**, each taking explicit entity IDs; there is no cursor, no breadcrumbs, no scope-lens, no per-session server state. @@ -133,7 +133,7 @@ the **guidance authoring lifecycle**. The split, by tool: | WS5 (this spec) | WS6 (separate cycle) | |---|---| | `guidance_for` (read composed sheets) | `propose_guidance`, `promote_guidance` (authoring) | -| `findings_for`, `wardline_for` | the `clarion guidance` CLI | +| `findings_for`, `wardline_for` | the `loomweave guidance` CLI | | `emit_observation` (general agent capability) | guidance staleness review + `promote_observation` | Rationale: reading guidance is part of the inspection surface; *authoring* guidance is a lifecycle @@ -146,7 +146,7 @@ with its own anti-poisoning flow (propose→observation→promote, NFR-SEC-02) t Two tools are **not** in WS5 because they need infrastructure beyond a catalog query — but they are **scheduled, not deferred indefinitely.** They form **WS5b — Advanced MCP queries**, which has its own design + delivery plan -(`docs/superpowers/plans/2026-06-02-clarion-ws5b-advanced-queries-plan.md`) and sequences as the +(`docs/superpowers/plans/2026-06-02-loomweave-ws5b-advanced-queries-plan.md`) and sequences as the parallel-band wave immediately after WS5. - **`search_semantic`** — needs embedding infrastructure (an `EmbeddingProvider`, a vector store, @@ -180,6 +180,6 @@ backlog entry that quietly never happens. - Every entity-returning tool carries the `sei` field (null pre-Wave-1, populated post). - Faceted search + the cheap exploration shortcuts ship; `search_semantic` and `find_dead_code` are logged as deferred follow-ons. -- The `clarion-workflow` skill / MCP tool docs are updated to describe the new tools (stateless, +- The `loomweave-workflow` skill / MCP tool docs are updated to describe the new tools (stateless, SEI-carrying); the §8-cursor-model reconciliation note is filed as a doc task. - All Rust CI gates green; Python gates if the plugin's categorisation emission is touched. diff --git a/docs/superpowers/specs/2026-06-05-descriptor-backed-wardline-annotation-metadata-design.md b/docs/superpowers/specs/2026-06-05-descriptor-backed-wardline-annotation-metadata-design.md index e256966d..ceb3005f 100644 --- a/docs/superpowers/specs/2026-06-05-descriptor-backed-wardline-annotation-metadata-design.md +++ b/docs/superpowers/specs/2026-06-05-descriptor-backed-wardline-annotation-metadata-design.md @@ -2,11 +2,11 @@ ## Summary -Clarion's Python plugin consumes Wardline's NG-25 trust-vocabulary descriptor +Loomweave's Python plugin consumes Wardline's NG-25 trust-vocabulary descriptor without importing Wardline. When the descriptor is available, the plugin records -source-observed Wardline decorator facts on Clarion function/class entities as +source-observed Wardline decorator facts on Loomweave function/class entities as metadata and tags. Wardline remains authoritative for vocabulary and policy -semantics; Clarion stores only what it observes in source against that +semantics; Loomweave stores only what it observes in source against that descriptor. ## Design diff --git a/docs/superpowers/specs/2026-06-05-loomweave-pypi-distribution-design.md b/docs/superpowers/specs/2026-06-05-loomweave-pypi-distribution-design.md new file mode 100644 index 00000000..3207bae4 --- /dev/null +++ b/docs/superpowers/specs/2026-06-05-loomweave-pypi-distribution-design.md @@ -0,0 +1,239 @@ +# Loomweave PyPI Distribution — Design + +**Date:** 2026-06-05 +**Status:** Approved (brainstorm) — pending implementation plan +**Owner:** John Morrissey + +## 1. Goal + +Ship Loomweave through **PyPI as the canonical install channel**, so a developer +gets the whole tool — native Rust binary **and** the Python language plugin — +in **one command**, consistent with the rest of the Loom suite (Filigree, +Wardline) which are native-Python packages on PyPI. + +Success criterion: + +```bash +uvx loomweave install --path . # or: pipx install loomweave && loomweave install --path . +``` + +installs the `loomweave` binary, brings the Python plugin into the same +environment, and a subsequent `loomweave analyze` **discovers and spawns the +plugin** with no extra flags, no URL pinning, and no manual platform choice. + +The existing signed GitHub Release tarballs remain as the **secondary / offline +channel** (cargo-binstall, air-gapped installs, a future Homebrew tap). + +## 2. Current state (v1.0.0) + +- **Rust core** → `loomweave` binary (`crates/loomweave-cli`, a plain `bin` crate, + no PyO3). `release.yml` cross-builds for `x86_64-unknown-linux-gnu` and + `aarch64-apple-darwin` only, tarballs them, **cosign-signs + Rekor-verifies**, + emits SHA256 checksums, attaches to a GitHub Release on `v*` tag (gated behind + the `verify` job + tag-on-main ancestry check). +- **Python plugin** → `loomweave-plugin-python` (`plugins/python`, hatchling), + currently built as an **sdist only** and attached to the same Release. It is a + *separate spawned process* (ADR-021 plugin jail), ships `plugin.toml` as + hatchling **shared-data** into `share/loomweave/plugins/python/`, exposes a + `loomweave-plugin-python` console script, and depends on `pyright==1.1.409` + (which pulls `nodeenv` → bootstraps a Node runtime on first run). +- **Install today** is a 3-step manual dance: download the right tarball → + drop the binary in `~/.local/bin` → `pipx install .tar.gz`. +- Versions are kept in lockstep by `scripts/check-workspace-version-lockstep.py`. + +### Discovery mechanism (the load-bearing constraint) + +`crates/loomweave-core/src/plugin/discovery.rs` (the only production discovery +path, called from `crates/loomweave-cli/src/analyze.rs:432`) is **`$PATH`-only**: + +1. Scan each `$PATH` directory for executables named `loomweave-plugin-` + (refusing world-writable dirs per ADR-021). +2. For each, resolve the manifest via: **neighbor** `/plugin.toml` → + **install-prefix** `/../share/loomweave/plugins//plugin.toml` + (only when `` basename is `bin`) → **symlink-resolved install-prefix** + (canonicalise the executable, retry install-prefix from the resolved venv). + +There is **no** `current_exe()`-relative probe, no `LOOMWEAVE_PLUGIN_PATH`, no +explicit registry. Every test exercises discovery by setting +`.env("PATH", plugin_path)`. + +**Consequence:** `pipx install loomweave` / `uvx loomweave` expose **only** the +`loomweave` entry point on the user's `$PATH`. The dependency's +`loomweave-plugin-python` script lands in the venv `bin/` but is *not* on `$PATH` +and the venv is not activated → current discovery finds **nothing**. This is the +single fact that forces a small core change (Section 4.2). It is empirically +gateable without PyPI (Section 7). + +## 3. Decisions + +| # | Decision | Rationale | +|---|----------|-----------| +| D1 | **PyPI is the canonical channel** | Suite consistency — Filigree/Wardline are PyPI-native | +| D2 | **Approach A: two PyPI projects**, `loomweave` depends on `loomweave-plugin-python` | Keeps the ADR-021 core/plugin boundary; one-command UX via dependency resolution; both already version in lockstep | +| D3 | **Plugin is a default (non-optional) dependency** for now | Python is the flagship language. Revisit an optional `[python]` extra when a *second* language plugin exists (YAGNI) | +| D4 | **Rust binary shipped via maturin `bindings = "bin"` platform wheels** | The ruff/uv pattern; `loomweave-cli` is already a plain bin crate | +| D5 | **Wheel matrix = standard 4**: linux `x86_64` + `aarch64` (manylinux), macOS `arm64` + `x86_64` | Best reach/cost balance; covers dev laptops + CI runners. Windows deferred | +| D6 | **Add a `current_exe()`-relative sibling discovery level** | The only way co-located (same-venv) plugin discovery works under both pipx *and* uv with zero flags (Section 4.2) | +| D7 | **Amend ADR-021** to document the new discovery level | The change touches the plugin-jail trust model; the security record stays honest | +| D8 | **PyPI Trusted Publishing (OIDC)** from GitHub Actions | No long-lived token; pairs with PEP 740 attestations; reuse existing `verify` + tag-on-main gating | +| D9 | **Keep cosign-signed GitHub Release tarballs** in parallel | Offline / cargo-binstall / future Homebrew; source-of-truth binaries | +| D10 | **Node/pyright: document the first-run fetch for v1**; file a follow-up to make it hermetic | Don't block the PyPI launch on hermetic Node; flag the supply-chain wrinkle | + +## 4. Architecture + +### 4.1 Package topology + +Two PyPI distributions, versioned in lockstep with the Cargo workspace +(`1.0.0` today): + +1. **`loomweave`** — platform wheels built by maturin (`bindings = "bin"`), each + carrying the cross-compiled `loomweave` binary placed as a wheel script (installs + into `/bin/loomweave`). Declares a pinned runtime dependency + `loomweave-plugin-python == `. +2. **`loomweave-plugin-python`** — the existing pure-Python package, now built as a + **wheel** in addition to the sdist. Unchanged shape: ships `plugin.toml` shared-data + into `/share/loomweave/plugins/python/`, exposes the `loomweave-plugin-python` + console script (installs into `/bin/`), depends on `pyright`/`pyyaml`/`packaging`. + +**Resulting venv layout** (what `uvx loomweave` / `pipx install loomweave` produces): + +``` +/bin/loomweave # maturin bin wheel +/bin/loomweave-plugin-python # plugin console script +/share/loomweave/plugins/python/plugin.toml # plugin shared-data +``` + +`loomweave` and the plugin are co-located in the same venv `bin/` and `share/`. + +### 4.2 Discovery change (D6) — `current_exe()`-relative sibling level + +Add one discovery level to `discovery.rs`: in addition to scanning `$PATH`, +scan the directory containing the **running `loomweave` binary** +(`std::env::current_exe()?.parent()`) for `loomweave-plugin-*` executables, feeding +each through the *existing* `load_plugin` / `find_manifest` logic (which already +handles the `bin/ → ../share/loomweave/plugins//` install-prefix layout). + +- This makes the co-located plugin discoverable with **no `$PATH` manipulation**: + `current_exe()` is `/bin/loomweave`, its parent is `/bin`, and the + existing install-prefix probe resolves `/share/loomweave/plugins/python/plugin.toml`. +- It works identically for `pipx` (real venv) and `uv tool`/`uvx`: on Linux + `current_exe()` reads `/proc/self/exe` and resolves symlinks into the venv; + the existing symlink-resolution branch covers the macOS path where the exposed + entry is a symlink. +- **Security:** the new directory is subject to the **same world-writable + refusal** as `$PATH` entries. The trust argument: the directory holding the + running `loomweave` binary is implicitly as trusted as that binary — an attacker + who can write a sibling `loomweave-plugin-*` there can already replace `loomweave` + itself. Discovery results from this level are **merged** with `$PATH` results + using the existing first-match-wins de-duplication (`seen_names`), so a + legitimately PATH-installed plugin is never shadowed surprisingly. Behaviour + ordering (`$PATH` first vs. `current_exe()` first) is specified in the plan; + default proposal: `$PATH` entries first, then the `current_exe()` dir, so an + operator's explicit PATH plugin wins. + +This is the change ADR-021 is amended to record (D7). + +### 4.3 Data flow (install → first analyze) + +1. `uvx loomweave install --path .` → uv resolves the `loomweave` platform wheel + + `loomweave-plugin-python` wheel into one environment; `loomweave install` does its + existing setup (`.loomweave/`, skills, MCP config, hook). +2. `loomweave analyze` → `discover()` scans `$PATH` **and** `current_exe()` dir → + finds `/bin/loomweave-plugin-python` → resolves + `/share/loomweave/plugins/python/plugin.toml` → host spawns the plugin + under the ADR-021 jail → analysis proceeds. +3. First spawn triggers pyright's `nodeenv` Node fetch if not cached (Section 6). + +## 5. Build, publish & versioning (CI) + +- **New wheels job(s)** (in `release.yml` or a dedicated `wheels.yml`), gated + behind the existing `verify` job: + - `loomweave` platform wheels via maturin (`bindings = "bin"`) for the standard-4 + matrix. linux-`aarch64` + bundled C (SQLite) uses native arm64 runners *or* + maturin `--zig` for the C cross — chosen in the plan. `rusqlite` is `bundled` + and `reqwest` is rustls, so manylinux/macOS wheels need no system libs. + - `loomweave-plugin-python` wheel via `uv build` (pure Python, one wheel). + - **sdist for `loomweave`**: produced, but gated by a **clean-room build test** + (Section 7) that proves the workspace + `Cargo.lock` vendor correctly and + compile from the sdist with a Rust toolchain. If that proves impractical, + fall back to **no `loomweave` sdist** and route off-matrix platforms to the + GitHub Release / cargo-binstall with a clear error. (Decision recorded in plan.) +- **Publish** via PyPI **Trusted Publishing (OIDC)**, with PEP 740 attestations. +- **Release ordering:** publish `loomweave-plugin-python` **before** `loomweave`, so + the `loomweave` wheel's `== ` dependency resolves on the first release. +- **GitHub Release** (cosign tarballs) continues unchanged, in parallel. +- **Version lockstep:** extend `check-workspace-version-lockstep.py` to assert the + two PyPI package versions **and** the `loomweave → loomweave-plugin-python` pin all + equal the Cargo workspace version, so a release can't publish a core depending + on a stale/absent plugin. + +## 6. The Node / pyright wrinkle + +`pyright==1.1.409` pulls `nodeenv`, which **downloads a Node runtime on first +run**. Implications: + +- First `loomweave analyze` needs network to fetch Node (surprising; breaks + air-gapped/offline installs). +- A runtime network fetch sits awkwardly with Loomweave's untrusted-corpus posture. + +**v1 plan:** document the first-run fetch in the README install section, and +prime the pyright/Node cache during `loomweave install` (a cache-warm step) so the +fetch happens at a predictable, online moment rather than mid-analyze. + +**Follow-up (out of scope here):** make Node hermetic by shipping it via a wheel +(e.g. `nodejs-wheel`) so the plugin install pulls a pinned Node with no runtime +fetch. Filed as a tracked issue, not blocking the PyPI launch. + +## 7. Testing & validation gates + +- **Discovery spike → permanent test (no PyPI needed):** an integration test that + stages a `/bin/loomweave` (the built binary) + `/share/loomweave/plugins/python/plugin.toml` + + `/bin/loomweave-plugin-python`, runs `loomweave analyze` with the plugin dir + **NOT on `$PATH`**, and asserts the plugin is discovered and spawned via the new + `current_exe()` level. This is the empirical proof of D6 and a regression guard. +- **Clean-room sdist build test** (gates D5's sdist decision): in a minimal + container with only a Rust toolchain, `pip install loomweave-.tar.gz` and run + `loomweave --version`. +- **Per-platform install-and-spawn smoke test:** on each matrix OS, install the + built wheels from a local index / **TestPyPI**, run `loomweave --version`, + `loomweave install --path `, and a tiny `analyze` proving plugin discovery in + a *real* tool-venv layout (pipx and uv). +- **TestPyPI dry-run** of the full publish before the first real PyPI publish. + +## 8. Documentation changes + +- README install section collapses from the 3-step tarball dance to + `uvx loomweave install --path .` (and the `pipx` equivalent), with the signed-tarball + path retained as the **offline / verified-binary** alternative. +- Note the first-run Node fetch (Section 6). + +## 9. Risks & mitigations + +| Risk | Severity | Mitigation | +|------|----------|-----------| +| Co-located discovery fails under a real tool-venv (symlink / `current_exe()` quirk on macOS) | High | D6 + the discovery integration test + per-platform smoke test on TestPyPI *before* publish | +| `loomweave` sdist (workspace path-deps + bundled C) won't compile on a user box | Medium | Clean-room sdist build test; fall back to no-sdist + GitHub Release/cargo-binstall route | +| manylinux build of bundled SQLite/blake3 fails | Medium | Expected fine (bundled, rustls — no system libs); validate in CI matrix | +| linux-`aarch64` cross with bundled C | Medium | Native arm64 runner or maturin `--zig`; decided in plan | +| Node fetch breaks offline/air-gapped first run | Medium | Document + cache-warm in `loomweave install`; hermetic-Node follow-up | +| First-release dependency resolution (`loomweave` needs plugin already on PyPI) | Low | Publish plugin before core | + +## 10. Out of scope (YAGNI) + +- Windows wheels (revisit on demand; git/plugin-jail paths are Unix-tuned). +- Optional `[python]` extra / plugin-decoupling (revisit at second plugin). +- Homebrew tap, cargo-binstall metadata (the signed GitHub Release already + supports binstall; a tap is a later nicety). +- Hermetic Node bundling (tracked follow-up). + +## 11. ADR-021 amendment (D7) + +Amend ADR-021 (plugin jail / discovery trust model) to record: + +- A third discovery source: the directory of the running `loomweave` binary + (`current_exe()` parent), in addition to `$PATH`. +- That the **world-writable refusal** and first-match-wins de-duplication apply + to this source identically. +- The trust rationale: co-location with the trusted `loomweave` binary; an attacker + who can write there can already replace `loomweave`. +- The merge ordering with `$PATH` results. diff --git a/docs/superpowers/specs/archive/2026-05-30-clarion-consume-wardline-data-design.md b/docs/superpowers/specs/archive/2026-05-30-loomweave-consume-wardline-data-design.md similarity index 77% rename from docs/superpowers/specs/archive/2026-05-30-clarion-consume-wardline-data-design.md rename to docs/superpowers/specs/archive/2026-05-30-loomweave-consume-wardline-data-design.md index 5e04787c..cd29f5e1 100644 --- a/docs/superpowers/specs/archive/2026-05-30-clarion-consume-wardline-data-design.md +++ b/docs/superpowers/specs/archive/2026-05-30-loomweave-consume-wardline-data-design.md @@ -1,4 +1,4 @@ -# Clarion consumes Wardline data — design +# Loomweave consumes Wardline data — design **Date:** 2026-05-30 **Status:** approved (brainstorm directive: "step up, Wardline drops today"); @@ -9,22 +9,22 @@ annotation) is re-scoped in the tracker and cross-referenced, not redesigned. **Builds on:** ADR-018 (qualname divergence / asterisk 2), ADR-015 (Wardline→ Filigree emission), the federation contract `docs/federation/contracts.md` §"Wardline qualname normalization (entity reconciliation)", and Wardline SP4 -(Outputs + Loom Integration, 2026-05-30). +(Outputs + Weft Integration, 2026-05-30). --- ## 1. Problem -Wardline SP4 ships a native Filigree emitter today: `POST /api/loom/scan-results` +Wardline SP4 ships a native Filigree emitter today: `POST /api/weft/scan-results` with `scan_source="wardline"`, each finding carrying `metadata.wardline.qualname` -(the pre-composed dotted `module.qualified_name`, i.e. Clarion's L7 +(the pre-composed dotted `module.qualified_name`, i.e. Loomweave's L7 `canonical_qualified_name`). SP4 §10 makes entity-association emission a Wardline -**non-goal** — Wardline does **not** emit a Clarion `entity_id`. +**non-goal** — Wardline does **not** emit a Loomweave `entity_id`. -Clarion's only existing consume hook (`issues_for` / `orientation_pack`) is +Loomweave's only existing consume hook (`issues_for` / `orientation_pack`) is **entity_id-keyed**: `GET /api/entity-associations?entity_id=` → then -`GET /api/loom/issues/{id}`. Wardline findings are qualname-keyed with no -`entity_id`, so nothing currently surfaces them on a Clarion entity. The +`GET /api/weft/issues/{id}`. Wardline findings are qualname-keyed with no +`entity_id`, so nothing currently surfaces them on a Loomweave entity. The `entities/resolve?scheme=wardline_qualname` oracle named in ADR-018 is deferred and unimplemented. The result is a consume-side blind spot the moment Wardline data starts landing in Filigree. @@ -32,8 +32,8 @@ data starts landing in Filigree. There are two distinct flows under "consume Wardline data": - **Flow A — extraction-time annotation.** The Python plugin reads Wardline's - NG-25 descriptor at `clarion analyze` time and stamps each entity's reserved - `wardline` column (`crates/clarion-storage/migrations/0001_initial_schema.sql`, + NG-25 descriptor at `loomweave analyze` time and stamps each entity's reserved + `wardline` column (`crates/loomweave-storage/migrations/0001_initial_schema.sql`, `EntityRecord.wardline_json`). Today the probe (`plugins/python/.../wardline_probe.py`) proves only the import/version handshake; the column is all-`None`. Tracked as `clarion-1f6241b329` + @@ -45,12 +45,12 @@ There are two distinct flows under "consume Wardline data": ## 2. The simplifying insight -`metadata.wardline.qualname` *is* Clarion's L7 `canonical_qualified_name`, which +`metadata.wardline.qualname` *is* Loomweave's L7 `canonical_qualified_name`, which is literally **segment 3 of the entity_id** (`{plugin}:{kind}:{qualname}`, e.g. -`python:function:pkg.mod.func`). So reconciling a Wardline finding to a Clarion -entity is a **local lookup against Clarion's own catalog** — compose +`python:function:pkg.mod.func`). So reconciling a Wardline finding to a Loomweave +entity is a **local lookup against Loomweave's own catalog** — compose `python:function:` and look it up — not a network round-trip -to a resolve oracle. Clarion owns its catalog; it never needs to ask a sibling +to a resolve oracle. Loomweave owns its catalog; it never needs to ask a sibling "does this qualname resolve?". This removes the largest piece of perceived work and keeps the matching logic (and the documented divergence traps) on the side that owns the truth. @@ -63,7 +63,7 @@ Enrich-only, parallel to the existing entity-association lookup, invoked when 1. **Fetch** Wardline findings *scoped by file `F`* from Filigree (see §4). 2. **Resolve each finding to an entity, locally.** For each fetched finding, - normalize its `metadata.wardline.qualname` and look it up in Clarion's catalog + normalize its `metadata.wardline.qualname` and look it up in Loomweave's catalog (a pure string compare — no network). Tag the finding with how it resolved: - `exact` — qualname matches an indexed entity. Bind the finding to that entity. @@ -96,33 +96,33 @@ preserved verbatim. ## 4. Data source — composed from existing Filigree routes (no new contract) **Verified against Filigree source (2026-05-30): no new Filigree route is -needed.** Flow B composes two existing Filigree *loom* read routes. (Note: the -`POST /api/v1/files:resolve` route in `contracts.md` is a route **Clarion -exposes** — it returns *Clarion* entity_ids for paths — not a Filigree route -Clarion consumes; it is the wrong direction for this and is not used here.) - -1. **`GET /api/loom/files?scan_source=wardline&path_prefix=`** - — `api_loom_list_files` (`filigree/dashboard_routes/files.py`); filters - include `scan_source` and `path_prefix`. Returns `FileRecordLoom` items - carrying `file_id` + `path`. Clarion takes the item whose `path` **exactly** +needed.** Flow B composes two existing Filigree *weft* read routes. (Note: the +`POST /api/v1/files:resolve` route in `contracts.md` is a route **Loomweave +exposes** — it returns *Loomweave* entity_ids for paths — not a Filigree route +Loomweave consumes; it is the wrong direction for this and is not used here.) + +1. **`GET /api/weft/files?scan_source=wardline&path_prefix=`** + — `api_weft_list_files` (`filigree/dashboard_routes/files.py`); filters + include `scan_source` and `path_prefix`. Returns `FileRecordWeft` items + carrying `file_id` + `path`. Loomweave takes the item whose `path` **exactly** equals `E.source_file_path` (`path_prefix` is a prefix, so an exact-match filter is required) → Filigree `file_id`. Pinned by - `tests/fixtures/contracts/loom/files.json`. -2. **`GET /api/loom/findings?scan_source=wardline&file_id=`** — - `api_loom_list_findings`; filters include `scan_source`, `status`, `file_id`. - Each row is a `ScanFindingLoom` carrying `rule_id`, `message`, `severity`, + `tests/fixtures/contracts/weft/files.json`. +2. **`GET /api/weft/findings?scan_source=wardline&file_id=`** — + `api_weft_list_findings`; filters include `scan_source`, `status`, `file_id`. + Each row is a `ScanFindingWeft` carrying `rule_id`, `message`, `severity`, `status`, `line_start/line_end`, `fingerprint`, `file_id`, and **`metadata`** (where `metadata.wardline.qualname` lives). Pinned by - `tests/fixtures/contracts/loom/findings.json`. + `tests/fixtures/contracts/weft/findings.json`. -So the fetch is: `loom/files?path_prefix=` → exact-path match → `file_id`, -then `loom/findings?scan_source=wardline&file_id=`, then the local qualname -match in §3. `ScanFindingLoom` rows reference the file by `file_id` (not `path`), +So the fetch is: `weft/files?path_prefix=` → exact-path match → `file_id`, +then `weft/findings?scan_source=wardline&file_id=`, then the local qualname +match in §3. `ScanFindingWeft` rows reference the file by `file_id` (not `path`), which is why the file-list hop is required; file-scoping also keeps each query bounded rather than sweeping all project findings. -This is **Clarion-side build only** — the MCP Filigree client (`filigree.rs`) -gains a loom-files-list call and a loom-findings-list call; no Filigree-side work, +This is **Loomweave-side build only** — the MCP Filigree client (`filigree.rs`) +gains a weft-files-list call and a weft-findings-list call; no Filigree-side work, and **no federation contract request** (both routes already exist — checked against Filigree source, not assumed; cf. the withdrawn prune ask in `7a93883`). Enrich-only still holds: if either route is unreachable, the `wardline_findings` @@ -159,10 +159,10 @@ suite. ## 7. Flow A coordination (re-scope, not redesigned here) Per Wardline SP4 §2, Wardline now **emits** the NG-25 descriptor (SP2d shipped), -but Clarion's **reader** is unbuilt and the plugin still imports +but Loomweave's **reader** is unbuilt and the plugin still imports `wardline.core.registry` directly. The external blocker on `clarion-1f6241b329` (*"Wardline SP2 finalizing the NG-25 descriptor shape"*) has therefore **lifted** -— it is now actionable Clarion work (build the descriptor reader; populate the +— it is now actionable Loomweave work (build the descriptor reader; populate the `wardline` column at `analyze` time), no longer gated on Wardline. Action (tracker only, this session): update `clarion-1f6241b329` and @@ -174,27 +174,27 @@ when it is built. ## 8. Non-goals -- No `entities/resolve?scheme=wardline_qualname` oracle (deferred; Clarion's +- No `entities/resolve?scheme=wardline_qualname` oracle (deferred; Loomweave's consume path matches locally, so the oracle is not on Flow B's critical path). - No entity-association write-back to Filigree (ADR-029) — Approach 2 from the - brainstorm was rejected; Flow B is read-time-only, no Clarion-initiated mutation + brainstorm was rejected; Flow B is read-time-only, no Loomweave-initiated mutation of Filigree state. - No class/module-targeted Wardline findings (Wardline emits functions/methods). - No Flow A implementation in this spec (re-scope only). - No new runtime dependency — reuses the existing Filigree HTTP client in - `crates/clarion-mcp/src/filigree.rs`. + `crates/loomweave-mcp/src/filigree.rs`. ## 9. Risks - **R1 — sibling-route shape drift.** Both consumed routes (`files:resolve`, - `GET /api/loom/findings`) exist today (verified), but their wire shape could + `GET /api/weft/findings`) exist today (verified), but their wire shape could drift. Mitigated by pinning the consumed shapes in `docs/federation/contracts.md` against Filigree's normative fixtures - (`tests/fixtures/contracts/loom/findings.json`) and testing the client against + (`tests/fixtures/contracts/weft/findings.json`) and testing the client against that fixture, not a guess. No new route is requested, so there is no ahead-of-build dependency to land. - **R2 — qualname mismatch surfaced as silent miss.** A producer divergence - (Wardline composing a qualname Clarion can't match) degrades to + (Wardline composing a qualname Loomweave can't match) degrades to `resolution_confidence: none`. Mitigated by the shared normative fixture and by surfacing `none`/`heuristic` counts rather than dropping them. - **R3 — enrich path inflating query latency.** A per-entity Filigree round-trip @@ -208,9 +208,9 @@ when it is built. 2. New `release:1.1` issue — **Flow B: read-time Wardline finding reconciliation** — citing this spec, ADR-018, and contracts.md §reconciliation. **Not** gated on any Filigree-side work (both consumed routes already exist); - it is straightforward Clarion-side build, sequenced whenever picked up. -3. Pin the two consumed loom read routes (`GET /api/loom/files?scan_source=…& - path_prefix=…` for path→file_id, and `GET /api/loom/findings?scan_source=…& + it is straightforward Loomweave-side build, sequenced whenever picked up. +3. Pin the two consumed weft read routes (`GET /api/weft/files?scan_source=…& + path_prefix=…` for path→file_id, and `GET /api/weft/findings?scan_source=…& file_id=…`) in `docs/federation/contracts.md` as part of the Flow B build. **No** federation contract request is filed — the routes already exist (verified against Filigree source), so a request would be moot (cf. the @@ -219,7 +219,7 @@ when it is built. cross-reference). Any ADR minted from this work (e.g. an ADR-018 amendment pinning the read-time -consume mechanism) is kept under `docs/clarion/adr/` per the editorial +consume mechanism) is kept under `docs/loomweave/adr/` per the editorial conventions, authored when the decision locks at build time. ## 11. References @@ -230,5 +230,5 @@ conventions, authored when the decision locks at build time. reconciliation)" and §"Consumed Filigree route: issue detail (enrichment)". - `docs/federation/fixtures/wardline-qualname-normalization.json` — normative qualname parity vectors. -- Wardline SP4 — Outputs + Loom Integration (2026-05-30) §2, §6, §10. -- `crates/clarion-mcp/src/filigree.rs` — existing enrich-only Filigree client. +- Wardline SP4 — Outputs + Weft Integration (2026-05-30) §2, §6, §10. +- `crates/loomweave-mcp/src/filigree.rs` — existing enrich-only Filigree client. diff --git a/docs/superpowers/specs/archive/2026-05-31-clarion-core-errors-design.md b/docs/superpowers/specs/archive/2026-05-31-loomweave-core-errors-design.md similarity index 87% rename from docs/superpowers/specs/archive/2026-05-31-clarion-core-errors-design.md rename to docs/superpowers/specs/archive/2026-05-31-loomweave-core-errors-design.md index fc7c5c1c..8a406753 100644 --- a/docs/superpowers/specs/archive/2026-05-31-clarion-core-errors-design.md +++ b/docs/superpowers/specs/archive/2026-05-31-loomweave-core-errors-design.md @@ -1,4 +1,4 @@ -# Design — `clarion-core::errors` shared error vocabulary +# Design — `loomweave-core::errors` shared error vocabulary **Ticket:** clarion-b57c6bc49f (feature, P2, `release:v1.1`, `category:architecture`). **Closes:** V11-ARCH-01 (deep-dive-arch v1.1 priority #1) — the MCP/HTTP error-code drift smell. @@ -7,15 +7,15 @@ ## 1. Problem -Clarion emits structured error codes on two independent wire surfaces, each with +Loomweave emits structured error codes on two independent wire surfaces, each with its own hand-maintained, drift-prone taxonomy: -1. **HTTP federation read API** (`crates/clarion-cli/src/http_read.rs`) — a private +1. **HTTP federation read API** (`crates/loomweave-cli/src/http_read.rs`) — a private `enum ErrorCode` with `#[serde(rename_all = "SCREAMING_SNAKE_CASE")]`, 10 variants, frozen as a wire contract in `docs/federation/contracts.md` and ADR-034. Switched on by Filigree / Wardline clients. Already typed; already has a partial `StorageError → (code, status)` classifier (`classify_read_error`). -2. **MCP tool-error envelope** (`crates/clarion-mcp/src/lib.rs`) — **bare kebab-case +2. **MCP tool-error envelope** (`crates/loomweave-mcp/src/lib.rs`) — **bare kebab-case string literals** passed to `tool_error_envelope(code: &str, …)` at ~47 call sites. No enum, no exhaustiveness, no compiler protection against typos or silent divergence. 18 distinct codes. Carries a `retryable: bool` flag the HTTP @@ -55,7 +55,7 @@ unification: ## 2. Decision -**Co-locate, keep per-surface wire spellings.** A single `clarion-core::errors` +**Co-locate, keep per-surface wire spellings.** A single `loomweave-core::errors` module becomes the source of truth for *both* typed vocabularies. We do **not** merge them into one grab-bag enum and do **not** re-spell either surface. @@ -72,9 +72,9 @@ it delivers the highest-value part of the ticket: giving the MCP side a real typ ## 3. Design -### 3.1 New module `crates/clarion-core/src/errors.rs` +### 3.1 New module `crates/loomweave-core/src/errors.rs` -`clarion-core` already depends on `serde` and `thiserror`; no new dependencies. +`loomweave-core` already depends on `serde` and `thiserror`; no new dependencies. **`HttpErrorCode`** — moved verbatim from `http_read.rs`: @@ -123,18 +123,18 @@ inferred-dispatch-timeout ### 3.2 Crate wiring -- `crates/clarion-core/src/lib.rs`: add `pub mod errors;` and, per the +- `crates/loomweave-core/src/lib.rs`: add `pub mod errors;` and, per the clarion-29acbcd042 facade policy, re-export at the crate root: `pub use errors::{HttpErrorCode, McpErrorCode};`. -- `crates/clarion-cli/src/http_read.rs`: delete the local `enum ErrorCode` and its - derives; add `use clarion_core::HttpErrorCode as ErrorCode;`. All 44 existing +- `crates/loomweave-cli/src/http_read.rs`: delete the local `enum ErrorCode` and its + derives; add `use loomweave_core::HttpErrorCode as ErrorCode;`. All 44 existing `ErrorCode::*` references compile unchanged via the alias. `classify_read_error`, the `ReadError` struct, and every per-call-site `StatusCode` choice stay in - clarion-cli — they bind `StorageError` (clarion-storage) and `StatusCode` - (axum/http), neither of which clarion-core sees. No wire change. + loomweave-cli — they bind `StorageError` (loomweave-storage) and `StatusCode` + (axum/http), neither of which loomweave-core sees. No wire change. -- `crates/clarion-mcp/src/lib.rs`: +- `crates/loomweave-mcp/src/lib.rs`: - `fn tool_error_envelope(code: McpErrorCode, message: &str, retryable: bool)` and `tool_error_envelope_with_diagnostics(code: McpErrorCode, …)`; the body inserts `code.as_str()` into the JSON. @@ -146,7 +146,7 @@ inferred-dispatch-timeout string; route it through `McpErrorCode::TokenCeilingExceeded.as_str()` so the literal lives in exactly one place. - All ~47 call sites pass a variant instead of a string literal. - - `storage_retryable(&StorageError)` stays in clarion-mcp unchanged (it depends on + - `storage_retryable(&StorageError)` stays in loomweave-mcp unchanged (it depends on `StorageError`; `retryable` remains an explicit argument — behavior-preserving). ### 3.3 Out of scope (scope discipline) @@ -170,7 +170,7 @@ inferred-dispatch-timeout string AND that `as_str()` agrees; one test asserts each `McpErrorCode::as_str()` returns its exact kebab string. These pin the wire form at the definition site, so any accidental rename fails a unit test next to the enum. -- **Existing MCP pinned tests** (`crates/clarion-mcp/tests/storage_tools.rs`) and any +- **Existing MCP pinned tests** (`crates/loomweave-mcp/tests/storage_tools.rs`) and any HTTP serve tests asserting SCREAMING_SNAKE strings pass **unchanged** — the wire bytes are byte-identical. - **Full workspace gate** (CLAUDE.md floor) must pass: `cargo fmt --check`, @@ -182,16 +182,16 @@ inferred-dispatch-timeout ## 5. Documentation in lockstep - **ADR-037** (new, short) — records the decision: co-locate two typed error - vocabularies in `clarion-core::errors`; do not merge; keep per-surface wire + vocabularies in `loomweave-core::errors`; do not merge; keep per-surface wire spellings; status stays per-endpoint. Add to the ADR index / README. - **`docs/federation/contracts.md`** — wire is unchanged; add an additive pointer - noting the HTTP `code` enum is now defined canonically in `clarion_core::errors`. + noting the HTTP `code` enum is now defined canonically in `loomweave_core::errors`. - The arch-analysis snapshots (`docs/arch-analysis-*`) are historical and are **not** edited. ## 6. Acceptance -- `McpErrorCode` and `HttpErrorCode` both live in `clarion-core::errors`; both +- `McpErrorCode` and `HttpErrorCode` both live in `loomweave-core::errors`; both re-exported at the crate root. - Zero bare error-code string literals remain at MCP `tool_error_envelope` call sites (each passes a variant). diff --git a/clarion.yaml b/loomweave.yaml similarity index 89% rename from clarion.yaml rename to loomweave.yaml index 2fcca002..6efcfa9f 100644 --- a/clarion.yaml +++ b/loomweave.yaml @@ -1,6 +1,6 @@ integrations: filigree: - actor: clarion-mcp + actor: loomweave-mcp base_url: http://127.0.0.1:8542 enabled: true timeout_seconds: 5 @@ -29,8 +29,8 @@ llm_policy: openrouter: api_key_env: OPENROUTER_API_KEY attribution: - referer: https://github.com/tachyon-beep/clarion - title: Clarion + referer: https://github.com/foundryside-dev/loomweave + title: Loomweave endpoint_url: https://openrouter.ai/api/v1 provider: openrouter session_token_ceiling: 1000000 diff --git a/plugins/python/README.md b/plugins/python/README.md index 6b7cb41b..6b05a92c 100644 --- a/plugins/python/README.md +++ b/plugins/python/README.md @@ -1,7 +1,7 @@ -# clarion-plugin-python +# loomweave-plugin-python -The Python language plugin for [Clarion](../../README.md). Extracts Python -entities from source files and serves them to the Clarion core over the +The Python language plugin for [Loomweave](../../README.md). Extracts Python +entities from source files and serves them to the Loomweave core over the JSON-RPC protocol defined in [WP2 L4](../../docs/implementation/sprint-1/wp2-plugin-host.md#l4--json-rpc-method-set--content-length-framing). **Status**: Python structural extractor. It emits modules, classes, functions, @@ -19,7 +19,7 @@ source .venv/bin/activate pip install -e '.[dev]' ``` -This places `clarion-plugin-python` on your `$PATH` and installs the +This places `loomweave-plugin-python` on your `$PATH` and installs the dev-time toolchain (`ruff`, `mypy`, `pytest`, `pytest-cov`, `pre-commit`). ## ADR-023 tooling gates @@ -39,11 +39,11 @@ CI runs the same four gates in the `python-plugin` job. - [WP3 plan](../../docs/implementation/sprint-1/wp3-python-plugin.md) — task ledger, lock-ins, and UQ resolutions. -- [ADR-003](../../docs/clarion/adr/ADR-003-entity-id-scheme.md) — 3-segment +- [ADR-003](../../docs/loomweave/adr/ADR-003-entity-id-scheme.md) — 3-segment `EntityId` format this plugin produces. -- [ADR-018](../../docs/clarion/adr/ADR-018-identity-reconciliation.md) — +- [ADR-018](../../docs/loomweave/adr/ADR-018-identity-reconciliation.md) — cross-product identity join with Wardline. -- [ADR-022](../../docs/clarion/adr/ADR-022-core-plugin-ontology.md) — +- [ADR-022](../../docs/loomweave/adr/ADR-022-core-plugin-ontology.md) — manifest schema and ontology-boundary enforcement. -- [ADR-023](../../docs/clarion/adr/ADR-023-tooling-baseline.md) — the four +- [ADR-023](../../docs/loomweave/adr/ADR-023-tooling-baseline.md) — the four Python gates and the `pre-commit` setup. diff --git a/plugins/python/plugin.toml b/plugins/python/plugin.toml index 4501e020..51638a28 100644 --- a/plugins/python/plugin.toml +++ b/plugins/python/plugin.toml @@ -1,11 +1,11 @@ [plugin] -name = "clarion-plugin-python" +name = "loomweave-plugin-python" plugin_id = "python" -version = "1.3.0" +version = "1.0.0" protocol_version = "1.0" # Bare basename per ADR-021 §Layer 1 + WP2 scrub commit eb0a41d — the host # refuses manifests whose `executable` carries any path component. -executable = "clarion-plugin-python" +executable = "loomweave-plugin-python" language = "python" extensions = ["py"] @@ -15,14 +15,14 @@ extensions = ["py"] # inherits this limit from the plugin process, so B.4* declares the core # ceiling rather than the Sprint-1 CPython-only working set. expected_max_rss_mb = 2048 -# Triggers CLA-INFRA-PLUGIN-ENTITY-OVERRUN-WARNING well before the 500k +# Triggers LMWV-INFRA-PLUGIN-ENTITY-OVERRUN-WARNING well before the 500k # core cap (warning emission itself is deferred to Tier B — Sprint 1 # only lands the declaration). expected_entities_per_file = 5000 # Wardline semantic extraction reads the NG-25 vocabulary descriptor without # importing Wardline, then emits source-observed decorator metadata on entities. wardline_aware = true -# v0.1 rejects `true` at initialize with CLA-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY. +# v0.1 rejects `true` at initialize with LMWV-INFRA-MANIFEST-UNSUPPORTED-CAPABILITY. reads_outside_project_root = false [capabilities.runtime.pyright] @@ -35,9 +35,9 @@ pin = "1.1.409" # `imports` candidate edges. entity_kinds = ["function", "class", "module"] edge_kinds = ["contains", "calls", "references", "imports"] -# Per ADR-022: uppercase `CLA-{PLUGIN_ID_UPPER}-`. Reserved at parse -# against the CLA-INFRA-* and CLA-FACT-* namespaces. -rule_id_prefix = "CLA-PY-" +# Per ADR-022: uppercase `LMWV-{PLUGIN_ID_UPPER}-`. Reserved at parse +# against the LMWV-INFRA-* and LMWV-FACT-* namespaces. +rule_id_prefix = "LMWV-PY-" # Bumps per ADR-027 when the entity/edge/rule set shifts. Phase 3 Task 3 is a # MINOR bump (additive `imports` edge kind). NOTE: ADR-007's summary-cache key # is the 5-tuple (entity_id, content_hash, prompt_template_id, model_tier, diff --git a/plugins/python/pyproject.toml b/plugins/python/pyproject.toml index 576b9e12..18cee269 100644 --- a/plugins/python/pyproject.toml +++ b/plugins/python/pyproject.toml @@ -3,9 +3,9 @@ requires = ["hatchling"] build-backend = "hatchling.build" [project] -name = "clarion-plugin-python" -version = "1.3.0" -description = "Clarion Python language plugin — v1.0 release" +name = "loomweave-plugin-python" +version = "1.0.0" +description = "Loomweave Python language plugin — v1.0 release" readme = "README.md" requires-python = ">=3.11" authors = [{ name = "John Morrissey", email = "qacona@gmail.com" }] @@ -34,18 +34,18 @@ dev = [ ] [project.scripts] -clarion-plugin-python = "clarion_plugin_python.__main__:main" +loomweave-plugin-python = "loomweave_plugin_python.__main__:main" [tool.hatch.build.targets.wheel] -packages = ["src/clarion_plugin_python"] +packages = ["src/loomweave_plugin_python"] [tool.hatch.build.targets.wheel.shared-data] -# Route plugin.toml into /share/clarion/plugins// so +# Route plugin.toml into /share/loomweave/plugins// so # WP2's L9 install-prefix fallback finds it. The is produced by -# `discovery.rs::strip_prefix("clarion-plugin-")` on the binary name, so for -# clarion-plugin-python the suffix is "python" — the target directory must +# `discovery.rs::strip_prefix("loomweave-plugin-")` on the binary name, so for +# loomweave-plugin-python the suffix is "python" — the target directory must # match that basename exactly. -"plugin.toml" = "share/clarion/plugins/python/plugin.toml" +"plugin.toml" = "share/loomweave/plugins/python/plugin.toml" [tool.ruff] target-version = "py311" @@ -96,7 +96,7 @@ ignore_missing_imports = true [tool.pytest.ini_options] testpaths = ["tests"] -addopts = "--strict-markers --cov=clarion_plugin_python --cov-report=term-missing --cov-fail-under=85" +addopts = "--strict-markers --cov=loomweave_plugin_python --cov-report=term-missing --cov-fail-under=85" pythonpath = ["src"] markers = [ "pyright: requires pyright-langserver on PATH or in the active virtualenv", diff --git a/plugins/python/src/clarion_plugin_python/__init__.py b/plugins/python/src/clarion_plugin_python/__init__.py deleted file mode 100644 index 5346d05c..00000000 --- a/plugins/python/src/clarion_plugin_python/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -"""clarion-plugin-python — Python language plugin for Clarion.""" - -__version__ = "1.3.0" diff --git a/plugins/python/src/loomweave_plugin_python/__init__.py b/plugins/python/src/loomweave_plugin_python/__init__.py new file mode 100644 index 00000000..1e1d9cab --- /dev/null +++ b/plugins/python/src/loomweave_plugin_python/__init__.py @@ -0,0 +1,3 @@ +"""loomweave-plugin-python — Python language plugin for Loomweave.""" + +__version__ = "1.0.0" diff --git a/plugins/python/src/clarion_plugin_python/__main__.py b/plugins/python/src/loomweave_plugin_python/__main__.py similarity index 70% rename from plugins/python/src/clarion_plugin_python/__main__.py rename to plugins/python/src/loomweave_plugin_python/__main__.py index ab83af17..2dfe267e 100644 --- a/plugins/python/src/clarion_plugin_python/__main__.py +++ b/plugins/python/src/loomweave_plugin_python/__main__.py @@ -1,4 +1,4 @@ -"""Entry point for the ``clarion-plugin-python`` executable. +"""Entry point for the ``loomweave-plugin-python`` executable. Installs stdout discipline (``stdout_guard``) and hands control to the JSON-RPC server loop. ``sys.exit`` threads the server's exit code out to @@ -9,7 +9,7 @@ import sys -from clarion_plugin_python.server import main +from loomweave_plugin_python.server import main if __name__ == "__main__": sys.exit(main()) diff --git a/plugins/python/src/clarion_plugin_python/call_resolver.py b/plugins/python/src/loomweave_plugin_python/call_resolver.py similarity index 100% rename from plugins/python/src/clarion_plugin_python/call_resolver.py rename to plugins/python/src/loomweave_plugin_python/call_resolver.py diff --git a/plugins/python/src/clarion_plugin_python/entity_id.py b/plugins/python/src/loomweave_plugin_python/entity_id.py similarity index 94% rename from plugins/python/src/clarion_plugin_python/entity_id.py rename to plugins/python/src/loomweave_plugin_python/entity_id.py index 763788b6..22c9c9bf 100644 --- a/plugins/python/src/clarion_plugin_python/entity_id.py +++ b/plugins/python/src/loomweave_plugin_python/entity_id.py @@ -1,9 +1,9 @@ """L2 3-segment EntityId assembler matching WP1's Rust ``entity_id()`` byte-for-byte. -Per ADR-003 + ADR-022, every Clarion entity has a 3-segment ID of the +Per ADR-003 + ADR-022, every Loomweave entity has a 3-segment ID of the form ``{plugin_id}:{kind}:{canonical_qualified_name}``. -Validation (mirrors ``crates/clarion-core/src/entity_id.rs``): +Validation (mirrors ``crates/loomweave-core/src/entity_id.rs``): - ``plugin_id`` and ``kind`` must match the identifier grammar ``[a-z][a-z0-9_]*`` (ADR-022). diff --git a/plugins/python/src/clarion_plugin_python/extractor.py b/plugins/python/src/loomweave_plugin_python/extractor.py similarity index 98% rename from plugins/python/src/clarion_plugin_python/extractor.py rename to plugins/python/src/loomweave_plugin_python/extractor.py index 75cd650e..07788a86 100644 --- a/plugins/python/src/clarion_plugin_python/extractor.py +++ b/plugins/python/src/loomweave_plugin_python/extractor.py @@ -6,7 +6,7 @@ ``imports``, ``calls``, and ``references`` candidate edges. Entity shape matches the Rust host's ``RawEntity`` + ``RawSource`` -contract (``crates/clarion-core/src/plugin/host.rs:132-154``):: +contract (``crates/loomweave-core/src/plugin/host.rs:132-154``):: { "id": "python:function:...", @@ -39,7 +39,7 @@ - ``SyntaxError`` during ``ast.parse`` → one degraded module entity with ``parse_status="syntax_error"`` plus one stderr log line (UQ-WP3-02). The run continues; WP4-era findings can later attach a - ``CLA-PY-SYNTAX-ERROR`` annotation. + ``LMWV-PY-SYNTAX-ERROR`` annotation. - Top-level ``__init__.py`` (where the dotted module name resolves to ``""``) is skipped with stderr; the entity-ID assembler rejects an empty ``canonical_qualified_name``. @@ -62,7 +62,7 @@ from pathlib import PurePosixPath from typing import TYPE_CHECKING, Literal, NotRequired, TypedDict, cast -from clarion_plugin_python.call_resolver import ( +from loomweave_plugin_python.call_resolver import ( CallResolutionResult, CallResolver, CallsEdgeProperties, @@ -70,9 +70,9 @@ NoOpCallResolver, UnresolvedCallSite, ) -from clarion_plugin_python.entity_id import entity_id -from clarion_plugin_python.qualname import reconstruct_qualname -from clarion_plugin_python.reference_resolver import ( +from loomweave_plugin_python.entity_id import entity_id +from loomweave_plugin_python.qualname import reconstruct_qualname +from loomweave_plugin_python.reference_resolver import ( NoOpReferenceResolver, ReferenceResolutionResult, ReferenceResolver, @@ -81,7 +81,7 @@ ) if TYPE_CHECKING: - from clarion_plugin_python.wardline_descriptor import WardlineVocabulary + from loomweave_plugin_python.wardline_descriptor import WardlineVocabulary _PLUGIN_ID = "python" _NOOP_CALL_RESOLVER = NoOpCallResolver() @@ -197,7 +197,7 @@ class RawEntity(TypedDict): # Short natural-language text used by analyze-time semantic embeddings. docstring: NotRequired[str] # Wardline descriptor-backed source-observed decorator facts. Wardline owns - # the vocabulary; Clarion stores only the annotation facts seen on entities. + # the vocabulary; Loomweave stores only the annotation facts seen on entities. wardline: NotRequired[WardlineEntityMetadata] @@ -402,10 +402,10 @@ def extract_with_stats( # noqa: PLR0913 - resolver seams + optional Wardline vo is_package_module = PurePosixPath(prefix_source).name == "__init__.py" # Top-level __init__.py would resolve to "" — entity_id() rejects that - # (crates/clarion-core/src/entity_id.rs:97-101). Skip with stderr. + # (crates/loomweave-core/src/entity_id.rs:97-101). Skip with stderr. if not dotted_module: sys.stderr.write( - f"clarion-plugin-python: skipping {file_path}: " + f"loomweave-plugin-python: skipping {file_path}: " f"top-level __init__.py has no package name\n", ) return ExtractResult([], [], ExtractionStats()) @@ -416,7 +416,7 @@ def extract_with_stats( # noqa: PLR0913 - resolver seams + optional Wardline vo except SyntaxError as exc: parse_latency_ms = _elapsed_ms(parse_started_ns) sys.stderr.write( - f"clarion-plugin-python: skipping {file_path}: syntax error at " + f"loomweave-plugin-python: skipping {file_path}: syntax error at " f"line {exc.lineno}: {exc.msg}\n", ) return ExtractResult( @@ -961,7 +961,7 @@ def _walk( # noqa: PLR0913 - recursive walker needs both accumulators + parent if child_id in state.seen_ids: state.duplicate_entities_dropped += 1 sys.stderr.write( - f"clarion-plugin-python: dropping duplicate entity {child_id} " + f"loomweave-plugin-python: dropping duplicate entity {child_id} " f"in {state.file_path} at line {child.lineno} " f"(first definition wins)\n", ) @@ -982,7 +982,7 @@ def _walk( # noqa: PLR0913 - recursive walker needs both accumulators + parent if child_id in state.seen_ids: state.duplicate_entities_dropped += 1 sys.stderr.write( - f"clarion-plugin-python: dropping duplicate entity {child_id} " + f"loomweave-plugin-python: dropping duplicate entity {child_id} " f"in {state.file_path} at line {child.lineno} " f"(first definition wins)\n", ) diff --git a/plugins/python/src/clarion_plugin_python/py.typed b/plugins/python/src/loomweave_plugin_python/py.typed similarity index 100% rename from plugins/python/src/clarion_plugin_python/py.typed rename to plugins/python/src/loomweave_plugin_python/py.typed diff --git a/plugins/python/src/clarion_plugin_python/pyright_session.py b/plugins/python/src/loomweave_plugin_python/pyright_session.py similarity index 98% rename from plugins/python/src/clarion_plugin_python/pyright_session.py rename to plugins/python/src/loomweave_plugin_python/pyright_session.py index 866ac764..a90be613 100644 --- a/plugins/python/src/clarion_plugin_python/pyright_session.py +++ b/plugins/python/src/loomweave_plugin_python/pyright_session.py @@ -20,30 +20,30 @@ from typing import IO, TYPE_CHECKING, Any, Literal, Self from urllib.parse import unquote, urlparse -from clarion_plugin_python import __version__ -from clarion_plugin_python.call_resolver import ( +from loomweave_plugin_python import __version__ +from loomweave_plugin_python.call_resolver import ( CallResolutionResult, CallsRawEdge, Finding, UnresolvedCallSite, ) -from clarion_plugin_python.entity_id import entity_id -from clarion_plugin_python.extractor import module_dotted_name -from clarion_plugin_python.qualname import reconstruct_qualname -from clarion_plugin_python.reference_resolver import ( +from loomweave_plugin_python.entity_id import entity_id +from loomweave_plugin_python.extractor import module_dotted_name +from loomweave_plugin_python.qualname import reconstruct_qualname +from loomweave_plugin_python.reference_resolver import ( ReferenceResolutionResult, ReferenceSite, ReferencesRawEdge, ) -FINDING_PYRIGHT_RESTART = "CLA-PY-PYRIGHT-RESTART" -FINDING_PYRIGHT_POISON_FRAME = "CLA-PY-PYRIGHT-POISON-FRAME" -FINDING_PYRIGHT_INIT_TIMEOUT = "CLA-PY-PYRIGHT-INIT-TIMEOUT" -FINDING_PYRIGHT_UNAVAILABLE = "CLA-PY-PYRIGHT-UNAVAILABLE" -FINDING_PYRIGHT_INSTALL_FAILURE = "CLA-PY-PYRIGHT-INSTALL-FAILURE" -FINDING_PYRIGHT_CALL_RESOLUTION_TIMEOUT = "CLA-PY-CALL-RESOLUTION-TIMEOUT" -FINDING_PYRIGHT_REFERENCE_RESOLUTION_TIMEOUT = "CLA-PY-REFERENCE-RESOLUTION-TIMEOUT" -FINDING_PYRIGHT_REFERENCE_SITE_CAP = "CLA-PY-REFERENCE-SITE-CAP" +FINDING_PYRIGHT_RESTART = "LMWV-PY-PYRIGHT-RESTART" +FINDING_PYRIGHT_POISON_FRAME = "LMWV-PY-PYRIGHT-POISON-FRAME" +FINDING_PYRIGHT_INIT_TIMEOUT = "LMWV-PY-PYRIGHT-INIT-TIMEOUT" +FINDING_PYRIGHT_UNAVAILABLE = "LMWV-PY-PYRIGHT-UNAVAILABLE" +FINDING_PYRIGHT_INSTALL_FAILURE = "LMWV-PY-PYRIGHT-INSTALL-FAILURE" +FINDING_PYRIGHT_CALL_RESOLUTION_TIMEOUT = "LMWV-PY-CALL-RESOLUTION-TIMEOUT" +FINDING_PYRIGHT_REFERENCE_RESOLUTION_TIMEOUT = "LMWV-PY-REFERENCE-RESOLUTION-TIMEOUT" +FINDING_PYRIGHT_REFERENCE_SITE_CAP = "LMWV-PY-REFERENCE-SITE-CAP" @dataclass @@ -70,7 +70,7 @@ class PyrightRunState: PYRIGHT_FILE_TIMEOUT_SECS = 3.0 STDERR_TAIL_LIMIT = 65536 PYRIGHT_EXCLUDE_PATTERNS = [ - "**/.clarion/**", + "**/.loomweave/**", "**/.git/**", "**/.hg/**", "**/.svn/**", @@ -79,7 +79,7 @@ class PyrightRunState: "**/__pycache__/**", "**/node_modules/**", ] -PROJECT_LOCAL_EXTERNAL_DIRS = {".clarion", ".git", ".hg", ".svn", ".jj", ".venv", "node_modules"} +PROJECT_LOCAL_EXTERNAL_DIRS = {".loomweave", ".git", ".hg", ".svn", ".jj", ".venv", "node_modules"} if TYPE_CHECKING: @@ -757,7 +757,7 @@ def _initialize(self) -> None: {"uri": self.project_root.as_uri(), "name": self.project_root.name}, ], "capabilities": {"workspace": {"configuration": True}}, - "clientInfo": {"name": "clarion-plugin-python", "version": __version__}, + "clientInfo": {"name": "loomweave-plugin-python", "version": __version__}, }, self.init_timeout_secs, ) diff --git a/plugins/python/src/clarion_plugin_python/qualname.py b/plugins/python/src/loomweave_plugin_python/qualname.py similarity index 89% rename from plugins/python/src/clarion_plugin_python/qualname.py rename to plugins/python/src/loomweave_plugin_python/qualname.py index 96efd1c1..c44c6ba9 100644 --- a/plugins/python/src/clarion_plugin_python/qualname.py +++ b/plugins/python/src/loomweave_plugin_python/qualname.py @@ -1,7 +1,7 @@ """L7 qualname reconstruction matching Python's ``__qualname__`` semantics. Python's ``__qualname__`` is only bound at runtime, after the function or -class definition has been executed; Clarion's static analyser has to +class definition has been executed; Loomweave's static analyser has to reconstruct the same string from the AST and the chain of parent scopes. Rules (CPython language reference, "``__qualname__``"): @@ -13,9 +13,9 @@ class definition has been executed; Clarion's static analyser has to prepends ``parent..`` — the ```` marker distinguishes a closure from a method. -The L7 lock-in (``wp3-python-plugin.md §L7``) is that Clarion reconstructs +The L7 lock-in (``wp3-python-plugin.md §L7``) is that Loomweave reconstructs the same bare Python ``__qualname__`` semantics that Wardline stores in its -``FingerprintEntry.qualified_name`` field. Clarion entity names prepend the +``FingerprintEntry.qualified_name`` field. Loomweave entity names prepend the dotted module path elsewhere; ADR-018 requires cross-product joins to translate between those shapes instead of comparing strings directly. diff --git a/plugins/python/src/clarion_plugin_python/reference_resolver.py b/plugins/python/src/loomweave_plugin_python/reference_resolver.py similarity index 96% rename from plugins/python/src/clarion_plugin_python/reference_resolver.py rename to plugins/python/src/loomweave_plugin_python/reference_resolver.py index a4077996..e0d9e541 100644 --- a/plugins/python/src/clarion_plugin_python/reference_resolver.py +++ b/plugins/python/src/loomweave_plugin_python/reference_resolver.py @@ -7,7 +7,7 @@ from collections.abc import Sequence from pathlib import Path - from clarion_plugin_python.call_resolver import Finding + from loomweave_plugin_python.call_resolver import Finding ReferenceSiteKind = Literal["name", "annotation"] diff --git a/plugins/python/src/clarion_plugin_python/server.py b/plugins/python/src/loomweave_plugin_python/server.py similarity index 93% rename from plugins/python/src/clarion_plugin_python/server.py rename to plugins/python/src/loomweave_plugin_python/server.py index 1c335726..96528782 100644 --- a/plugins/python/src/clarion_plugin_python/server.py +++ b/plugins/python/src/loomweave_plugin_python/server.py @@ -2,7 +2,7 @@ Implements the five L4 methods — ``initialize``, ``initialized``, ``analyze_file``, ``shutdown``, ``exit`` — exactly matching the Rust host's -typed request/response contracts in ``crates/clarion-core/src/plugin/protocol.rs``. +typed request/response contracts in ``crates/loomweave-core/src/plugin/protocol.rs``. Response shapes (required by the Rust host's typed deserialise path): @@ -28,11 +28,11 @@ from pathlib import Path from typing import IO, Any -from clarion_plugin_python import __version__ -from clarion_plugin_python.extractor import extract_with_stats -from clarion_plugin_python.pyright_session import PyrightRunState, PyrightSession -from clarion_plugin_python.stdout_guard import install_stdio -from clarion_plugin_python.wardline_descriptor import WardlineVocabulary, load_wardline_descriptor +from loomweave_plugin_python import __version__ +from loomweave_plugin_python.extractor import extract_with_stats +from loomweave_plugin_python.pyright_session import PyrightRunState, PyrightSession +from loomweave_plugin_python.stdout_guard import install_stdio +from loomweave_plugin_python.wardline_descriptor import WardlineVocabulary, load_wardline_descriptor ONTOLOGY_VERSION = "0.7.0" @@ -149,11 +149,11 @@ def handle_initialize(params: dict[str, Any], state: ServerState) -> dict[str, A state.wardline_vocabulary = wardline.vocabulary if wardline.status == "absent": sys.stderr.write( - "clarion-plugin-python: Wardline vocabulary descriptor unavailable; " + "loomweave-plugin-python: Wardline vocabulary descriptor unavailable; " "continuing without Wardline annotation metadata\n", ) return { - "name": "clarion-plugin-python", + "name": "loomweave-plugin-python", "version": __version__, "ontology_version": ONTOLOGY_VERSION, "capabilities": {"wardline": wardline.as_capability()}, @@ -163,7 +163,7 @@ def handle_initialize(params: dict[str, Any], state: ServerState) -> dict[str, A def _resolve_module_path(file_path_raw: str, state: ServerState) -> str: """Compute the entity ``module_path`` relative to ``project_root``. - The host sends absolute paths (see ``crates/clarion-cli/src/analyze.rs`` + The host sends absolute paths (see ``crates/loomweave-cli/src/analyze.rs`` — ``project_root`` is canonicalised and file entries are built by ``entry.path()`` joins). To produce the expected L7 qualified names (``pkg.module.func`` rather than ``tmp.xyz.demo.func``), the plugin @@ -207,7 +207,7 @@ def handle_analyze_file(params: dict[str, Any], state: ServerState) -> dict[str, try: source = path.read_text(encoding="utf-8") except (OSError, UnicodeDecodeError) as exc: - sys.stderr.write(f"clarion-plugin-python: cannot read {file_path_raw}: {exc}\n") + sys.stderr.write(f"loomweave-plugin-python: cannot read {file_path_raw}: {exc}\n") return {"entities": [], "edges": [], "stats": empty_stats} # Emit source.file_path exactly as received so the host's jail check # (which canonicalises against project_root) sees the original path. diff --git a/plugins/python/src/clarion_plugin_python/stdout_guard.py b/plugins/python/src/loomweave_plugin_python/stdout_guard.py similarity index 96% rename from plugins/python/src/clarion_plugin_python/stdout_guard.py rename to plugins/python/src/loomweave_plugin_python/stdout_guard.py index 1514de6f..661a7541 100644 --- a/plugins/python/src/clarion_plugin_python/stdout_guard.py +++ b/plugins/python/src/loomweave_plugin_python/stdout_guard.py @@ -1,6 +1,6 @@ """Stdout discipline for JSON-RPC plugins (WP2 UQ-WP2-08 plugin-side resolution). -The Clarion plugin protocol reserves ``stdout`` for Content-Length-framed +The Loomweave plugin protocol reserves ``stdout`` for Content-Length-framed JSON-RPC frames. A stray ``print()`` or library-emitted message on stdout would corrupt the framing parser on the host side and trip either the Content-Length ceiling or the JSON decoder. diff --git a/plugins/python/src/clarion_plugin_python/wardline_descriptor.py b/plugins/python/src/loomweave_plugin_python/wardline_descriptor.py similarity index 97% rename from plugins/python/src/clarion_plugin_python/wardline_descriptor.py rename to plugins/python/src/loomweave_plugin_python/wardline_descriptor.py index c4d4a07d..e91d2b16 100644 --- a/plugins/python/src/clarion_plugin_python/wardline_descriptor.py +++ b/plugins/python/src/loomweave_plugin_python/wardline_descriptor.py @@ -1,11 +1,11 @@ """Wardline NG-25 vocabulary descriptor reader. This module deliberately reads descriptor files without importing Wardline. -Wardline remains authoritative for the vocabulary; Clarion records only the +Wardline remains authoritative for the vocabulary; Loomweave records only the source-observed decorator facts it can derive from that descriptor. Two contract details below (``PROJECT_DESCRIPTOR_PATH`` and the descriptor -``version`` semantics) are Clarion-side assumptions pending Wardline's +``version`` semantics) are Loomweave-side assumptions pending Wardline's "Pre-Rust core hardening" Task B, which has not yet published the canonical project-local descriptor location or the ``schema: wardline.vocabulary/v1`` format-version field. The parser ignores unknown top-level keys, so a future diff --git a/plugins/python/tests/test_entity_id.py b/plugins/python/tests/test_entity_id.py index a772fdb4..ab155af4 100644 --- a/plugins/python/tests/test_entity_id.py +++ b/plugins/python/tests/test_entity_id.py @@ -12,7 +12,7 @@ import pytest -from clarion_plugin_python.entity_id import ( +from loomweave_plugin_python.entity_id import ( EmptySegmentError, GrammarViolationError, SegmentContainsColonError, @@ -101,7 +101,7 @@ def test_matches_shared_fixture() -> None: """UQ-WP3-08: byte-for-byte L2 parity with the Rust assembler. The same ``fixtures/entity_id.json`` is consumed by - ``crates/clarion-core/src/entity_id.rs::tests::shared_fixture_byte_for_byte_parity``. + ``crates/loomweave-core/src/entity_id.rs::tests::shared_fixture_byte_for_byte_parity``. If this test or the Rust test disagrees on any row, the ID scheme has drifted between languages — the cross-product identity join (ADR-018) would break silently. CI fails both sides in lockstep. diff --git a/plugins/python/tests/test_extractor.py b/plugins/python/tests/test_extractor.py index c8e02b61..fe2559d9 100644 --- a/plugins/python/tests/test_extractor.py +++ b/plugins/python/tests/test_extractor.py @@ -11,8 +11,8 @@ import pytest -from clarion_plugin_python.call_resolver import CallResolutionResult -from clarion_plugin_python.extractor import ( +from loomweave_plugin_python.call_resolver import CallResolutionResult +from loomweave_plugin_python.extractor import ( ExtractResult, ImportsEdgeProperties, RawEdge, @@ -21,9 +21,9 @@ extract_with_stats, module_dotted_name, ) -from clarion_plugin_python.pyright_session import PyrightSession -from clarion_plugin_python.reference_resolver import ReferenceResolutionResult, ReferenceSite -from clarion_plugin_python.wardline_descriptor import DescriptorEntry, WardlineVocabulary +from loomweave_plugin_python.pyright_session import PyrightSession +from loomweave_plugin_python.reference_resolver import ReferenceResolutionResult, ReferenceSite +from loomweave_plugin_python.wardline_descriptor import DescriptorEntry, WardlineVocabulary if TYPE_CHECKING: from collections.abc import Sequence @@ -94,7 +94,7 @@ def pyright_langserver() -> str: if resolved is None: pytest.fail( "pyright-langserver not found on PATH or in the active virtualenv. " - "It is a hard runtime dependency of clarion-plugin-python " + "It is a hard runtime dependency of loomweave-plugin-python " "(pyproject.toml `dependencies`); a missing executable means the " "install is broken. Skipping these tests would mask a regression.", ) @@ -1021,13 +1021,13 @@ def _wardline_vocabulary( def test_wardline_vocabulary_attaches_decorator_metadata_and_tags() -> None: source = """\ -from loom_markers import external_boundary, trust_boundary, trusted +from weft_markers import external_boundary, trust_boundary, trusted @external_boundary def read_body(): return "" -@loom_markers.trust_boundary(to_level="ASSURED") +@weft_markers.trust_boundary(to_level="ASSURED") @trusted(level="INTEGRAL") class Sanitizer: pass @@ -1057,7 +1057,7 @@ class Sanitizer: assert sanitizer["wardline"]["decorators"] == [ { "canonical_name": "trust_boundary", - "qualified_name": "loom_markers.trust_boundary", + "qualified_name": "weft_markers.trust_boundary", "group": 1, "attrs": {"_wardline_to_level": "TaintState"}, "line": 7, @@ -1200,7 +1200,7 @@ def test_top_level_init_py_skipped_with_stderr( `module_dotted_name("__init__.py")` returns "" (the empty stem case). Emitting an entity with empty qualified_name would crash the entity-ID - assembler at crates/clarion-core/src/entity_id.rs:97-101. + assembler at crates/loomweave-core/src/entity_id.rs:97-101. """ entities, _ = extract("def helper():\n pass\n", "__init__.py") assert entities == [] diff --git a/plugins/python/tests/test_package.py b/plugins/python/tests/test_package.py index 5ac67ef1..0a8c7ca7 100644 --- a/plugins/python/tests/test_package.py +++ b/plugins/python/tests/test_package.py @@ -6,8 +6,8 @@ from pathlib import Path from typing import Any -import clarion_plugin_python -from clarion_plugin_python.wardline_descriptor import EXPECTED_DESCRIPTOR_VERSION +import loomweave_plugin_python +from loomweave_plugin_python.wardline_descriptor import EXPECTED_DESCRIPTOR_VERSION _PLUGIN_ROOT = Path(__file__).resolve().parents[1] @@ -17,7 +17,7 @@ def _read_toml(path: Path) -> dict[str, Any]: def test_package_version_matches_pyproject() -> None: - assert clarion_plugin_python.__version__ == "1.3.0" + assert loomweave_plugin_python.__version__ == "1.0.0" def test_plugin_version_lockstep_across_pyproject_manifest_and_module() -> None: @@ -31,7 +31,7 @@ def test_plugin_version_lockstep_across_pyproject_manifest_and_module() -> None: pyproject_version = pyproject["project"]["version"] manifest_version = manifest["plugin"]["version"] - module_version = clarion_plugin_python.__version__ + module_version = loomweave_plugin_python.__version__ assert pyproject_version == manifest_version == module_version, ( f"version drift: pyproject={pyproject_version!r}, " @@ -42,7 +42,7 @@ def test_plugin_version_lockstep_across_pyproject_manifest_and_module() -> None: def test_manifest_declares_current_v1_ontology_only() -> None: manifest = _read_toml(_PLUGIN_ROOT / "plugin.toml") - assert manifest["plugin"]["version"] == "1.3.0" + assert manifest["plugin"]["version"] == "1.0.0" assert manifest["capabilities"]["runtime"]["wardline_aware"] is True assert manifest["integrations"]["wardline"]["expected_descriptor_version"] == ( EXPECTED_DESCRIPTOR_VERSION diff --git a/plugins/python/tests/test_pyright_session.py b/plugins/python/tests/test_pyright_session.py index 7b68daef..04e27656 100644 --- a/plugins/python/tests/test_pyright_session.py +++ b/plugins/python/tests/test_pyright_session.py @@ -10,7 +10,7 @@ import pytest -from clarion_plugin_python.pyright_session import ( +from loomweave_plugin_python.pyright_session import ( FINDING_PYRIGHT_CALL_RESOLUTION_TIMEOUT, FINDING_PYRIGHT_INIT_TIMEOUT, FINDING_PYRIGHT_INSTALL_FAILURE, @@ -29,12 +29,12 @@ _unresolved_call_site_total_for_function, _unresolved_call_sites_for_function, ) -from clarion_plugin_python.reference_resolver import ReferenceSite, ReferenceSiteKind +from loomweave_plugin_python.reference_resolver import ReferenceSite, ReferenceSiteKind if TYPE_CHECKING: from collections.abc import Sequence - from clarion_plugin_python.call_resolver import Finding + from loomweave_plugin_python.call_resolver import Finding @pytest.fixture(scope="session") @@ -46,7 +46,7 @@ def pyright_langserver() -> str: if resolved is None: pytest.fail( "pyright-langserver not found on PATH or in the active virtualenv. " - "It is a hard runtime dependency of clarion-plugin-python " + "It is a hard runtime dependency of loomweave-plugin-python " "(pyproject.toml `dependencies`); a missing executable means the " "install is broken. Skipping these tests would mask a regression.", ) @@ -475,7 +475,7 @@ def test_pyright_session_reference_unavailable_binary_missing(tmp_path: Path) -> module = _write_module(tmp_path, source) site = _reference_site(source, from_id="python:module:demo", needle="world", occurrence=1) - with PyrightSession(tmp_path, executable="clarion-missing-pyright") as session: + with PyrightSession(tmp_path, executable="loomweave-missing-pyright") as session: result = session.resolve_references(module, [site]) assert result.edges == [] @@ -853,7 +853,7 @@ def test_pyright_session_init_timeout(tmp_path: Path) -> None: def test_pyright_session_unavailable_binary_missing(tmp_path: Path) -> None: module = _write_module(tmp_path, "def caller():\n print('x')\n") - with PyrightSession(tmp_path, executable="clarion-missing-pyright") as session: + with PyrightSession(tmp_path, executable="loomweave-missing-pyright") as session: result = session.resolve_calls(module, ["python:function:demo.caller"]) assert result.edges == [] diff --git a/plugins/python/tests/test_qualname.py b/plugins/python/tests/test_qualname.py index 4f946db2..6aca04d3 100644 --- a/plugins/python/tests/test_qualname.py +++ b/plugins/python/tests/test_qualname.py @@ -13,7 +13,7 @@ import ast -from clarion_plugin_python.qualname import reconstruct_qualname +from loomweave_plugin_python.qualname import reconstruct_qualname def _parse(source: str) -> ast.Module: diff --git a/plugins/python/tests/test_round_trip.py b/plugins/python/tests/test_round_trip.py index fbeb0084..02eb3256 100644 --- a/plugins/python/tests/test_round_trip.py +++ b/plugins/python/tests/test_round_trip.py @@ -1,6 +1,6 @@ """Round-trip self-test: plugin analyses its own source (WP3 Task 8). -Drives the *installed* ``clarion-plugin-python`` entry-point binary +Drives the *installed* ``loomweave-plugin-python`` entry-point binary (not ``sys.executable -m``) so the pip-install entry point is exercised end-to-end. The plugin's own ``extractor.py`` is the analysis target; the test asserts the module's public API functions appear in the returned @@ -43,11 +43,11 @@ def _read_frame(stream: IO[bytes]) -> dict[str, Any]: def _locate_binary() -> Path: scripts = Path(sysconfig.get_path("scripts")) - binary = scripts / "clarion-plugin-python" + binary = scripts / "loomweave-plugin-python" if not binary.exists(): pytest.fail( - f"clarion-plugin-python not found at {binary}. It is the installed " - "console entry point of clarion-plugin-python; a missing binary means " + f"loomweave-plugin-python not found at {binary}. It is the installed " + "console entry point of loomweave-plugin-python; a missing binary means " "`pip install -e plugins/python[dev]` was not run or the install is " "broken. Skipping this round-trip test would mask that regression in CI.", ) @@ -59,10 +59,10 @@ def test_round_trip_self_analysis() -> None: # noqa: PLR0915 - by-kind invarian binary = _locate_binary() # plugins/python/src is the package root; using it as project_root lets - # the plugin relativise extractor.py to `clarion_plugin_python/extractor.py`, - # whose dotted module name is `clarion_plugin_python.extractor`. + # the plugin relativise extractor.py to `loomweave_plugin_python/extractor.py`, + # whose dotted module name is `loomweave_plugin_python.extractor`. plugin_src = Path(__file__).resolve().parents[1] / "src" - target = plugin_src / "clarion_plugin_python" / "extractor.py" + target = plugin_src / "loomweave_plugin_python" / "extractor.py" assert target.is_file(), f"target source not found at {target}" proc = subprocess.Popen( # noqa: S603 - invoking our own installed entry point @@ -92,7 +92,7 @@ def test_round_trip_self_analysis() -> None: # noqa: PLR0915 - by-kind invarian proc.stdin.flush() init_response = _read_frame(proc.stdout) assert init_response["id"] == 1 - assert init_response["result"]["name"] == "clarion-plugin-python" + assert init_response["result"]["name"] == "loomweave-plugin-python" proc.stdin.write( _encode_frame({"jsonrpc": "2.0", "method": "initialized", "params": {}}), @@ -124,22 +124,27 @@ def test_round_trip_self_analysis() -> None: # noqa: PLR0915 - by-kind invarian # Invariants — no exact totals (those become merge-conflict generators # the moment someone adds a private helper to extractor.py). assert len(module_entities) == 1, "exactly one module entity per analyzed file" - assert module_entities[0]["id"] == "python:module:clarion_plugin_python.extractor" + assert module_entities[0]["id"] == "python:module:loomweave_plugin_python.extractor" assert module_entities[0].get("parse_status") == "ok" # Public extractor API must be present. - assert "python:function:clarion_plugin_python.extractor.module_dotted_name" in function_ids - assert "python:function:clarion_plugin_python.extractor.extract" in function_ids + assert ( + "python:function:loomweave_plugin_python.extractor.module_dotted_name" in function_ids + ) + assert "python:function:loomweave_plugin_python.extractor.extract" in function_ids # Private walker is a FunctionDef too, so it emits. - assert "python:function:clarion_plugin_python.extractor._walk" in function_ids + assert "python:function:loomweave_plugin_python.extractor._walk" in function_ids # B.2 renamed `_build_entity` → `_build_function_entity` and added # `_build_class_entity` + `_build_module_entity` (and `_module_source_range`). assert ( - "python:function:clarion_plugin_python.extractor._build_function_entity" in function_ids + "python:function:loomweave_plugin_python.extractor._build_function_entity" + in function_ids + ) + assert ( + "python:function:loomweave_plugin_python.extractor._build_class_entity" in function_ids ) - assert "python:function:clarion_plugin_python.extractor._build_class_entity" in function_ids assert ( - "python:function:clarion_plugin_python.extractor._build_module_entity" in function_ids + "python:function:loomweave_plugin_python.extractor._build_module_entity" in function_ids ) # extractor.py defines its wire-shape TypedDicts at module level @@ -147,9 +152,9 @@ def test_round_trip_self_analysis() -> None: # noqa: PLR0915 - by-kind invarian # and so emit as `class` entities. Subset assertion only — # exhaustive enumeration would be brittle. class_ids = {e["id"] for e in class_entities} - assert "python:class:clarion_plugin_python.extractor.SourceRange" in class_ids - assert "python:class:clarion_plugin_python.extractor.EntitySource" in class_ids - assert "python:class:clarion_plugin_python.extractor.RawEntity" in class_ids + assert "python:class:loomweave_plugin_python.extractor.SourceRange" in class_ids + assert "python:class:loomweave_plugin_python.extractor.EntitySource" in class_ids + assert "python:class:loomweave_plugin_python.extractor.RawEntity" in class_ids # Every entity carries the absolute source.file_path we sent # (project_root relativisation only affects the qualified_name prefix). diff --git a/plugins/python/tests/test_server.py b/plugins/python/tests/test_server.py index dce33f3c..2d38fcfe 100644 --- a/plugins/python/tests/test_server.py +++ b/plugins/python/tests/test_server.py @@ -1,10 +1,10 @@ """Integration tests for the JSON-RPC server loop (WP3 Task 2). -Spawns the installed `clarion-plugin-python` binary as a subprocess, speaks +Spawns the installed `loomweave-plugin-python` binary as a subprocess, speaks Content-Length-framed JSON-RPC to it over stdin/stdout, and asserts the handshake response matches the Rust host's `InitializeResult` contract (`{name, version, ontology_version, capabilities}` per -`crates/clarion-core/src/plugin/protocol.rs` line 293). +`crates/loomweave-core/src/plugin/protocol.rs` line 293). """ from __future__ import annotations @@ -15,14 +15,14 @@ import textwrap from typing import IO, TYPE_CHECKING, Any, cast -from clarion_plugin_python import server as server_module -from clarion_plugin_python.call_resolver import CallResolutionResult -from clarion_plugin_python.pyright_session import ( +from loomweave_plugin_python import server as server_module +from loomweave_plugin_python.call_resolver import CallResolutionResult +from loomweave_plugin_python.pyright_session import ( FINDING_PYRIGHT_RESTART, PyrightRunState, PyrightSession, ) -from clarion_plugin_python.reference_resolver import ReferenceResolutionResult, ReferenceSite +from loomweave_plugin_python.reference_resolver import ReferenceResolutionResult, ReferenceSite if TYPE_CHECKING: from collections.abc import Sequence @@ -34,7 +34,7 @@ # the test works regardless of whether the venv's bin dir is on $PATH when # pytest runs. Task 8's round-trip test exercises the entry-point binary; this # test only needs ``main()`` reached via the package module. -_SERVER_CMD = [sys.executable, "-m", "clarion_plugin_python"] +_SERVER_CMD = [sys.executable, "-m", "loomweave_plugin_python"] def _encode_frame(payload: dict[str, Any]) -> bytes: @@ -85,8 +85,8 @@ def test_initialize_roundtrip() -> None: assert response["jsonrpc"] == "2.0" assert response["id"] == 1 result = response["result"] - assert result["name"] == "clarion-plugin-python" - assert result["version"] == "1.3.0" + assert result["name"] == "loomweave-plugin-python" + assert result["version"] == "1.0.0" assert result["ontology_version"] == "0.7.0" assert set(result["capabilities"]) == {"wardline"} assert result["capabilities"]["wardline"]["status"] in { @@ -430,7 +430,7 @@ def resolve_calls( pyright_index_parse_latency_ms=[5], findings=[ { - "subcode": "CLA-PY-PYRIGHT-UNAVAILABLE", + "subcode": "LMWV-PY-PYRIGHT-UNAVAILABLE", "severity": "warning", "message": "pyright unavailable", "metadata": {"reason": "test"}, @@ -501,7 +501,7 @@ def close(self) -> None: } assert response["findings"] == [ { - "subcode": "CLA-PY-PYRIGHT-UNAVAILABLE", + "subcode": "LMWV-PY-PYRIGHT-UNAVAILABLE", "severity": "warning", "message": "pyright unavailable", "metadata": {"reason": "test"}, diff --git a/plugins/python/tests/test_stdout_guard.py b/plugins/python/tests/test_stdout_guard.py index 996877bf..8c710e9b 100644 --- a/plugins/python/tests/test_stdout_guard.py +++ b/plugins/python/tests/test_stdout_guard.py @@ -14,7 +14,7 @@ def test_install_stdio_blocks_print() -> None: """After install_stdio(), a Python `print()` raises StdoutGuardError.""" code = ( - "from clarion_plugin_python.stdout_guard import install_stdio, StdoutGuardError\n" + "from loomweave_plugin_python.stdout_guard import install_stdio, StdoutGuardError\n" "import sys\n" "install_stdio()\n" "try:\n" @@ -39,7 +39,7 @@ def test_install_stdio_blocks_print() -> None: def test_install_stdio_returns_real_streams() -> None: """install_stdio() yields usable stdin/stdout bytes streams.""" code = ( - "from clarion_plugin_python.stdout_guard import install_stdio\n" + "from loomweave_plugin_python.stdout_guard import install_stdio\n" "stdin, stdout = install_stdio()\n" "stdout.write(b'raw-bytes-out')\n" "stdout.flush()\n" diff --git a/plugins/python/tests/test_wardline_descriptor.py b/plugins/python/tests/test_wardline_descriptor.py index 1c0bda7a..3f42b219 100644 --- a/plugins/python/tests/test_wardline_descriptor.py +++ b/plugins/python/tests/test_wardline_descriptor.py @@ -3,7 +3,7 @@ import builtins from typing import TYPE_CHECKING, Any -from clarion_plugin_python.wardline_descriptor import ( +from loomweave_plugin_python.wardline_descriptor import ( EXPECTED_DESCRIPTOR_VERSION, load_wardline_descriptor, ) @@ -53,7 +53,7 @@ def test_project_descriptor_wins_over_package_descriptor( encoding="utf-8", ) monkeypatch.setattr( - "clarion_plugin_python.wardline_descriptor.metadata.files", + "loomweave_plugin_python.wardline_descriptor.metadata.files", lambda name: [_FakePackagePath(package_descriptor)] if name == "wardline" else None, ) @@ -86,7 +86,7 @@ def fail_import(name: str, *args: Any, **kwargs: Any) -> object: return real_import(name, *args, **kwargs) monkeypatch.setattr( - "clarion_plugin_python.wardline_descriptor.metadata.files", + "loomweave_plugin_python.wardline_descriptor.metadata.files", lambda name: [_FakePackagePath(package_descriptor)] if name == "wardline" else None, ) monkeypatch.setattr( @@ -104,7 +104,7 @@ def fail_import(name: str, *args: Any, **kwargs: Any) -> object: def test_absent_descriptor_degrades_without_vocabulary(tmp_path: Path, monkeypatch: Any) -> None: monkeypatch.setattr( - "clarion_plugin_python.wardline_descriptor.metadata.files", + "loomweave_plugin_python.wardline_descriptor.metadata.files", lambda _name: None, ) diff --git a/plugins/python/uv.lock b/plugins/python/uv.lock index 233f78dc..89aa6f96 100644 --- a/plugins/python/uv.lock +++ b/plugins/python/uv.lock @@ -194,44 +194,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, ] -[[package]] -name = "clarion-plugin-python" -version = "1.3.0" -source = { editable = "." } -dependencies = [ - { name = "packaging" }, - { name = "pyright" }, - { name = "pyyaml" }, -] - -[package.optional-dependencies] -dev = [ - { name = "build" }, - { name = "mypy" }, - { name = "pip-audit" }, - { name = "pre-commit" }, - { name = "pytest" }, - { name = "pytest-cov" }, - { name = "ruff" }, - { name = "types-pyyaml" }, -] - -[package.metadata] -requires-dist = [ - { name = "build", marker = "extra == 'dev'", specifier = ">=1.2" }, - { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.11" }, - { name = "packaging", specifier = ">=24" }, - { name = "pip-audit", marker = "extra == 'dev'", specifier = ">=2.9" }, - { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=3.8" }, - { name = "pyright", specifier = "==1.1.409" }, - { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0" }, - { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=5.0" }, - { name = "pyyaml", specifier = ">=6.0" }, - { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.6" }, - { name = "types-pyyaml", marker = "extra == 'dev'", specifier = ">=6.0" }, -] -provides-extras = ["dev"] - [[package]] name = "colorama" version = "0.4.6" @@ -500,6 +462,44 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/af/40/791891d4c0c4dab4c5e187c17261cedc26285fd41541577f900470a45a4d/license_expression-30.4.4-py3-none-any.whl", hash = "sha256:421788fdcadb41f049d2dc934ce666626265aeccefddd25e162a26f23bcbf8a4", size = 120615, upload-time = "2025-07-22T11:13:31.217Z" }, ] +[[package]] +name = "loomweave-plugin-python" +version = "1.0.0" +source = { editable = "." } +dependencies = [ + { name = "packaging" }, + { name = "pyright" }, + { name = "pyyaml" }, +] + +[package.optional-dependencies] +dev = [ + { name = "build" }, + { name = "mypy" }, + { name = "pip-audit" }, + { name = "pre-commit" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "ruff" }, + { name = "types-pyyaml" }, +] + +[package.metadata] +requires-dist = [ + { name = "build", marker = "extra == 'dev'", specifier = ">=1.2" }, + { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.11" }, + { name = "packaging", specifier = ">=24" }, + { name = "pip-audit", marker = "extra == 'dev'", specifier = ">=2.9" }, + { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=3.8" }, + { name = "pyright", specifier = "==1.1.409" }, + { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0" }, + { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=5.0" }, + { name = "pyyaml", specifier = ">=6.0" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.6" }, + { name = "types-pyyaml", marker = "extra == 'dev'", specifier = ">=6.0" }, +] +provides-extras = ["dev"] + [[package]] name = "markdown-it-py" version = "4.2.0" diff --git a/scripts/b4-gate-run.sh b/scripts/b4-gate-run.sh index 2e28c4c2..1fb7c6c8 100755 --- a/scripts/b4-gate-run.sh +++ b/scripts/b4-gate-run.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # B.4* week-2 gate runner. # -# Calibration baseline: Clarion reference Linux workstation, x86_64, +# Calibration baseline: Loomweave reference Linux workstation, x86_64, # 32 GiB RAM, Python 3.12; reference run date 2026-05-17. # Operators on materially slower/faster hardware may set # OPERATOR_HARDWARE_RATIO. The ratio scales Green/Yellow/Red thresholds and @@ -53,9 +53,9 @@ from datetime import UTC, datetime from pathlib import Path from typing import Any -from clarion_plugin_python.call_resolver import NoOpCallResolver -from clarion_plugin_python.extractor import extract_with_stats -from clarion_plugin_python.pyright_session import PyrightSession +from loomweave_plugin_python.call_resolver import NoOpCallResolver +from loomweave_plugin_python.extractor import extract_with_stats +from loomweave_plugin_python.pyright_session import PyrightSession @dataclass @@ -120,17 +120,17 @@ def read_pyright_pin(repo_root: Path) -> str: def run_cli(repo_root: Path, venv: Path, corpus_root: Path) -> tuple[int, dict[str, Any]]: - with tempfile.TemporaryDirectory(prefix=f"clarion-b4-{corpus_root.name}-") as tmp_raw: + with tempfile.TemporaryDirectory(prefix=f"loomweave-b4-{corpus_root.name}-") as tmp_raw: tmp = Path(tmp_raw) project = tmp / "project" shutil.copytree(corpus_root, project) env = os.environ.copy() env["PATH"] = f"{repo_root / 'target/release'}:{venv / 'bin'}:{env.get('PATH', '')}" started = time.perf_counter() - subprocess.run(["clarion", "install"], cwd=project, env=env, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) - subprocess.run(["clarion", "analyze", "."], cwd=project, env=env, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + subprocess.run(["loomweave", "install"], cwd=project, env=env, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + subprocess.run(["loomweave", "analyze", "."], cwd=project, env=env, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) wall_ms = int(round((time.perf_counter() - started) * 1000)) - db_path = project / ".clarion" / "clarion.db" + db_path = project / ".loomweave" / "loomweave.db" with sqlite3.connect(db_path) as conn: row = conn.execute("select stats from runs where status = 'completed' order by started_at desc limit 1").fetchone() if row is None: @@ -313,7 +313,7 @@ def main() -> int: f"calibration_machine: {machine_label()}", f"operator_hardware_ratio: {ratio}", f"pyright_pin: {pyright_pin}", - f"clarion_commit: {commit}", + f"loomweave_commit: {commit}", "", "### Corpus Results", *(format_corpus(metrics) for metrics in measured), diff --git a/scripts/check-entity-cap-lockstep.py b/scripts/check-entity-cap-lockstep.py index af49dbaa..12c63e92 100755 --- a/scripts/check-entity-cap-lockstep.py +++ b/scripts/check-entity-cap-lockstep.py @@ -5,9 +5,9 @@ ``finding`` notifications from a single plugin. That number is duplicated in two places that must stay in lockstep: -* docs/clarion/adr/ADR-021-plugin-authority-hybrid.md §2c — the normative +* docs/loomweave/adr/ADR-021-plugin-authority-hybrid.md §2c — the normative ``Default: **500,000**`` statement. -* crates/clarion-core/src/plugin/limits.rs — ``EntityCountCap::DEFAULT_MAX``, +* crates/loomweave-core/src/plugin/limits.rs — ``EntityCountCap::DEFAULT_MAX``, the const the host actually enforces. If the const drifts from the ADR, the enforced guardrail no longer matches the @@ -23,8 +23,8 @@ import tempfile from pathlib import Path -DEFAULT_LIMITS = Path("crates/clarion-core/src/plugin/limits.rs") -DEFAULT_ADR = Path("docs/clarion/adr/ADR-021-plugin-authority-hybrid.md") +DEFAULT_LIMITS = Path("crates/loomweave-core/src/plugin/limits.rs") +DEFAULT_ADR = Path("docs/loomweave/adr/ADR-021-plugin-authority-hybrid.md") # `pub const DEFAULT_MAX: usize = 500_000;` — underscores are stripped before int(). _RUST_DEFAULT_MAX_RE = re.compile( diff --git a/scripts/check-migration-retirement.py b/scripts/check-migration-retirement.py index 8278735f..7bb54b1b 100644 --- a/scripts/check-migration-retirement.py +++ b/scripts/check-migration-retirement.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 """Guard ADR-024's in-place migration retirement trigger. -Before the first external published build, Clarion may edit migration 0001 in +Before the first external published build, Loomweave may edit migration 0001 in place. After that trigger fires, add: - crates/clarion-storage/migrations/published_build.txt + crates/loomweave-storage/migrations/published_build.txt with the git ref of the first published build whose 0001 migration must stay stable. Once the marker exists, this guard fails if the working tree's 0001 @@ -19,8 +19,8 @@ import tempfile from pathlib import Path -MIGRATION_PATH = Path("crates/clarion-storage/migrations/0001_initial_schema.sql") -MARKER_PATH = Path("crates/clarion-storage/migrations/published_build.txt") +MIGRATION_PATH = Path("crates/loomweave-storage/migrations/0001_initial_schema.sql") +MARKER_PATH = Path("crates/loomweave-storage/migrations/published_build.txt") class CheckError(Exception): @@ -96,8 +96,8 @@ def run_self_test() -> None: with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) git_ok(root, "init", "-q") - git_ok(root, "config", "user.email", "clarion-test@example.invalid") - git_ok(root, "config", "user.name", "Clarion Test") + git_ok(root, "config", "user.email", "loomweave-test@example.invalid") + git_ok(root, "config", "user.name", "Loomweave Test") write(root / MIGRATION_PATH, "initial migration\n") git_ok(root, "add", MIGRATION_PATH.as_posix()) diff --git a/scripts/check-python-ontology-version.py b/scripts/check-python-ontology-version.py index fc8fdf1f..1a746804 100644 --- a/scripts/check-python-ontology-version.py +++ b/scripts/check-python-ontology-version.py @@ -4,7 +4,7 @@ The Python plugin declares the ontology version in two places: * plugins/python/plugin.toml, consumed by the Rust host during discovery. -* clarion_plugin_python.server.ONTOLOGY_VERSION, returned during initialize. +* loomweave_plugin_python.server.ONTOLOGY_VERSION, returned during initialize. Those values are intentionally duplicated so the plugin can answer the JSON-RPC handshake without parsing its installed manifest at runtime. This CI guard keeps @@ -21,7 +21,7 @@ from pathlib import Path DEFAULT_MANIFEST = Path("plugins/python/plugin.toml") -DEFAULT_SERVER = Path("plugins/python/src/clarion_plugin_python/server.py") +DEFAULT_SERVER = Path("plugins/python/src/loomweave_plugin_python/server.py") class CheckError(Exception): diff --git a/scripts/check-workspace-version-lockstep.py b/scripts/check-workspace-version-lockstep.py index 5997eafb..9eb494b9 100755 --- a/scripts/check-workspace-version-lockstep.py +++ b/scripts/check-workspace-version-lockstep.py @@ -1,26 +1,43 @@ #!/usr/bin/env python3 """Cross-workspace version lockstep guard. -Asserts that the Rust workspace package version -(`Cargo.toml [workspace.package].version`) matches the Python plugin's -`[project].version` (`plugins/python/pyproject.toml`). The Python plugin -manifest and `__init__.__version__` are already cross-checked by -`plugins/python/tests/test_package.py`; this script catches the wider -ecosystem-level drift (someone bumps Cargo but not the Python plugin, or -vice versa) before a release tag is cut. +Asserts that every package that ships as part of the Loomweave 1.0 product +carries the *same* version as the Rust workspace +(`Cargo.toml [workspace.package].version`): -Designed to run as a CI step on every PR — fast, no third-party deps, -parses with the stdlib `tomllib` (Python 3.11+). + * `plugins/python/pyproject.toml` `[project].version` + * `crates/loomweave-cli/pyproject.toml` `[project].version` (maturin bin-wheel) + +and that the maturin bin-wheel pins the plugin at exactly the workspace +version: + + * `crates/loomweave-cli/pyproject.toml` `[project].dependencies` + must contain `loomweave-plugin-python==` + +The `loomweave` wheel depends on `loomweave-plugin-python==`; if that pin +ever drifts from the version actually published, the `==` requirement fails +to resolve on first release. This guard catches that before a tag is cut. + +The Python plugin manifest and `__init__.__version__` are already +cross-checked by `plugins/python/tests/test_package.py`; this script catches +the wider ecosystem-level drift. + +Designed to run as a CI step on every PR — fast, no third-party deps, parses +with the stdlib `tomllib` (Python 3.11+). + +Usage: + check-workspace-version-lockstep.py # check the live repo + check-workspace-version-lockstep.py --self-test # run built-in fixtures Exit codes: - 0 versions agree - 1 versions disagree, or required keys are missing + 0 versions agree (or --self-test passed) + 1 versions disagree, a required pin is missing, or --self-test failed 2 usage / I/O error (e.g. file not found) -The acceptable-drift policy in v1.0 is "no drift": the Rust workspace and -Python plugin ship as a single product version. Post-1.0 patch releases -that need divergent semver should drop the strict-equality check and -replace it with a compatibility-bound check in the same script. +The acceptable-drift policy in v1.0 is "no drift": every component ships as a +single product version. Post-1.0 patch releases that need divergent semver +should replace the strict-equality checks with compatibility-bound checks in +the same script. """ from __future__ import annotations @@ -32,7 +49,14 @@ REPO_ROOT = Path(__file__).resolve().parents[1] CARGO_TOML = REPO_ROOT / "Cargo.toml" -PYPROJECT_TOML = REPO_ROOT / "plugins/python/pyproject.toml" +PLUGIN_PYPROJECT_TOML = REPO_ROOT / "plugins/python/pyproject.toml" +CLI_PYPROJECT_TOML = REPO_ROOT / "crates/loomweave-cli/pyproject.toml" + +PLUGIN_PACKAGE = "loomweave-plugin-python" + + +class _Missing(Exception): + """A required TOML key was absent.""" def _read_toml(path: Path) -> dict[str, Any]: @@ -42,44 +66,167 @@ def _read_toml(path: Path) -> dict[str, Any]: return tomllib.loads(path.read_text(encoding="utf-8")) -def _get_path(data: dict[str, Any], *keys: str) -> Any: +def _dig(data: dict[str, Any], *keys: str) -> Any: cursor: Any = data for key in keys: if not isinstance(cursor, dict) or key not in cursor: - path_repr = ".".join(keys) - print( - f"check-workspace-version-lockstep: key {path_repr!r} not found", - file=sys.stderr, - ) - sys.exit(1) + raise _Missing(".".join(keys)) cursor = cursor[key] return cursor -def main() -> int: - cargo = _read_toml(CARGO_TOML) - pyproject = _read_toml(PYPROJECT_TOML) +def _pinned_version(dependencies: Any, package: str) -> str | None: + """Return the `==`-pinned version for `package` in a PEP 508 dependency list. + + Returns `None` if the package is absent or not pinned with `==`. + """ + if not isinstance(dependencies, list): + return None + needle = f"{package}==" + for dep in dependencies: + if not isinstance(dep, str): + continue + norm = dep.replace(" ", "") + if norm.startswith(needle): + return norm[len(needle) :] + return None + + +def check_lockstep( + cargo: dict[str, Any], + plugin_pyproject: dict[str, Any], + cli_pyproject: dict[str, Any], +) -> list[str]: + """Return a list of drift errors. An empty list means everything is in lockstep.""" + errors: list[str] = [] + + try: + rust_version = _dig(cargo, "workspace", "package", "version") + except _Missing as missing: + # Without the anchor version there is nothing to compare against. + return [f"Cargo.toml key {missing} not found"] + + try: + plugin_version = _dig(plugin_pyproject, "project", "version") + if plugin_version != rust_version: + errors.append( + f"plugin version {plugin_version!r} != workspace {rust_version!r}" + ) + except _Missing as missing: + errors.append(f"plugins/python/pyproject.toml key {missing} not found") + + try: + cli_version = _dig(cli_pyproject, "project", "version") + if cli_version != rust_version: + errors.append( + f"loomweave-cli version {cli_version!r} != workspace {rust_version!r}" + ) + except _Missing as missing: + errors.append(f"crates/loomweave-cli/pyproject.toml key {missing} not found") + + try: + deps = _dig(cli_pyproject, "project", "dependencies") + pin = _pinned_version(deps, PLUGIN_PACKAGE) + if pin is None: + errors.append( + f"loomweave-cli pyproject does not pin {PLUGIN_PACKAGE}==" + ) + elif pin != rust_version: + errors.append( + f"loomweave-cli pins {PLUGIN_PACKAGE}=={pin} != workspace {rust_version!r}" + ) + except _Missing as missing: + errors.append(f"crates/loomweave-cli/pyproject.toml key {missing} not found") - rust_version = _get_path(cargo, "workspace", "package", "version") - python_version = _get_path(pyproject, "project", "version") + return errors - if rust_version != python_version: + +def _self_test() -> int: + """Exercise check_lockstep against in-memory fixtures.""" + cargo = tomllib.loads('[workspace.package]\nversion = "1.0.0"\n') + + def plugin(version: str) -> dict[str, Any]: + return tomllib.loads( + f'[project]\nname = "loomweave-plugin-python"\nversion = "{version}"\n' + ) + + def cli(version: str, deps: str) -> dict[str, Any]: + return tomllib.loads( + f'[project]\nname = "loomweave"\nversion = "{version}"\n{deps}\n' + ) + + good_deps = 'dependencies = ["loomweave-plugin-python==1.0.0"]' + cases: list[tuple[str, dict[str, Any], dict[str, Any], bool]] = [ + ("aligned", plugin("1.0.0"), cli("1.0.0", good_deps), True), + ("plugin version drift", plugin("1.0.1"), cli("1.0.0", good_deps), False), + ("cli version drift", plugin("1.0.0"), cli("0.9.0", good_deps), False), + ( + "cli pin drift", + plugin("1.0.0"), + cli("1.0.0", 'dependencies = ["loomweave-plugin-python==0.9.0"]'), + False, + ), + ( + "cli pin absent", + plugin("1.0.0"), + cli("1.0.0", 'dependencies = ["something-else>=1"]'), + False, + ), + ( + "cli pin unpinned (>=)", + plugin("1.0.0"), + cli("1.0.0", 'dependencies = ["loomweave-plugin-python>=1.0.0"]'), + False, + ), + ] + + failures = 0 + for name, plugin_py, cli_py, expect_ok in cases: + errors = check_lockstep(cargo, plugin_py, cli_py) + actual_ok = not errors + if actual_ok != expect_ok: + failures += 1 + print( + f" SELF-TEST FAIL [{name}]: expected ok={expect_ok}, got {errors!r}", + file=sys.stderr, + ) + else: + print(f" self-test ok [{name}]") + + if failures: print( - "check-workspace-version-lockstep: drift detected", + f"check-workspace-version-lockstep: --self-test FAILED ({failures})", file=sys.stderr, ) - print(f" Cargo.toml [workspace.package].version = {rust_version!r}", file=sys.stderr) - print(f" plugins/python/pyproject.toml [project].version = {python_version!r}", file=sys.stderr) + return 1 + print("check-workspace-version-lockstep: --self-test passed") + return 0 + + +def main(argv: list[str]) -> int: + if "--self-test" in argv: + return _self_test() + + cargo = _read_toml(CARGO_TOML) + plugin_pyproject = _read_toml(PLUGIN_PYPROJECT_TOML) + cli_pyproject = _read_toml(CLI_PYPROJECT_TOML) + + errors = check_lockstep(cargo, plugin_pyproject, cli_pyproject) + if errors: + print("check-workspace-version-lockstep: drift detected", file=sys.stderr) + for error in errors: + print(f" - {error}", file=sys.stderr) print( - "Bump them in lockstep, or split this guard into compatibility-bounded " - "ranges if v1.0+ wants divergent semver.", + "Bump every component in lockstep, or split this guard into " + "compatibility-bounded ranges if v1.0+ wants divergent semver.", file=sys.stderr, ) return 1 + rust_version = _dig(cargo, "workspace", "package", "version") print(f"check-workspace-version-lockstep: ok ({rust_version})") return 0 if __name__ == "__main__": - sys.exit(main()) + sys.exit(main(sys.argv[1:])) diff --git a/tests/e2e/external-operator-smoke.md b/tests/e2e/external-operator-smoke.md index 8bbb9d27..83f90777 100644 --- a/tests/e2e/external-operator-smoke.md +++ b/tests/e2e/external-operator-smoke.md @@ -12,7 +12,7 @@ This document is the procedure overview and the rationale for each step. Verify that an outside operator, working from a fresh machine and reading **only** the top-level `README.md` and `docs/operator/getting-started.md`, can -install Clarion, analyse a small public Python project, connect a consult-mode +install Loomweave, analyse a small public Python project, connect a consult-mode MCP client, ask substantive questions, re-run idempotently, and observe the pre-ingest secret-block fire on a planted credential. @@ -38,14 +38,14 @@ the operator to fill in. A draft results file is written to ### External-operator smoke (binary install) -The outside-operator scenario assumes Clarion has been installed via the +The outside-operator scenario assumes Loomweave has been installed via the GitHub Release archive and the Python plugin via `pipx`. Tell the harness to skip the build step and point it at the operator-installed binaries: ```bash export CARGO_BUILD=0 -export CLARION_BIN=/path/to/installed/clarion -export CLARION_PLUGIN_BIN=/path/to/installed/clarion-plugin-python +export LOOMWEAVE_BIN=/path/to/installed/loomweave +export LOOMWEAVE_PLUGIN_BIN=/path/to/installed/loomweave-plugin-python bash external-operator-smoke.sh # script is published as a release asset ``` @@ -56,9 +56,9 @@ it from any directory. | Variable | Default | Purpose | |---|---|---| -| `CARGO_BUILD` | `1` | Build clarion + plugin from source. `0` skips. | -| `CLARION_BIN` | (autodetected) | Path to the `clarion` binary. | -| `CLARION_PLUGIN_BIN` | (autodetected) | Path to `clarion-plugin-python`. | +| `CARGO_BUILD` | `1` | Build loomweave + plugin from source. `0` skips. | +| `LOOMWEAVE_BIN` | (autodetected) | Path to the `loomweave` binary. | +| `LOOMWEAVE_PLUGIN_BIN` | (autodetected) | Path to `loomweave-plugin-python`. | | `CORPUS_REPO` | `https://github.com/psf/requests.git` | Public Python corpus to analyse. | | `CORPUS_REF` | `v2.32.3` | Pinned tag for reproducibility. | | `OPENROUTER_API_KEY` | unset | If unset, step 4.3(c) `summary` is skipped (not failed). | @@ -81,15 +81,15 @@ fills it into the generated results file. | # | Step | Pass criteria | Automated? | |---|---|---|---| -| 1 | Install the Rust binary per WS-C's chosen path (GitHub Releases per ADR-033) | `clarion --version` prints a version on `$PATH` | ✅ | -| 2 | Install the Python plugin via `pipx` per the tutorial | `which clarion-plugin-python` resolves; the binary is on `$PATH` | ✅ | -| 3 | `clarion install` against a small Python project (the harness uses `psf/requests` at a pinned tag) | `.clarion/` exists; `.clarion/clarion.db` exists; init log line emitted | ✅ | -| 4.1 | `clarion analyze` | Exit 0; entity count > 0 | ✅ | -| 4.2 | Start `clarion serve`; connect MCP client | MCP `tools/list` returns the 9 tools (`entity_at`, `find_entity`, `callers_of`, `execution_paths_from`, `summary`, `issues_for`, `neighborhood`, `subsystem_members`, `project_status`) | ✅ | +| 1 | Install the Rust binary per WS-C's chosen path (GitHub Releases per ADR-033) | `loomweave --version` prints a version on `$PATH` | ✅ | +| 2 | Install the Python plugin via `pipx` per the tutorial | `which loomweave-plugin-python` resolves; the binary is on `$PATH` | ✅ | +| 3 | `loomweave install` against a small Python project (the harness uses `psf/requests` at a pinned tag) | `.loomweave/` exists; `.loomweave/loomweave.db` exists; init log line emitted | ✅ | +| 4.1 | `loomweave analyze` | Exit 0; entity count > 0 | ✅ | +| 4.2 | Start `loomweave serve`; connect MCP client | MCP `tools/list` returns the 9 tools (`entity_at`, `find_entity`, `callers_of`, `execution_paths_from`, `summary`, `issues_for`, `neighborhood`, `subsystem_members`, `project_status`) | ✅ | | 4.3 | Three MCP queries against analyzed corpus:
(a) `find_entity(pattern="Session")` lists module-level matches
(b) `callers_of(id=)` returns a non-empty list
(c) `summary(id=)` returns a paragraph | (a) and (b) return non-empty; (c) returns a paragraph (live LLM) or is skipped when `OPENROUTER_API_KEY` is absent | ✅ | -| 5 | Re-run `clarion analyze` | Idempotent: entity/edge counts on the second run match the first | ✅ | -| 6 | Plant `.env` with `AWS_ACCESS_KEY_ID=AKIA0123456789ABCDEF`; re-run `clarion analyze` | `briefing_blocked` entities count > 0; `CLA-SEC-SECRET-DETECTED` finding emitted | ✅ | -| 7 | `clarion serve` against the post-secret-block DB; call `summary(id)` on a blocked entity's id | Returns `briefing_blocked` envelope with no LLM call | ✅ | +| 5 | Re-run `loomweave analyze` | Idempotent: entity/edge counts on the second run match the first | ✅ | +| 6 | Plant `.env` with `AWS_ACCESS_KEY_ID=AKIA0123456789ABCDEF`; re-run `loomweave analyze` | `briefing_blocked` entities count > 0; `LMWV-SEC-SECRET-DETECTED` finding emitted | ✅ | +| 7 | `loomweave serve` against the post-secret-block DB; call `summary(id)` on a blocked entity's id | Returns `briefing_blocked` envelope with no LLM call | ✅ | | 8 | Operator-improvisation tally | Total count of "I had to look at the source to understand what to do next" events. **Target: 0.** Any positive count is a B.1/B.2 docs bug. | ❌ (human-judged) | ## Recording results @@ -109,7 +109,7 @@ issue) before closing. ## Repeating on additional platforms The release matrix in -[ADR-033](../../docs/clarion/adr/ADR-033-v1.0-distribution.md) produces +[ADR-033](../../docs/loomweave/adr/ADR-033-v1.0-distribution.md) produces `x86_64-unknown-linux-gnu`, `x86_64-apple-darwin`, and `aarch64-apple-darwin` binaries. Run the harness on at least one Linux target and one macOS target before the `v1.0.0` tag is treated as generally publishable. Each platform diff --git a/tests/e2e/external-operator-smoke.sh b/tests/e2e/external-operator-smoke.sh index 5bff3c56..ff5f76ea 100755 --- a/tests/e2e/external-operator-smoke.sh +++ b/tests/e2e/external-operator-smoke.sh @@ -11,12 +11,12 @@ # # Modes # ----- -# - `CARGO_BUILD=1` (default in repo CI): builds clarion + installs the +# - `CARGO_BUILD=1` (default in repo CI): builds loomweave + installs the # plugin from the source tree, fetches the canonical corpus, runs the # walkthrough, writes a results file. -# - `CARGO_BUILD=0` + `CLARION_BIN=...` + `CLARION_PLUGIN_BIN=...`: skips +# - `CARGO_BUILD=0` + `LOOMWEAVE_BIN=...` + `LOOMWEAVE_PLUGIN_BIN=...`: skips # the source build; expects the operator to have already installed -# clarion (via GitHub Release archive) and clarion-plugin-python (via +# loomweave (via GitHub Release archive) and loomweave-plugin-python (via # pipx) on $PATH. # # Outputs @@ -29,7 +29,7 @@ # ---------- # 0 — all technical steps PASS (or explicit SKIPs were declared upfront). # 1 — any technical step FAILED. -# 78 — soft-failure exit (matches `clarion analyze`'s soft-fail convention) +# 78 — soft-failure exit (matches `loomweave analyze`'s soft-fail convention) # reserved; not currently used but documented for future hardening. set -euo pipefail @@ -69,51 +69,51 @@ record() { cd "$REPO_ROOT" if [ "$CARGO_BUILD" = "1" ]; then - log "preflight: building clarion (release) ..." + log "preflight: building loomweave (release) ..." cargo build --workspace --release - CLARION_BIN="${CLARION_BIN:-$REPO_ROOT/target/release/clarion}" + LOOMWEAVE_BIN="${LOOMWEAVE_BIN:-$REPO_ROOT/target/release/loomweave}" if [ ! -d "$VENV" ]; then log "preflight: creating plugin venv at $VENV ..." python3 -m venv "$VENV" fi - log "preflight: installing clarion-plugin-python (editable) ..." + log "preflight: installing loomweave-plugin-python (editable) ..." "$VENV/bin/pip" install --quiet -e "$REPO_ROOT/plugins/python[dev]" - CLARION_PLUGIN_BIN="${CLARION_PLUGIN_BIN:-$VENV/bin/clarion-plugin-python}" + LOOMWEAVE_PLUGIN_BIN="${LOOMWEAVE_PLUGIN_BIN:-$VENV/bin/loomweave-plugin-python}" export PATH="$REPO_ROOT/target/release:$VENV/bin:$PATH" else - CLARION_BIN="${CLARION_BIN:-$(command -v clarion || true)}" - CLARION_PLUGIN_BIN="${CLARION_PLUGIN_BIN:-$(command -v clarion-plugin-python || true)}" + LOOMWEAVE_BIN="${LOOMWEAVE_BIN:-$(command -v loomweave || true)}" + LOOMWEAVE_PLUGIN_BIN="${LOOMWEAVE_PLUGIN_BIN:-$(command -v loomweave-plugin-python || true)}" fi -[ -x "$CLARION_BIN" ] || fail "clarion binary missing at $CLARION_BIN (set CLARION_BIN= or CARGO_BUILD=1)" -[ -x "$CLARION_PLUGIN_BIN" ] || fail "clarion-plugin-python missing at $CLARION_PLUGIN_BIN (set CLARION_PLUGIN_BIN= or CARGO_BUILD=1)" +[ -x "$LOOMWEAVE_BIN" ] || fail "loomweave binary missing at $LOOMWEAVE_BIN (set LOOMWEAVE_BIN= or CARGO_BUILD=1)" +[ -x "$LOOMWEAVE_PLUGIN_BIN" ] || fail "loomweave-plugin-python missing at $LOOMWEAVE_PLUGIN_BIN (set LOOMWEAVE_PLUGIN_BIN= or CARGO_BUILD=1)" -CLARION_VERSION="$("$CLARION_BIN" --version 2>&1 | head -1)" -PLUGIN_VERSION="$("$CLARION_PLUGIN_BIN" --version 2>&1 | head -1 || true)" -log "preflight: $CLARION_VERSION / $PLUGIN_VERSION" +LOOMWEAVE_VERSION="$("$LOOMWEAVE_BIN" --version 2>&1 | head -1)" +PLUGIN_VERSION="$("$LOOMWEAVE_PLUGIN_BIN" --version 2>&1 | head -1 || true)" +log "preflight: $LOOMWEAVE_VERSION / $PLUGIN_VERSION" # Scratch directory for the corpus. -WORK_DIR="$(mktemp -d -t clarion-smoke-XXXXXX)" +WORK_DIR="$(mktemp -d -t loomweave-smoke-XXXXXX)" trap 'rm -rf "$WORK_DIR"' EXIT log "scratch: $WORK_DIR" -# -------- step 1: clarion is on $PATH and --version works -------- +# -------- step 1: loomweave is on $PATH and --version works -------- -if [ -n "$CLARION_VERSION" ]; then - record "1" "PASS" "clarion --version: $CLARION_VERSION" +if [ -n "$LOOMWEAVE_VERSION" ]; then + record "1" "PASS" "loomweave --version: $LOOMWEAVE_VERSION" else - record "1" "FAIL" "clarion --version produced no output" + record "1" "FAIL" "loomweave --version produced no output" fi -# -------- step 2: clarion-plugin-python on $PATH -------- +# -------- step 2: loomweave-plugin-python on $PATH -------- -if [ -x "$CLARION_PLUGIN_BIN" ]; then - record "2" "PASS" "plugin binary at $CLARION_PLUGIN_BIN; $PLUGIN_VERSION" +if [ -x "$LOOMWEAVE_PLUGIN_BIN" ]; then + record "2" "PASS" "plugin binary at $LOOMWEAVE_PLUGIN_BIN; $PLUGIN_VERSION" else record "2" "FAIL" "plugin binary not found" fi -# -------- step 3: clarion install against a small Python corpus -------- +# -------- step 3: loomweave install against a small Python corpus -------- log "fetching corpus $CORPUS_REPO @ $CORPUS_REF ..." cd "$WORK_DIR" @@ -123,44 +123,44 @@ if ! git clone --quiet --depth 1 --branch "$CORPUS_REF" "$CORPUS_REPO" corpus; t fi cd corpus -if "$CLARION_BIN" install >/dev/null 2>"$WORK_DIR/install.err"; then - if [ -f .clarion/clarion.db ]; then - record "3" "PASS" ".clarion/clarion.db created against psf/requests@$CORPUS_REF" +if "$LOOMWEAVE_BIN" install >/dev/null 2>"$WORK_DIR/install.err"; then + if [ -f .loomweave/loomweave.db ]; then + record "3" "PASS" ".loomweave/loomweave.db created against psf/requests@$CORPUS_REF" else - record "3" "FAIL" "install reported success but .clarion/clarion.db missing" + record "3" "FAIL" "install reported success but .loomweave/loomweave.db missing" fi else - record "3" "FAIL" "clarion install exited non-zero: $(tr '\n' ' ' < "$WORK_DIR/install.err")" + record "3" "FAIL" "loomweave install exited non-zero: $(tr '\n' ' ' < "$WORK_DIR/install.err")" fi -# -------- step 4.1: clarion analyze (initial) -------- +# -------- step 4.1: loomweave analyze (initial) -------- -log "running clarion analyze ..." -if "$CLARION_BIN" analyze . >"$WORK_DIR/analyze1.out" 2>"$WORK_DIR/analyze1.err"; then - ENTITY_COUNT_1="$(sqlite3 .clarion/clarion.db 'SELECT COUNT(*) FROM entities WHERE kind != "subsystem"' || echo 0)" - EDGE_COUNT_1="$(sqlite3 .clarion/clarion.db 'SELECT COUNT(*) FROM edges' || echo 0)" +log "running loomweave analyze ..." +if "$LOOMWEAVE_BIN" analyze . >"$WORK_DIR/analyze1.out" 2>"$WORK_DIR/analyze1.err"; then + ENTITY_COUNT_1="$(sqlite3 .loomweave/loomweave.db 'SELECT COUNT(*) FROM entities WHERE kind != "subsystem"' || echo 0)" + EDGE_COUNT_1="$(sqlite3 .loomweave/loomweave.db 'SELECT COUNT(*) FROM edges' || echo 0)" if [ "$ENTITY_COUNT_1" -gt 0 ]; then record "4.1" "PASS" "analyze ok; entities=$ENTITY_COUNT_1 edges=$EDGE_COUNT_1" else record "4.1" "FAIL" "analyze ok but entity count is 0" fi else - record "4.1" "FAIL" "clarion analyze exited non-zero: $(tail -3 "$WORK_DIR/analyze1.err" | tr '\n' ' ')" + record "4.1" "FAIL" "loomweave analyze exited non-zero: $(tail -3 "$WORK_DIR/analyze1.err" | tr '\n' ' ')" fi -# -------- step 4.2 + 4.3: clarion serve + MCP queries -------- +# -------- step 4.2 + 4.3: loomweave serve + MCP queries -------- log "driving MCP stdio ..." MCP_REPORT="$WORK_DIR/mcp.json" PROJECT_DIR_FOR_PY="$WORK_DIR/corpus" -CLARION_BIN_FOR_PY="$CLARION_BIN" +LOOMWEAVE_BIN_FOR_PY="$LOOMWEAVE_BIN" set +e -python3 - "$CLARION_BIN_FOR_PY" "$PROJECT_DIR_FOR_PY" "$MCP_REPORT" "${OPENROUTER_API_KEY:-NONE}" <<'PY' +python3 - "$LOOMWEAVE_BIN_FOR_PY" "$PROJECT_DIR_FOR_PY" "$MCP_REPORT" "${OPENROUTER_API_KEY:-NONE}" <<'PY' import json, sys, subprocess, sqlite3 from pathlib import Path -clarion_bin, project_dir, report_path, openrouter_key = sys.argv[1:5] +loomweave_bin, project_dir, report_path, openrouter_key = sys.argv[1:5] project_dir = Path(project_dir) report = {"step_4_2": None, "step_4_3a": None, "step_4_3b": None, "step_4_3c": None, "tools_listed": None, "find_entity_hits": None, "callers_of_hits": None, @@ -191,7 +191,7 @@ def tool_call(proc, rid, name, args): return read_frame(proc) # Pick a real entity from the analyzed corpus to test against. -conn = sqlite3.connect(project_dir / ".clarion" / "clarion.db") +conn = sqlite3.connect(project_dir / ".loomweave" / "loomweave.db") ent = conn.execute(""" SELECT id, kind, name FROM entities WHERE kind = 'function' AND name = 'get' @@ -213,7 +213,7 @@ if not ent: Path(report_path).write_text(json.dumps(report)) sys.exit(2) -proc = subprocess.Popen([clarion_bin, "serve"], +proc = subprocess.Popen([loomweave_bin, "serve"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=str(project_dir)) @@ -241,7 +241,7 @@ try: report["tools_listed"] = tools def unwrap(envelope): - """Clarion MCP envelope wraps the tool payload under `result`.""" + """Loomweave MCP envelope wraps the tool payload under `result`.""" return envelope.get("result") if isinstance(envelope, dict) else None # 4.3(a) find_entity @@ -255,7 +255,7 @@ try: if matches else f"FAIL: find_entity('{pattern}') returned empty (envelope: {fe_body})") # 4.3(b) callers_of — find a function with at least one caller. - conn2 = sqlite3.connect(project_dir / ".clarion" / "clarion.db") + conn2 = sqlite3.connect(project_dir / ".loomweave" / "loomweave.db") row = conn2.execute(""" SELECT e.id, COUNT(*) AS c FROM entities e JOIN edges ed ON ed.to_id = e.id @@ -326,10 +326,10 @@ fi # -------- step 5: re-run analyze for idempotency -------- -log "running clarion analyze (re-run for idempotency) ..." -if "$CLARION_BIN" analyze . >"$WORK_DIR/analyze2.out" 2>"$WORK_DIR/analyze2.err"; then - ENTITY_COUNT_2="$(sqlite3 .clarion/clarion.db 'SELECT COUNT(*) FROM entities WHERE kind != "subsystem"')" - EDGE_COUNT_2="$(sqlite3 .clarion/clarion.db 'SELECT COUNT(*) FROM edges')" +log "running loomweave analyze (re-run for idempotency) ..." +if "$LOOMWEAVE_BIN" analyze . >"$WORK_DIR/analyze2.out" 2>"$WORK_DIR/analyze2.err"; then + ENTITY_COUNT_2="$(sqlite3 .loomweave/loomweave.db 'SELECT COUNT(*) FROM entities WHERE kind != "subsystem"')" + EDGE_COUNT_2="$(sqlite3 .loomweave/loomweave.db 'SELECT COUNT(*) FROM edges')" if [ "$ENTITY_COUNT_2" = "$ENTITY_COUNT_1" ] && [ "$EDGE_COUNT_2" = "$EDGE_COUNT_1" ]; then record "5" "PASS" "idempotent: entities=$ENTITY_COUNT_2 edges=$EDGE_COUNT_2 unchanged" else @@ -348,34 +348,34 @@ AWS_SECRET_ACCESS_KEY=examplefakefakefakefakefakefakefakefake1234 ENV ANALYZE_3_EXIT=0 -"$CLARION_BIN" analyze . >"$WORK_DIR/analyze3.out" 2>"$WORK_DIR/analyze3.err" || ANALYZE_3_EXIT=$? +"$LOOMWEAVE_BIN" analyze . >"$WORK_DIR/analyze3.out" 2>"$WORK_DIR/analyze3.err" || ANALYZE_3_EXIT=$? # Expected: soft-failure (exit 78) or success with briefing_blocked recorded. -BLOCKED_COUNT="$(sqlite3 .clarion/clarion.db "SELECT COUNT(*) FROM entities WHERE json_extract(properties, '\$.briefing_blocked') IS NOT NULL" 2>/dev/null || echo 0)" -FINDING_COUNT="$(sqlite3 .clarion/clarion.db "SELECT COUNT(*) FROM findings WHERE rule_id = 'CLA-SEC-SECRET-DETECTED'" 2>/dev/null || echo 0)" +BLOCKED_COUNT="$(sqlite3 .loomweave/loomweave.db "SELECT COUNT(*) FROM entities WHERE json_extract(properties, '\$.briefing_blocked') IS NOT NULL" 2>/dev/null || echo 0)" +FINDING_COUNT="$(sqlite3 .loomweave/loomweave.db "SELECT COUNT(*) FROM findings WHERE rule_id = 'LMWV-SEC-SECRET-DETECTED'" 2>/dev/null || echo 0)" if [ "$BLOCKED_COUNT" -gt 0 ] && [ "$FINDING_COUNT" -gt 0 ]; then record "6" "PASS" "post-plant: $BLOCKED_COUNT blocked entities, $FINDING_COUNT secret findings (analyze exit $ANALYZE_3_EXIT)" elif [ "$BLOCKED_COUNT" -gt 0 ]; then - record "6" "PASS" "post-plant: $BLOCKED_COUNT blocked entities (no CLA-SEC-SECRET-DETECTED finding rows; finding code may have changed)" + record "6" "PASS" "post-plant: $BLOCKED_COUNT blocked entities (no LMWV-SEC-SECRET-DETECTED finding rows; finding code may have changed)" elif [ "$FINDING_COUNT" -gt 0 ]; then record "6" "FAIL" "secret finding emitted but no entity marked briefing_blocked" else - record "6" "FAIL" "no briefing_blocked entities and no CLA-SEC-SECRET-DETECTED finding after planting" + record "6" "FAIL" "no briefing_blocked entities and no LMWV-SEC-SECRET-DETECTED finding after planting" fi # -------- step 7: serve against post-block DB; summary on blocked entity returns blocked envelope -------- if [ "$BLOCKED_COUNT" -gt 0 ]; then log "verifying blocked-entity summary refusal ..." - BLOCKED_ID="$(sqlite3 .clarion/clarion.db "SELECT id FROM entities WHERE json_extract(properties, '\$.briefing_blocked') IS NOT NULL LIMIT 1")" + BLOCKED_ID="$(sqlite3 .loomweave/loomweave.db "SELECT id FROM entities WHERE json_extract(properties, '\$.briefing_blocked') IS NOT NULL LIMIT 1")" if [ -n "$BLOCKED_ID" ]; then set +e - python3 - "$CLARION_BIN" "$WORK_DIR/corpus" "$BLOCKED_ID" "$WORK_DIR/step7.json" <<'PY' + python3 - "$LOOMWEAVE_BIN" "$WORK_DIR/corpus" "$BLOCKED_ID" "$WORK_DIR/step7.json" <<'PY' import json, sys, subprocess from pathlib import Path -clarion_bin, project_dir, blocked_id, out_path = sys.argv[1:5] +loomweave_bin, project_dir, blocked_id, out_path = sys.argv[1:5] def write_frame(proc, message): body = json.dumps(message, separators=(",", ":")).encode("utf-8") @@ -393,7 +393,7 @@ def read_frame(proc): headers[k.lower()] = v.strip() return json.loads(proc.stdout.read(int(headers["content-length"]))) -proc = subprocess.Popen([clarion_bin, "serve"], +proc = subprocess.Popen([loomweave_bin, "serve"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=project_dir) try: @@ -442,7 +442,7 @@ mkdir -p "$(dirname "$RESULTS_FILE")" echo echo "**Date (UTC)**: $(date -u +%Y-%m-%dT%H:%M:%SZ)" echo "**Host**: $(uname -srm)" - echo "**Clarion**: $CLARION_VERSION" + echo "**Loomweave**: $LOOMWEAVE_VERSION" echo "**Plugin**: $PLUGIN_VERSION" echo "**Corpus**: $CORPUS_REPO @ $CORPUS_REF" echo "**Mode**: $([ "$CARGO_BUILD" = "1" ] && echo "in-repo build (CARGO_BUILD=1)" || echo "external binary")" diff --git a/tests/e2e/phase3_subsystems.sh b/tests/e2e/phase3_subsystems.sh index 72bb1c36..bba8939d 100755 --- a/tests/e2e/phase3_subsystems.sh +++ b/tests/e2e/phase3_subsystems.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # Phase 3 subsystem clustering end-to-end test. # -# Builds a real Clarion database through `clarion analyze`, verifies persisted +# Builds a real Loomweave database through `loomweave analyze`, verifies persisted # subsystem entities / membership edges / clustering stats, checks deterministic # subsystem signatures across two clean project copies, and exercises the MCP # `subsystem_members` tool over stdio. @@ -19,22 +19,22 @@ fail() { printf '[phase3-subsystems] FAIL: %s\n' "$*" >&2; exit 1; } cd "$REPO_ROOT" if [ "$CARGO_BUILD" = "1" ]; then - log "building clarion (release) ..." + log "building loomweave (release) ..." cargo build --workspace --release fi -CLARION_BIN="$REPO_ROOT/target/release/clarion" -[ -x "$CLARION_BIN" ] || fail "clarion binary missing at $CLARION_BIN" +LOOMWEAVE_BIN="$REPO_ROOT/target/release/loomweave" +[ -x "$LOOMWEAVE_BIN" ] || fail "loomweave binary missing at $LOOMWEAVE_BIN" if [ ! -d "$VENV" ]; then log "creating venv at $VENV ..." python3 -m venv "$VENV" fi -log "installing clarion-plugin-python (editable) ..." +log "installing loomweave-plugin-python (editable) ..." "$VENV/bin/pip" install --quiet -e "$REPO_ROOT/plugins/python[dev]" -PLUGIN_BIN="$VENV/bin/clarion-plugin-python" -[ -x "$PLUGIN_BIN" ] || fail "clarion-plugin-python missing at $PLUGIN_BIN" +PLUGIN_BIN="$VENV/bin/loomweave-plugin-python" +[ -x "$PLUGIN_BIN" ] || fail "loomweave-plugin-python missing at $PLUGIN_BIN" -ROOT="$(mktemp -d -t clarion-phase3-XXXXXX)" +ROOT="$(mktemp -d -t loomweave-phase3-XXXXXX)" trap 'rm -rf "$ROOT"' EXIT PROJECT_A="$ROOT/project-a" PROJECT_B="$ROOT/project-b" @@ -87,7 +87,7 @@ def apply(amount: int) -> int: def preview(amount: int) -> int: return ledger.record(amount) PY - cat > "$dest/clarion.yaml" < "$dest/loomweave.yaml" < dict[str, object]: proc = subprocess.Popen( - [str(clarion_bin), "serve", "--path", str(project_dir)], + [str(loomweave_bin), "serve", "--path", str(project_dir)], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, diff --git a/tests/e2e/sprint_1_walking_skeleton.sh b/tests/e2e/sprint_1_walking_skeleton.sh index 9c495a62..a101844a 100755 --- a/tests/e2e/sprint_1_walking_skeleton.sh +++ b/tests/e2e/sprint_1_walking_skeleton.sh @@ -2,9 +2,9 @@ # Sprint 1 walking-skeleton end-to-end demo (WP3 Task 9 / signoffs A.4). # # Runs the README §3 demo script end-to-end and verifies: -# - `clarion install` creates `.clarion/clarion.db` -# - `clarion analyze .` spawns the Python plugin and persists at least one entity -# - `sqlite3 .clarion/clarion.db` returns Python module/function entities +# - `loomweave install` creates `.loomweave/loomweave.db` +# - `loomweave analyze .` spawns the Python plugin and persists at least one entity +# - `sqlite3 .loomweave/loomweave.db` returns Python module/function entities # - Python function rows include source path, source line range, and content hash # - resolved and ambiguous calls edges are persisted end-to-end # - resolved references edges are persisted end-to-end @@ -14,7 +14,7 @@ # Env overrides: # REPO_ROOT — auto-detected via `git rev-parse`; override to test an external checkout. # VENV — defaults to $REPO_ROOT/plugins/python/.venv; override to reuse an existing venv. -# CARGO_BUILD — set to "0" to skip `cargo build` (assumes target/release/clarion already present). +# CARGO_BUILD — set to "0" to skip `cargo build` (assumes target/release/loomweave already present). set -euo pipefail @@ -27,28 +27,28 @@ fail() { printf '[walking-skeleton] FAIL: %s\n' "$*" >&2; exit 1; } cd "$REPO_ROOT" -# ── 1. Build clarion binary ────────────────────────────────────────────────── +# ── 1. Build loomweave binary ────────────────────────────────────────────────── if [ "$CARGO_BUILD" = "1" ]; then - log "building clarion (release) ..." + log "building loomweave (release) ..." cargo build --workspace --release fi -CLARION_BIN="$REPO_ROOT/target/release/clarion" -[ -x "$CLARION_BIN" ] || fail "clarion binary missing at $CLARION_BIN" +LOOMWEAVE_BIN="$REPO_ROOT/target/release/loomweave" +[ -x "$LOOMWEAVE_BIN" ] || fail "loomweave binary missing at $LOOMWEAVE_BIN" # ── 2. Install Python plugin (editable) ────────────────────────────────────── if [ ! -d "$VENV" ]; then log "creating venv at $VENV ..." python3 -m venv "$VENV" fi -log "installing clarion-plugin-python (editable) ..." +log "installing loomweave-plugin-python (editable) ..." "$VENV/bin/pip" install --quiet -e "$REPO_ROOT/plugins/python[dev]" -PLUGIN_BIN="$VENV/bin/clarion-plugin-python" -[ -x "$PLUGIN_BIN" ] || fail "clarion-plugin-python missing at $PLUGIN_BIN" -PLUGIN_MANIFEST="$VENV/share/clarion/plugins/python/plugin.toml" +PLUGIN_BIN="$VENV/bin/loomweave-plugin-python" +[ -x "$PLUGIN_BIN" ] || fail "loomweave-plugin-python missing at $PLUGIN_BIN" +PLUGIN_MANIFEST="$VENV/share/loomweave/plugins/python/plugin.toml" [ -f "$PLUGIN_MANIFEST" ] || fail "plugin.toml missing at $PLUGIN_MANIFEST (WP2 L9 install-prefix fallback)" # ── 3. Scratch project ─────────────────────────────────────────────────────── -DEMO_DIR="$(mktemp -d -t clarion-demo-XXXXXX)" +DEMO_DIR="$(mktemp -d -t loomweave-demo-XXXXXX)" trap 'rm -rf "$DEMO_DIR"' EXIT log "scratch project: $DEMO_DIR" cd "$DEMO_DIR" @@ -76,21 +76,21 @@ def annotated(x: Marker) -> Marker: CONST_REF = world PY -# ── 4. PATH wiring — clarion + plugin binary ──────────────────────────────── +# ── 4. PATH wiring — loomweave + plugin binary ──────────────────────────────── export PATH="$REPO_ROOT/target/release:$VENV/bin:$PATH" -# ── 5. clarion install ─────────────────────────────────────────────────────── -log "running: clarion install" -clarion install -[ -f "$DEMO_DIR/.clarion/clarion.db" ] || fail ".clarion/clarion.db not created by clarion install" +# ── 5. loomweave install ─────────────────────────────────────────────────────── +log "running: loomweave install" +loomweave install +[ -f "$DEMO_DIR/.loomweave/loomweave.db" ] || fail ".loomweave/loomweave.db not created by loomweave install" -# ── 6. clarion analyze ─────────────────────────────────────────────────────── -log "running: clarion analyze ." -clarion analyze . +# ── 6. loomweave analyze ─────────────────────────────────────────────────────── +log "running: loomweave analyze ." +loomweave analyze . # ── 6b. Verify database integrity (STO-04) ─────────────────────────────────── log "verifying database integrity via PRAGMA integrity_check ..." -INTEGRITY=$(sqlite3 "$DEMO_DIR/.clarion/clarion.db" "PRAGMA integrity_check;") +INTEGRITY=$(sqlite3 "$DEMO_DIR/.loomweave/loomweave.db" "PRAGMA integrity_check;") if [ "$INTEGRITY" != "ok" ]; then log "integrity_check output:" printf '%s\n' "$INTEGRITY" >&2 @@ -99,7 +99,7 @@ fi # ── 7. Verify entity via sqlite3 ───────────────────────────────────────────── log "verifying persisted entity via sqlite3 ..." -RESULT=$(sqlite3 "$DEMO_DIR/.clarion/clarion.db" "select id, kind from entities order by id;") +RESULT=$(sqlite3 "$DEMO_DIR/.loomweave/loomweave.db" "select id, kind from entities order by id;") # B.2 (Sprint 2): every analyzed file emits a module entity in addition to # its function/class entities. v1.0 also mints a core file entity for file # identity and federation reads. B.4* adds direct and dict-dispatch call sites. @@ -115,47 +115,47 @@ python:module:demo|module" if [ "$RESULT" != "$EXPECTED" ]; then log "DB contents:" - sqlite3 "$DEMO_DIR/.clarion/clarion.db" "select * from entities;" >&2 || true + sqlite3 "$DEMO_DIR/.loomweave/loomweave.db" "select * from entities;" >&2 || true message=$(printf 'expected exactly:\n%s\ngot:\n%s' "$EXPECTED" "$RESULT") fail "$message" fi # ── 8. Verify source metadata for MCP entity_at/summary cache (B.6a) ───────── log "verifying persisted Python function source metadata ..." -SOURCE_METADATA=$(sqlite3 "$DEMO_DIR/.clarion/clarion.db" \ +SOURCE_METADATA=$(sqlite3 "$DEMO_DIR/.loomweave/loomweave.db" \ "select source_file_id, source_file_path, source_line_start, source_line_end, length(content_hash) from entities where id = 'python:function:demo.hello';") SOURCE_METADATA_EXPECTED="core:file:demo.py|$DEMO_DIR/demo.py|10|11|64" if [ "$SOURCE_METADATA" != "$SOURCE_METADATA_EXPECTED" ]; then log "DB entity source metadata:" - sqlite3 "$DEMO_DIR/.clarion/clarion.db" \ + sqlite3 "$DEMO_DIR/.loomweave/loomweave.db" \ "select id, source_file_id, source_file_path, source_line_start, source_line_end, content_hash from entities order by id;" >&2 || true fail "expected Python function source metadata:\n$SOURCE_METADATA_EXPECTED\ngot:\n$SOURCE_METADATA" fi log "verifying core file anchor metadata and module parent chain ..." -FILE_ANCHOR=$(sqlite3 "$DEMO_DIR/.clarion/clarion.db" \ +FILE_ANCHOR=$(sqlite3 "$DEMO_DIR/.loomweave/loomweave.db" \ "select id, plugin_id, kind, name, source_file_id, source_file_path, json_extract(properties, '\$.language'), length(content_hash) from entities where id = 'core:file:demo.py';") FILE_ANCHOR_EXPECTED="core:file:demo.py|core|file|demo.py||$DEMO_DIR/demo.py|python|64" if [ "$FILE_ANCHOR" != "$FILE_ANCHOR_EXPECTED" ]; then log "DB file anchor metadata:" - sqlite3 "$DEMO_DIR/.clarion/clarion.db" \ + sqlite3 "$DEMO_DIR/.loomweave/loomweave.db" \ "select id, plugin_id, kind, name, parent_id, source_file_id, source_file_path, properties, content_hash from entities order by id;" >&2 || true fail "expected core file anchor metadata:\n$FILE_ANCHOR_EXPECTED\ngot:\n$FILE_ANCHOR" fi -MODULE_PARENT=$(sqlite3 "$DEMO_DIR/.clarion/clarion.db" \ +MODULE_PARENT=$(sqlite3 "$DEMO_DIR/.loomweave/loomweave.db" \ "select parent_id, source_file_id from entities where id = 'python:module:demo';") MODULE_PARENT_EXPECTED="core:file:demo.py|core:file:demo.py" if [ "$MODULE_PARENT" != "$MODULE_PARENT_EXPECTED" ]; then log "DB module parent/source metadata:" - sqlite3 "$DEMO_DIR/.clarion/clarion.db" \ + sqlite3 "$DEMO_DIR/.loomweave/loomweave.db" \ "select id, parent_id, source_file_id from entities order by id;" >&2 || true fail "expected module parent/source metadata:\n$MODULE_PARENT_EXPECTED\ngot:\n$MODULE_PARENT" fi # ── 9. Verify contains edge via sqlite3 (B.3) ──────────────────────────────── log "verifying persisted contains edge via sqlite3 ..." -EDGE_RESULT=$(sqlite3 "$DEMO_DIR/.clarion/clarion.db" \ +EDGE_RESULT=$(sqlite3 "$DEMO_DIR/.loomweave/loomweave.db" \ "select kind, from_id, to_id from edges where kind = 'contains' order by from_id, to_id;") EDGE_EXPECTED="contains|core:file:demo.py|python:module:demo contains|python:module:demo|python:class:demo.Marker @@ -167,85 +167,85 @@ contains|python:module:demo|python:function:demo.z_fallback" if [ "$EDGE_RESULT" != "$EDGE_EXPECTED" ]; then log "DB edge contents:" - sqlite3 "$DEMO_DIR/.clarion/clarion.db" "select * from edges;" >&2 || true + sqlite3 "$DEMO_DIR/.loomweave/loomweave.db" "select * from edges;" >&2 || true fail "expected edge row:\n$EDGE_EXPECTED\ngot:\n$EDGE_RESULT" fi # ── 10. Verify run stats include B.3 + B.4* + B.5* edges ──────────────────── log "verifying run stats include edges_inserted >= 10 ..." -EDGES_INSERTED=$(sqlite3 "$DEMO_DIR/.clarion/clarion.db" \ +EDGES_INSERTED=$(sqlite3 "$DEMO_DIR/.loomweave/loomweave.db" \ "select json_extract(stats, '\$.edges_inserted') from runs where status = 'completed';") if [ "$EDGES_INSERTED" -lt 10 ]; then log "runs row:" - sqlite3 "$DEMO_DIR/.clarion/clarion.db" "select id, status, stats from runs;" >&2 || true + sqlite3 "$DEMO_DIR/.loomweave/loomweave.db" "select id, status, stats from runs;" >&2 || true fail "expected runs.stats.edges_inserted >= 10; got $EDGES_INSERTED" fi # ── 11. Verify dropped_edges_total == 0 (B.3 §6 / §9 exit criterion 6) ────── log "verifying run stats include dropped_edges_total == 0 ..." -DROPPED=$(sqlite3 "$DEMO_DIR/.clarion/clarion.db" \ +DROPPED=$(sqlite3 "$DEMO_DIR/.loomweave/loomweave.db" \ "select json_extract(stats, '\$.dropped_edges_total') from runs where status = 'completed';") if [ "$DROPPED" != "0" ]; then log "runs row:" - sqlite3 "$DEMO_DIR/.clarion/clarion.db" "select id, status, stats from runs;" >&2 || true + sqlite3 "$DEMO_DIR/.loomweave/loomweave.db" "select id, status, stats from runs;" >&2 || true fail "expected runs.stats.dropped_edges_total == 0; got $DROPPED" fi # ── 12. Verify resolved + ambiguous calls edges (B.4*) ────────────────────── log "verifying persisted resolved calls edge ..." -RESOLVED_CALLS=$(sqlite3 "$DEMO_DIR/.clarion/clarion.db" \ +RESOLVED_CALLS=$(sqlite3 "$DEMO_DIR/.loomweave/loomweave.db" \ "select count(*) from edges where kind = 'calls' and confidence = 'resolved';") if [ "$RESOLVED_CALLS" -lt 1 ]; then log "DB edge contents:" - sqlite3 "$DEMO_DIR/.clarion/clarion.db" "select kind, from_id, to_id, confidence, properties from edges order by kind, from_id, to_id;" >&2 || true + sqlite3 "$DEMO_DIR/.loomweave/loomweave.db" "select kind, from_id, to_id, confidence, properties from edges order by kind, from_id, to_id;" >&2 || true fail "expected at least one resolved calls edge; got $RESOLVED_CALLS" fi log "verifying persisted ambiguous calls edge with properties.candidates ..." -AMBIGUOUS_WITH_CANDIDATES=$(sqlite3 "$DEMO_DIR/.clarion/clarion.db" \ +AMBIGUOUS_WITH_CANDIDATES=$(sqlite3 "$DEMO_DIR/.loomweave/loomweave.db" \ "select count(*) from edges where kind = 'calls' and confidence = 'ambiguous' and json_type(properties, '\$.candidates') = 'array';") if [ "$AMBIGUOUS_WITH_CANDIDATES" -lt 1 ]; then log "DB edge contents:" - sqlite3 "$DEMO_DIR/.clarion/clarion.db" "select kind, from_id, to_id, confidence, properties from edges order by kind, from_id, to_id;" >&2 || true + sqlite3 "$DEMO_DIR/.loomweave/loomweave.db" "select kind, from_id, to_id, confidence, properties from edges order by kind, from_id, to_id;" >&2 || true fail "expected at least one ambiguous calls edge with properties.candidates; got $AMBIGUOUS_WITH_CANDIDATES" fi log "verifying run stats include ambiguous_edges_total >= 1 ..." -AMBIGUOUS_EDGES=$(sqlite3 "$DEMO_DIR/.clarion/clarion.db" \ +AMBIGUOUS_EDGES=$(sqlite3 "$DEMO_DIR/.loomweave/loomweave.db" \ "select json_extract(stats, '\$.ambiguous_edges_total') from runs where status = 'completed';") if [ "$AMBIGUOUS_EDGES" -lt 1 ]; then log "runs row:" - sqlite3 "$DEMO_DIR/.clarion/clarion.db" "select id, status, stats from runs;" >&2 || true + sqlite3 "$DEMO_DIR/.loomweave/loomweave.db" "select id, status, stats from runs;" >&2 || true fail "expected runs.stats.ambiguous_edges_total >= 1; got $AMBIGUOUS_EDGES" fi log "verifying run stats include unresolved_call_sites_total == 0 ..." -UNRESOLVED_CALL_SITES=$(sqlite3 "$DEMO_DIR/.clarion/clarion.db" \ +UNRESOLVED_CALL_SITES=$(sqlite3 "$DEMO_DIR/.loomweave/loomweave.db" \ "select json_extract(stats, '\$.unresolved_call_sites_total') from runs where status = 'completed';") if [ "$UNRESOLVED_CALL_SITES" != "0" ]; then log "runs row:" - sqlite3 "$DEMO_DIR/.clarion/clarion.db" "select id, status, stats from runs;" >&2 || true + sqlite3 "$DEMO_DIR/.loomweave/loomweave.db" "select id, status, stats from runs;" >&2 || true fail "expected runs.stats.unresolved_call_sites_total == 0; got $UNRESOLVED_CALL_SITES" fi # ── 13. Verify resolved references edges (B.5*) ───────────────────────────── log "verifying persisted resolved references edges ..." -RESOLVED_REFERENCES=$(sqlite3 "$DEMO_DIR/.clarion/clarion.db" \ +RESOLVED_REFERENCES=$(sqlite3 "$DEMO_DIR/.loomweave/loomweave.db" \ "select count(*) from edges where kind = 'references' and confidence = 'resolved';") if [ "$RESOLVED_REFERENCES" -lt 2 ]; then log "DB edge contents:" - sqlite3 "$DEMO_DIR/.clarion/clarion.db" "select kind, from_id, to_id, confidence, properties from edges order by kind, from_id, to_id;" >&2 || true + sqlite3 "$DEMO_DIR/.loomweave/loomweave.db" "select kind, from_id, to_id, confidence, properties from edges order by kind, from_id, to_id;" >&2 || true fail "expected at least two resolved references edges; got $RESOLVED_REFERENCES" fi log "verifying run stats include reference resolver counters ..." -REFERENCE_SITES=$(sqlite3 "$DEMO_DIR/.clarion/clarion.db" \ +REFERENCE_SITES=$(sqlite3 "$DEMO_DIR/.loomweave/loomweave.db" \ "select json_extract(stats, '\$.reference_sites_total') from runs where status = 'completed';") -REFERENCES_RESOLVED=$(sqlite3 "$DEMO_DIR/.clarion/clarion.db" \ +REFERENCES_RESOLVED=$(sqlite3 "$DEMO_DIR/.loomweave/loomweave.db" \ "select json_extract(stats, '\$.references_resolved_total') from runs where status = 'completed';") if [ "$REFERENCE_SITES" -lt 2 ] || [ "$REFERENCES_RESOLVED" -lt 2 ]; then log "runs row:" - sqlite3 "$DEMO_DIR/.clarion/clarion.db" "select id, status, stats from runs;" >&2 || true + sqlite3 "$DEMO_DIR/.loomweave/loomweave.db" "select id, status, stats from runs;" >&2 || true fail "expected reference_sites_total and references_resolved_total >= 2; got sites=$REFERENCE_SITES resolved=$REFERENCES_RESOLVED" fi diff --git a/tests/e2e/sprint_2_mcp_surface.sh b/tests/e2e/sprint_2_mcp_surface.sh index 9c684674..1afc74d1 100755 --- a/tests/e2e/sprint_2_mcp_surface.sh +++ b/tests/e2e/sprint_2_mcp_surface.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash # Sprint 2 B.6 MCP-surface end-to-end test. # -# Builds a real demo Clarion database through `clarion analyze`, starts -# `clarion serve`, and sends Content-Length framed MCP JSON-RPC requests for +# Builds a real demo Loomweave database through `loomweave analyze`, starts +# `loomweave serve`, and sends Content-Length framed MCP JSON-RPC requests for # the MCP navigation tools. Filigree is represented by a local HTTP # server that implements the B.7 reverse entity-association route. @@ -18,22 +18,22 @@ fail() { printf '[mcp-surface] FAIL: %s\n' "$*" >&2; exit 1; } cd "$REPO_ROOT" if [ "$CARGO_BUILD" = "1" ]; then - log "building clarion (release) ..." + log "building loomweave (release) ..." cargo build --workspace --release fi -CLARION_BIN="$REPO_ROOT/target/release/clarion" -[ -x "$CLARION_BIN" ] || fail "clarion binary missing at $CLARION_BIN" +LOOMWEAVE_BIN="$REPO_ROOT/target/release/loomweave" +[ -x "$LOOMWEAVE_BIN" ] || fail "loomweave binary missing at $LOOMWEAVE_BIN" if [ ! -d "$VENV" ]; then log "creating venv at $VENV ..." python3 -m venv "$VENV" fi -log "installing clarion-plugin-python (editable) ..." +log "installing loomweave-plugin-python (editable) ..." "$VENV/bin/pip" install --quiet -e "$REPO_ROOT/plugins/python[dev]" -PLUGIN_BIN="$VENV/bin/clarion-plugin-python" -[ -x "$PLUGIN_BIN" ] || fail "clarion-plugin-python missing at $PLUGIN_BIN" +PLUGIN_BIN="$VENV/bin/loomweave-plugin-python" +[ -x "$PLUGIN_BIN" ] || fail "loomweave-plugin-python missing at $PLUGIN_BIN" -DEMO_DIR="$(mktemp -d -t clarion-mcp-demo-XXXXXX)" +DEMO_DIR="$(mktemp -d -t loomweave-mcp-demo-XXXXXX)" trap 'rm -rf "$DEMO_DIR"' EXIT log "scratch project: $DEMO_DIR" cd "$DEMO_DIR" @@ -63,15 +63,15 @@ PY export PATH="$REPO_ROOT/target/release:$VENV/bin:$PATH" -log "running: clarion install" -clarion install -[ -f "$DEMO_DIR/.clarion/clarion.db" ] || fail ".clarion/clarion.db not created" +log "running: loomweave install" +loomweave install +[ -f "$DEMO_DIR/.loomweave/loomweave.db" ] || fail ".loomweave/loomweave.db not created" -log "running: clarion analyze ." -clarion analyze . +log "running: loomweave analyze ." +loomweave analyze . log "driving MCP stdio requests ..." -python3 - "$CLARION_BIN" "$DEMO_DIR" <<'PY' +python3 - "$LOOMWEAVE_BIN" "$DEMO_DIR" <<'PY' import json import sqlite3 import subprocess @@ -81,7 +81,7 @@ import urllib.parse from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from pathlib import Path -clarion_bin = Path(sys.argv[1]) +loomweave_bin = Path(sys.argv[1]) project_dir = Path(sys.argv[2]) @@ -119,7 +119,7 @@ def assert_tool_ok(response: dict[str, object]) -> dict[str, object]: return envelope -conn = sqlite3.connect(project_dir / ".clarion" / "clarion.db") +conn = sqlite3.connect(project_dir / ".loomweave" / "loomweave.db") world_hash = conn.execute( "SELECT content_hash FROM entities WHERE id = ?", ("python:function:demo.world",), @@ -145,7 +145,7 @@ world_source = Path(world_entity[3]).read_text(encoding="utf-8") world_lines = world_source.splitlines(keepends=True) world_excerpt = "".join(world_lines[int(world_entity[4]) - 1 : int(world_entity[5])]) world_prompt = ( - "You are summarising one Clarion entity at leaf scope only.\n" + "You are summarising one Loomweave entity at leaf scope only.\n" f"Entity id: {world_entity[0]}\n" f"Kind: {world_entity[1]}\n" f"Name: {world_entity[2]}\n" @@ -180,7 +180,7 @@ recording_fixture = [ }, } ] -(project_dir / ".clarion" / "openrouter-recording.json").write_text( +(project_dir / ".loomweave" / "openrouter-recording.json").write_text( json.dumps(recording_fixture, separators=(",", ":")), encoding="utf-8", ) @@ -199,7 +199,7 @@ class FiligreeHandler(BaseHTTPRequestHandler): associations.append( { "issue_id": "filigree-world", - "clarion_entity_id": entity_id, + "loomweave_entity_id": entity_id, "content_hash_at_attach": world_hash, "attached_at": "2026-05-17T00:00:00.000Z", "attached_by": "codex", @@ -209,14 +209,14 @@ class FiligreeHandler(BaseHTTPRequestHandler): associations.append( { "issue_id": "filigree-hello-drifted", - "clarion_entity_id": entity_id, + "loomweave_entity_id": entity_id, "content_hash_at_attach": "old-hash", "attached_at": "2026-05-17T00:00:00.000Z", "attached_by": "codex", } ) body = json.dumps({"associations": associations}).encode("utf-8") - elif parsed.path == "/api/loom/files": + elif parsed.path == "/api/weft/files": path_prefix = query.get("path_prefix", [""])[0] items = [] if query.get("scan_source", [""])[0] == "wardline" and path_prefix == "demo.py": @@ -229,7 +229,7 @@ class FiligreeHandler(BaseHTTPRequestHandler): } ] body = json.dumps({"items": items, "has_more": False}).encode("utf-8") - elif parsed.path == "/api/loom/findings": + elif parsed.path == "/api/weft/findings": body = json.dumps({"items": [], "has_more": False}).encode("utf-8") else: self.send_error(404) @@ -247,7 +247,7 @@ class FiligreeHandler(BaseHTTPRequestHandler): filigree_server = ThreadingHTTPServer(("127.0.0.1", 0), FiligreeHandler) filigree_thread = threading.Thread(target=filigree_server.serve_forever, daemon=True) filigree_thread.start() -(project_dir / "clarion.yaml").write_text( +(project_dir / "loomweave.yaml").write_text( f""" version: 1 llm_policy: @@ -255,7 +255,7 @@ llm_policy: provider: recording model_id: anthropic/claude-sonnet-4.6 session_token_ceiling: 1000000 - recording_fixture_path: .clarion/openrouter-recording.json + recording_fixture_path: .loomweave/openrouter-recording.json serve: mcp: enable_write_tools: true @@ -263,7 +263,7 @@ integrations: filigree: enabled: true base_url: http://127.0.0.1:{filigree_server.server_port} - actor: clarion-e2e + actor: loomweave-e2e token_env: FILIGREE_API_TOKEN timeout_seconds: 2 """.lstrip(), @@ -271,7 +271,7 @@ integrations: ) proc = subprocess.Popen( - [str(clarion_bin), "serve", "--path", str(project_dir)], + [str(loomweave_bin), "serve", "--path", str(project_dir)], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, @@ -290,7 +290,7 @@ requests: list[tuple[str, dict[str, object]]] = [ "params": { "protocolVersion": "2025-11-25", "capabilities": {}, - "clientInfo": {"name": "clarion-e2e", "version": "0.0.0"}, + "clientInfo": {"name": "loomweave-e2e", "version": "0.0.0"}, }, }, ), @@ -418,7 +418,7 @@ requests: list[tuple[str, dict[str, object]]] = [ "jsonrpc": "2.0", "id": "context", "method": "resources/read", - "params": {"uri": "clarion://context"}, + "params": {"uri": "loomweave://context"}, }, ), ] @@ -436,11 +436,11 @@ finally: stderr = proc.stderr.read().decode("utf-8", "replace") filigree_server.shutdown() filigree_server.server_close() - assert status == 0, f"clarion serve exited {status}; stderr={stderr}" + assert status == 0, f"loomweave serve exited {status}; stderr={stderr}" assert responses["initialize"]["result"]["protocolVersion"] == "2025-11-25" init_result = responses["initialize"]["result"] -assert "clarion-workflow" in init_result["instructions"], init_result.get("instructions") +assert "loomweave-workflow" in init_result["instructions"], init_result.get("instructions") assert isinstance(init_result["capabilities"]["resources"], dict), init_result["capabilities"] assert isinstance(init_result["capabilities"]["prompts"], dict), init_result["capabilities"] tools = responses["tools"]["result"]["tools"] diff --git a/tests/e2e/wp5_secret_scan.sh b/tests/e2e/wp5_secret_scan.sh index 0365ac41..6e4b62e4 100755 --- a/tests/e2e/wp5_secret_scan.sh +++ b/tests/e2e/wp5_secret_scan.sh @@ -12,18 +12,18 @@ fail() { printf '[wp5-secret-scan] FAIL: %s\n' "$*" >&2; exit 1; } cd "$REPO_ROOT" if [ "$CARGO_BUILD" = "1" ]; then - log "building clarion (release) ..." + log "building loomweave (release) ..." cargo build --workspace --release fi -CLARION_BIN="$REPO_ROOT/target/release/clarion" -[ -x "$CLARION_BIN" ] || fail "clarion binary missing at $CLARION_BIN" +LOOMWEAVE_BIN="$REPO_ROOT/target/release/loomweave" +[ -x "$LOOMWEAVE_BIN" ] || fail "loomweave binary missing at $LOOMWEAVE_BIN" -DEMO_DIR="$(mktemp -d -t clarion-wp5-demo-XXXXXX)" -PLUGIN_DIR="$(mktemp -d -t clarion-wp5-plugin-XXXXXX)" +DEMO_DIR="$(mktemp -d -t loomweave-wp5-demo-XXXXXX)" +PLUGIN_DIR="$(mktemp -d -t loomweave-wp5-plugin-XXXXXX)" trap 'rm -rf "$DEMO_DIR" "$PLUGIN_DIR"' EXIT -cat > "$PLUGIN_DIR/clarion-plugin-secretfixture" <<'PY' +cat > "$PLUGIN_DIR/loomweave-plugin-secretfixture" <<'PY' #!/usr/bin/python3 import json import pathlib @@ -56,7 +56,7 @@ while True: raise SystemExit(0) ident = msg["id"] if method == "initialize": - write_frame({"jsonrpc":"2.0","id":ident,"result":{"name":"clarion-plugin-secretfixture","version":"0.1.0","ontology_version":"0.1.0","capabilities":{}}}) + write_frame({"jsonrpc":"2.0","id":ident,"result":{"name":"loomweave-plugin-secretfixture","version":"0.1.0","ontology_version":"0.1.0","capabilities":{}}}) elif method == "analyze_file": path = msg["params"]["file_path"] name = "file_" + re.sub(r"[^A-Za-z0-9_]", "_", pathlib.Path(path).name) @@ -66,15 +66,15 @@ while True: else: raise SystemExit(1) PY -chmod 755 "$PLUGIN_DIR/clarion-plugin-secretfixture" +chmod 755 "$PLUGIN_DIR/loomweave-plugin-secretfixture" cat > "$PLUGIN_DIR/plugin.toml" <<'TOML' [plugin] -name = "clarion-plugin-secretfixture" +name = "loomweave-plugin-secretfixture" plugin_id = "secretfixture" version = "0.1.0" protocol_version = "1.0" -executable = "clarion-plugin-secretfixture" +executable = "loomweave-plugin-secretfixture" language = "secretfixture" extensions = ["sec"] @@ -87,23 +87,23 @@ reads_outside_project_root = false [ontology] entity_kinds = ["module"] edge_kinds = [] -rule_id_prefix = "CLA-SECRET-FIXTURE-" +rule_id_prefix = "LMWV-SECRET-FIXTURE-" ontology_version = "0.1.0" TOML log "scratch project: $DEMO_DIR" -"$CLARION_BIN" install --path "$DEMO_DIR" +"$LOOMWEAVE_BIN" install --path "$DEMO_DIR" printf "aws_access_key_id = 'AKIAIOSFODNN7EXAMPLE'\n" > "$DEMO_DIR/leaky.sec" printf "aws_access_key_id = 'AKIAIOSFODNN7EXAMPLE'\n" > "$DEMO_DIR/.env" -PATH="$PLUGIN_DIR" "$CLARION_BIN" analyze "$DEMO_DIR" +PATH="$PLUGIN_DIR" "$LOOMWEAVE_BIN" analyze "$DEMO_DIR" -DB="$DEMO_DIR/.clarion/clarion.db" +DB="$DEMO_DIR/.loomweave/loomweave.db" BLOCKED=$(sqlite3 "$DB" "select count(*) from entities where json_extract(properties, '\$.briefing_blocked') = 'secret_present';") [ "$BLOCKED" = "3" ] || fail "expected three briefing_blocked secret_present entities (core file + plugin source entity + dotenv anchor), got $BLOCKED" -FINDINGS=$(sqlite3 "$DB" "select count(*) from findings where rule_id = 'CLA-SEC-SECRET-DETECTED';") -[ "$FINDINGS" = "2" ] || fail "expected two CLA-SEC-SECRET-DETECTED findings, got $FINDINGS" +FINDINGS=$(sqlite3 "$DB" "select count(*) from findings where rule_id = 'LMWV-SEC-SECRET-DETECTED';") +[ "$FINDINGS" = "2" ] || fail "expected two LMWV-SEC-SECRET-DETECTED findings, got $FINDINGS" DOTENV_ANCHOR=$(sqlite3 "$DB" "select count(*) from entities where plugin_id = 'core' and kind = 'file' and source_file_path = '$DEMO_DIR/.env' and json_extract(properties, '\$.briefing_blocked') = 'secret_present';") [ "$DOTENV_ANCHOR" = "1" ] || fail "expected .env core file anchor with briefing_blocked secret_present, got $DOTENV_ANCHOR" @@ -117,12 +117,12 @@ log "PASS: WP5 secret scan blocks summaries and records finding" # baseline-suppression flow (clarion-55fc5aa885 §I11) # ---------------------------------------------------------------------------- log "scenario: baseline-suppressed detection emits BASELINE-MATCH only, no block" -BASELINE_DIR="$(mktemp -d -t clarion-wp5-baseline-XXXXXX)" +BASELINE_DIR="$(mktemp -d -t loomweave-wp5-baseline-XXXXXX)" trap 'rm -rf "$DEMO_DIR" "$PLUGIN_DIR" "$BASELINE_DIR" "$OVERRIDE_DIR" "$MALFORMED_DIR"' EXIT -"$CLARION_BIN" install --path "$BASELINE_DIR" +"$LOOMWEAVE_BIN" install --path "$BASELINE_DIR" printf "aws_access_key_id = 'AKIAIOSFODNN7EXAMPLE'\n" > "$BASELINE_DIR/leaky.sec" HASHED_SECRET=$(printf 'AKIAIOSFODNN7EXAMPLE' | sha1sum | awk '{print $1}') -cat > "$BASELINE_DIR/.clarion/secrets-baseline.yaml" < "$BASELINE_DIR/.loomweave/secrets-baseline.yaml" < "$OVERRIDE_DIR/leaky.sec" -PATH="$PLUGIN_DIR" "$CLARION_BIN" analyze \ +PATH="$PLUGIN_DIR" "$LOOMWEAVE_BIN" analyze \ --allow-unredacted-secrets \ --confirm-allow-unredacted-secrets=yes-i-understand \ "$OVERRIDE_DIR" -ODB="$OVERRIDE_DIR/.clarion/clarion.db" +ODB="$OVERRIDE_DIR/.loomweave/loomweave.db" O_BLOCKED=$(sqlite3 "$ODB" "select count(*) from entities where json_extract(properties, '\$.briefing_blocked') = 'secret_present';") [ "$O_BLOCKED" = "0" ] || fail "override: expected 0 blocked entities, got $O_BLOCKED" -O_SECRET=$(sqlite3 "$ODB" "select count(*) from findings where rule_id = 'CLA-SEC-SECRET-DETECTED';") +O_SECRET=$(sqlite3 "$ODB" "select count(*) from findings where rule_id = 'LMWV-SEC-SECRET-DETECTED';") [ "$O_SECRET" = "1" ] || fail "override is additive per ADR-013: expected 1 SECRET_DETECTED finding, got $O_SECRET" -O_OVERRIDE=$(sqlite3 "$ODB" "select count(*) from findings where rule_id = 'CLA-SEC-UNREDACTED-SECRETS-ALLOWED';") +O_OVERRIDE=$(sqlite3 "$ODB" "select count(*) from findings where rule_id = 'LMWV-SEC-UNREDACTED-SECRETS-ALLOWED';") [ "$O_OVERRIDE" = "1" ] || fail "override: expected 1 UNREDACTED-SECRETS-ALLOWED finding, got $O_OVERRIDE" O_OVERRIDE_USED=$(sqlite3 "$ODB" "select json_extract(stats, '\$.secret_override_used') from runs;") [ "$O_OVERRIDE_USED" = "1" ] || fail "override: expected stats.secret_override_used = 1, got $O_OVERRIDE_USED" @@ -168,16 +168,16 @@ log "PASS: override admission lands both SECRET_DETECTED and UNREDACTED-SECRETS- # malformed-baseline abort (clarion-55fc5aa885 §I11) # ---------------------------------------------------------------------------- log "scenario: malformed baseline aborts analyze before BeginRun (exit 78)" -MALFORMED_DIR="$(mktemp -d -t clarion-wp5-malformed-XXXXXX)" -"$CLARION_BIN" install --path "$MALFORMED_DIR" +MALFORMED_DIR="$(mktemp -d -t loomweave-wp5-malformed-XXXXXX)" +"$LOOMWEAVE_BIN" install --path "$MALFORMED_DIR" printf "harmless = 'nothing'\n" > "$MALFORMED_DIR/clean.sec" -printf "not: valid: yaml: [\n" > "$MALFORMED_DIR/.clarion/secrets-baseline.yaml" +printf "not: valid: yaml: [\n" > "$MALFORMED_DIR/.loomweave/secrets-baseline.yaml" set +e -PATH="$PLUGIN_DIR" "$CLARION_BIN" analyze "$MALFORMED_DIR" 2>/dev/null +PATH="$PLUGIN_DIR" "$LOOMWEAVE_BIN" analyze "$MALFORMED_DIR" 2>/dev/null MALFORMED_EXIT=$? set -e [ "$MALFORMED_EXIT" -ne 0 ] || fail "malformed baseline: expected non-zero exit, got $MALFORMED_EXIT" -MDB="$MALFORMED_DIR/.clarion/clarion.db" +MDB="$MALFORMED_DIR/.loomweave/loomweave.db" M_RUNS=$(sqlite3 "$MDB" "select count(*) from runs;") [ "$M_RUNS" = "0" ] || fail "malformed baseline must abort BEFORE BeginRun; got $M_RUNS run rows" log "PASS: malformed baseline aborts with non-zero exit and no runs row" @@ -189,17 +189,17 @@ log "PASS: malformed baseline aborts with non-zero exit and no runs row" # DEMO_DIR (which started blocked) after we wipe and re-add a baseline. # ---------------------------------------------------------------------------- log "scenario: retry-after-baseline-add unblocks a previously-blocked file" -RETRY_DIR="$(mktemp -d -t clarion-wp5-retry-XXXXXX)" +RETRY_DIR="$(mktemp -d -t loomweave-wp5-retry-XXXXXX)" trap 'rm -rf "$DEMO_DIR" "$PLUGIN_DIR" "$BASELINE_DIR" "$OVERRIDE_DIR" "$MALFORMED_DIR" "$RETRY_DIR"' EXIT -"$CLARION_BIN" install --path "$RETRY_DIR" +"$LOOMWEAVE_BIN" install --path "$RETRY_DIR" printf "aws_access_key_id = 'AKIAIOSFODNN7EXAMPLE'\n" > "$RETRY_DIR/leaky.sec" # First run blocks (no baseline yet). -PATH="$PLUGIN_DIR" "$CLARION_BIN" analyze "$RETRY_DIR" -RDB="$RETRY_DIR/.clarion/clarion.db" +PATH="$PLUGIN_DIR" "$LOOMWEAVE_BIN" analyze "$RETRY_DIR" +RDB="$RETRY_DIR/.loomweave/loomweave.db" R_BLOCKED_BEFORE=$(sqlite3 "$RDB" "select count(*) from entities where json_extract(properties, '\$.briefing_blocked') = 'secret_present';") [ "$R_BLOCKED_BEFORE" = "2" ] || fail "retry: first run must block the source file entity and plugin entity, got $R_BLOCKED_BEFORE" # Operator commits a baseline acknowledging the example key. -cat > "$RETRY_DIR/.clarion/secrets-baseline.yaml" < "$RETRY_DIR/.loomweave/secrets-baseline.yaml" < None: class McpClient: def __init__( self, - clarion_bin: Path, + loomweave_bin: Path, project: Path, config: Path | None, timeout_seconds: float, ) -> None: - command = [str(clarion_bin), "serve", "--path", str(project)] + command = [str(loomweave_bin), "serve", "--path", str(project)] if config is not None: command.extend(["--config", str(config)]) self.proc = subprocess.Popen( @@ -411,7 +411,7 @@ def close(self) -> str: stderr = self.proc.stderr.read().decode("utf-8", "replace") if self.proc.returncode != 0: raise RuntimeError( - f"clarion serve exited {self.proc.returncode}; stderr={stderr}" + f"loomweave serve exited {self.proc.returncode}; stderr={stderr}" ) return stderr @@ -423,7 +423,7 @@ def _rows( def discover_targets(project: Path) -> QueryTargets: - db_path = project / ".clarion" / "clarion.db" + db_path = project / ".loomweave" / "loomweave.db" conn = sqlite3.connect(db_path) conn.row_factory = sqlite3.Row try: @@ -713,14 +713,14 @@ def build_requests( def run_driver(args: argparse.Namespace) -> dict[str, Any]: project = args.project.resolve() - clarion_bin = args.clarion_bin.resolve() + loomweave_bin = args.loomweave_bin.resolve() config = args.config.resolve() if args.config else None targets = discover_targets(project) requests, skipped_patterns = build_requests( targets, args.heavy_count, not args.skip_inferred ) - client = McpClient(clarion_bin, project, config, args.timeout_seconds) + client = McpClient(loomweave_bin, project, config, args.timeout_seconds) records: list[CallRecord] = [] try: init_response, _, initialize_latency_ms = client.request( @@ -731,7 +731,7 @@ def run_driver(args: argparse.Namespace) -> dict[str, Any]: "params": { "protocolVersion": "2025-11-25", "capabilities": {}, - "clientInfo": {"name": "clarion-b8-driver", "version": "0.1.0"}, + "clientInfo": {"name": "loomweave-b8-driver", "version": "0.1.0"}, }, } ) @@ -766,7 +766,7 @@ def run_driver(args: argparse.Namespace) -> dict[str, Any]: return { "generated_at_unix": int(time.time()), "project": str(project), - "clarion_bin": str(clarion_bin), + "loomweave_bin": str(loomweave_bin), "config": str(config) if config else None, "initialize": init_response, "initialize_latency_ms": round(initialize_latency_ms, 3), @@ -786,13 +786,13 @@ def build_parser() -> argparse.ArgumentParser: "--project", type=Path, required=True, - help="Analyzed project root containing .clarion/clarion.db", + help="Analyzed project root containing .loomweave/loomweave.db", ) parser.add_argument( - "--clarion-bin", type=Path, default=Path("target/release/clarion") + "--loomweave-bin", type=Path, default=Path("target/release/loomweave") ) parser.add_argument( - "--config", type=Path, help="Optional clarion serve config path" + "--config", type=Path, help="Optional loomweave serve config path" ) parser.add_argument("--output", type=Path, required=True, help="JSON output path") parser.add_argument("--heavy-count", type=int, default=50) diff --git a/tests/perf/b8_scale_test/results/2026-05-17T2156Z/analyze-metrics.json b/tests/perf/b8_scale_test/results/2026-05-17T2156Z/analyze-metrics.json index 6a6417ae..f12ba62f 100644 --- a/tests/perf/b8_scale_test/results/2026-05-17T2156Z/analyze-metrics.json +++ b/tests/perf/b8_scale_test/results/2026-05-17T2156Z/analyze-metrics.json @@ -1,8 +1,8 @@ { "command": [ - "/home/john/clarion/target/release/clarion", + "/home/john/loomweave/target/release/loomweave", "analyze", - "/tmp/clarion-b8-elspeth-tests-20260517T2156Z" + "/tmp/loomweave-b8-elspeth-tests-20260517T2156Z" ], "peak_rss_bytes": 2033442816, "peak_rss_mb": 1939.242, diff --git a/tests/perf/b8_scale_test/results/2026-05-17T2156Z/analyze.stdout b/tests/perf/b8_scale_test/results/2026-05-17T2156Z/analyze.stdout index ddd6e4e0..4a10843b 100644 --- a/tests/perf/b8_scale_test/results/2026-05-17T2156Z/analyze.stdout +++ b/tests/perf/b8_scale_test/results/2026-05-17T2156Z/analyze.stdout @@ -1,15 +1,15 @@ -2026-05-17T21:58:02.761145Z WARN skipping plugin: discovery error error=no plugin.toml found for /home/john/clarion/target/release/clarion-plugin-fixture (searched neighbor dir and install-prefix share/) -2026-05-17T21:58:02.761174Z INFO discovered plugin plugin_id=python executable=/home/john/clarion/plugins/python/.venv/bin/clarion-plugin-python +2026-05-17T21:58:02.761145Z WARN skipping plugin: discovery error error=no plugin.toml found for /home/john/loomweave/target/release/loomweave-plugin-fixture (searched neighbor dir and install-prefix share/) +2026-05-17T21:58:02.761174Z INFO discovered plugin plugin_id=python executable=/home/john/loomweave/plugins/python/.venv/bin/loomweave-plugin-python 2026-05-17T21:58:02.762551Z INFO source tree walk complete file_count=1037 2026-05-17T21:58:02.762641Z INFO processing plugin plugin_id=python file_count=1037 2026-05-17T22:05:24.108180Z WARN plugin host collected findings plugin_id=python finding_count=8 -2026-05-17T22:05:24.108206Z WARN plugin host finding plugin_id=python subcode=CLA-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:integration.cli.test_instantiate_plugins_value_source._build_yaml_with_model", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "0", "source_byte_end": "3309", "source_byte_start": "2425"} -2026-05-17T22:05:24.108214Z WARN plugin host finding plugin_id=python subcode=CLA-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:integration.audit.test_pass_through_violation_persists.TestAuditRoundTrip.test_json_extract_returns_per_token_identifiers", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "15", "source_byte_end": "6229", "source_byte_start": "5123"} -2026-05-17T22:05:24.108220Z WARN plugin host finding plugin_id=python subcode=CLA-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:unit.scripts.cicd.test_adr019_test_inventory.test_positive_fixture_reports_required_finding_kinds", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "1", "source_byte_end": "1642", "source_byte_start": "613"} -2026-05-17T22:05:24.108227Z WARN plugin host finding plugin_id=python subcode=CLA-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:unit.web.composer.test_tools.TestPreviewPipeline.test_preview_pipeline_suggests_fork_gate_for_duplicate_consumers", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "0", "source_byte_end": "277113", "source_byte_start": "275103"} -2026-05-17T22:05:24.108233Z WARN plugin host finding plugin_id=python subcode=CLA-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:unit.web.composer.test_tools.TestPreviewPipeline.test_preview_pipeline_suggests_fork_gate_for_duplicate_consumers", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "1", "source_byte_end": "276976", "source_byte_start": "275103"} -2026-05-17T22:05:24.108239Z WARN plugin host finding plugin_id=python subcode=CLA-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:unit.web.composer.test_tools.TestPreviewPipeline.test_preview_pipeline_suggests_fork_gate_for_duplicate_consumers", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "2", "source_byte_end": "276221", "source_byte_start": "275103"} -2026-05-17T22:05:24.108244Z WARN plugin host finding plugin_id=python subcode=CLA-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:property.audit.test_terminal_states.count_duplicate_terminal_outcomes", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "1", "source_byte_end": "3642", "source_byte_start": "3088"} -2026-05-17T22:05:24.108250Z WARN plugin host finding plugin_id=python subcode=CLA-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:property.audit.test_fork_join_balance.count_fork_groups_with_unexpected_children", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "1", "source_byte_end": "5971", "source_byte_start": "5387"} +2026-05-17T22:05:24.108206Z WARN plugin host finding plugin_id=python subcode=LMWV-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:integration.cli.test_instantiate_plugins_value_source._build_yaml_with_model", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "0", "source_byte_end": "3309", "source_byte_start": "2425"} +2026-05-17T22:05:24.108214Z WARN plugin host finding plugin_id=python subcode=LMWV-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:integration.audit.test_pass_through_violation_persists.TestAuditRoundTrip.test_json_extract_returns_per_token_identifiers", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "15", "source_byte_end": "6229", "source_byte_start": "5123"} +2026-05-17T22:05:24.108220Z WARN plugin host finding plugin_id=python subcode=LMWV-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:unit.scripts.cicd.test_adr019_test_inventory.test_positive_fixture_reports_required_finding_kinds", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "1", "source_byte_end": "1642", "source_byte_start": "613"} +2026-05-17T22:05:24.108227Z WARN plugin host finding plugin_id=python subcode=LMWV-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:unit.web.composer.test_tools.TestPreviewPipeline.test_preview_pipeline_suggests_fork_gate_for_duplicate_consumers", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "0", "source_byte_end": "277113", "source_byte_start": "275103"} +2026-05-17T22:05:24.108233Z WARN plugin host finding plugin_id=python subcode=LMWV-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:unit.web.composer.test_tools.TestPreviewPipeline.test_preview_pipeline_suggests_fork_gate_for_duplicate_consumers", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "1", "source_byte_end": "276976", "source_byte_start": "275103"} +2026-05-17T22:05:24.108239Z WARN plugin host finding plugin_id=python subcode=LMWV-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:unit.web.composer.test_tools.TestPreviewPipeline.test_preview_pipeline_suggests_fork_gate_for_duplicate_consumers", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "2", "source_byte_end": "276221", "source_byte_start": "275103"} +2026-05-17T22:05:24.108244Z WARN plugin host finding plugin_id=python subcode=LMWV-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:property.audit.test_terminal_states.count_duplicate_terminal_outcomes", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "1", "source_byte_end": "3642", "source_byte_start": "3088"} +2026-05-17T22:05:24.108250Z WARN plugin host finding plugin_id=python subcode=LMWV-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:property.audit.test_fork_join_balance.count_fork_groups_with_unexpected_children", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "1", "source_byte_end": "5971", "source_byte_start": "5387"} 2026-05-17T22:05:29.807596Z INFO plugin complete plugin_id=python entity_count=26813 edge_count=45369 analyze complete: run 2c1191ee-294d-472e-90ea-d73173da8368 completed (26813 entities, 45369 edges) diff --git a/tests/perf/b8_scale_test/results/2026-05-17T2156Z/corpus-copy.txt b/tests/perf/b8_scale_test/results/2026-05-17T2156Z/corpus-copy.txt index 230b3643..e3f87b94 100644 --- a/tests/perf/b8_scale_test/results/2026-05-17T2156Z/corpus-copy.txt +++ b/tests/perf/b8_scale_test/results/2026-05-17T2156Z/corpus-copy.txt @@ -1,3 +1,3 @@ -slice=/tmp/clarion-b8-elspeth-tests-20260517T2156Z +slice=/tmp/loomweave-b8-elspeth-tests-20260517T2156Z python_files=1037 python_loc=429870 diff --git a/tests/perf/b8_scale_test/results/2026-05-17T2156Z/install.stdout b/tests/perf/b8_scale_test/results/2026-05-17T2156Z/install.stdout index 6e0fe363..09c70bbe 100644 --- a/tests/perf/b8_scale_test/results/2026-05-17T2156Z/install.stdout +++ b/tests/perf/b8_scale_test/results/2026-05-17T2156Z/install.stdout @@ -1,3 +1,3 @@ 2026-05-17T21:58:02.731231Z INFO applying migration version=1 name="0001_initial_schema" -2026-05-17T21:58:02.736718Z INFO clarion install complete clarion_dir=/tmp/clarion-b8-elspeth-tests-20260517T2156Z/.clarion -Initialised /tmp/clarion-b8-elspeth-tests-20260517T2156Z/.clarion +2026-05-17T21:58:02.736718Z INFO loomweave install complete loomweave_dir=/tmp/loomweave-b8-elspeth-tests-20260517T2156Z/.loomweave +Initialised /tmp/loomweave-b8-elspeth-tests-20260517T2156Z/.loomweave diff --git a/tests/perf/b8_scale_test/results/2026-05-17T2156Z/mcp-driver-output.json b/tests/perf/b8_scale_test/results/2026-05-17T2156Z/mcp-driver-output.json index e489b869..9e696463 100644 --- a/tests/perf/b8_scale_test/results/2026-05-17T2156Z/mcp-driver-output.json +++ b/tests/perf/b8_scale_test/results/2026-05-17T2156Z/mcp-driver-output.json @@ -1,6 +1,6 @@ { - "clarion_bin": "/home/john/clarion/target/release/clarion", - "config": "/tmp/clarion-b8-elspeth-tests-20260517T2156Z/clarion-b8-live.yaml", + "loomweave_bin": "/home/john/loomweave/target/release/loomweave", + "config": "/tmp/loomweave-b8-elspeth-tests-20260517T2156Z/loomweave-b8-live.yaml", "generated_at_unix": 1779055991, "initialize": { "id": "init", @@ -11,7 +11,7 @@ }, "protocolVersion": "2025-11-25", "serverInfo": { - "name": "clarion", + "name": "loomweave", "version": "0.1.0-dev" } } @@ -1085,7 +1085,7 @@ "tool": "callers_of" } ], - "project": "/tmp/clarion-b8-elspeth-tests-20260517T2156Z", + "project": "/tmp/loomweave-b8-elspeth-tests-20260517T2156Z", "records": [ { "cache_hit": null, diff --git a/tests/perf/b8_scale_test/results/2026-05-17T2156Z/mcp-raw-error-probe.json b/tests/perf/b8_scale_test/results/2026-05-17T2156Z/mcp-raw-error-probe.json index 2c5c86de..0a25a6c1 100644 --- a/tests/perf/b8_scale_test/results/2026-05-17T2156Z/mcp-raw-error-probe.json +++ b/tests/perf/b8_scale_test/results/2026-05-17T2156Z/mcp-raw-error-probe.json @@ -1,7 +1,7 @@ { "generated_from": "targeted post-run raw MCP probe", - "project": "/tmp/clarion-b8-elspeth-tests-20260517T2156Z", - "config": "/tmp/clarion-b8-elspeth-tests-20260517T2156Z/clarion-b8-live.yaml", + "project": "/tmp/loomweave-b8-elspeth-tests-20260517T2156Z", + "config": "/tmp/loomweave-b8-elspeth-tests-20260517T2156Z/loomweave-b8-live.yaml", "summary_probe": { "latency_ms": 12213.408, "response": { diff --git a/tests/perf/b8_scale_test/results/2026-05-17T2243Z/mcp-driver-output-warm-cache.json b/tests/perf/b8_scale_test/results/2026-05-17T2243Z/mcp-driver-output-warm-cache.json index 5756ce32..d4da0f2a 100644 --- a/tests/perf/b8_scale_test/results/2026-05-17T2243Z/mcp-driver-output-warm-cache.json +++ b/tests/perf/b8_scale_test/results/2026-05-17T2243Z/mcp-driver-output-warm-cache.json @@ -1,6 +1,6 @@ { - "clarion_bin": "/home/john/clarion/target/release/clarion", - "config": "/tmp/clarion-b8-elspeth-tests-20260517T2156Z/clarion-b8-live.yaml", + "loomweave_bin": "/home/john/loomweave/target/release/loomweave", + "config": "/tmp/loomweave-b8-elspeth-tests-20260517T2156Z/loomweave-b8-live.yaml", "generated_at_unix": 1779058078, "initialize": { "id": "init", @@ -11,7 +11,7 @@ }, "protocolVersion": "2025-11-25", "serverInfo": { - "name": "clarion", + "name": "loomweave", "version": "0.1.0-dev" } } @@ -1085,7 +1085,7 @@ "tool": "callers_of" } ], - "project": "/tmp/clarion-b8-elspeth-tests-20260517T2156Z", + "project": "/tmp/loomweave-b8-elspeth-tests-20260517T2156Z", "records": [ { "cache_hit": null, diff --git a/tests/perf/b8_scale_test/results/2026-05-17T2243Z/mcp-driver-output.json b/tests/perf/b8_scale_test/results/2026-05-17T2243Z/mcp-driver-output.json index ca721bdd..f0edce8c 100644 --- a/tests/perf/b8_scale_test/results/2026-05-17T2243Z/mcp-driver-output.json +++ b/tests/perf/b8_scale_test/results/2026-05-17T2243Z/mcp-driver-output.json @@ -1,6 +1,6 @@ { - "clarion_bin": "/home/john/clarion/target/release/clarion", - "config": "/tmp/clarion-b8-elspeth-tests-20260517T2156Z/clarion-b8-live.yaml", + "loomweave_bin": "/home/john/loomweave/target/release/loomweave", + "config": "/tmp/loomweave-b8-elspeth-tests-20260517T2156Z/loomweave-b8-live.yaml", "generated_at_unix": 1779058028, "initialize": { "id": "init", @@ -11,7 +11,7 @@ }, "protocolVersion": "2025-11-25", "serverInfo": { - "name": "clarion", + "name": "loomweave", "version": "0.1.0-dev" } } @@ -1085,7 +1085,7 @@ "tool": "callers_of" } ], - "project": "/tmp/clarion-b8-elspeth-tests-20260517T2156Z", + "project": "/tmp/loomweave-b8-elspeth-tests-20260517T2156Z", "records": [ { "cache_hit": null, diff --git a/tests/perf/b8_scale_test/results/2026-05-18T0017Z/analyze-metrics.json b/tests/perf/b8_scale_test/results/2026-05-18T0017Z/analyze-metrics.json index 5bb4a482..457242d6 100644 --- a/tests/perf/b8_scale_test/results/2026-05-18T0017Z/analyze-metrics.json +++ b/tests/perf/b8_scale_test/results/2026-05-18T0017Z/analyze-metrics.json @@ -1,8 +1,8 @@ { "command": [ - "/home/john/clarion/target/release/clarion", + "/home/john/loomweave/target/release/loomweave", "analyze", - "/tmp/clarion-b8-elspeth-full-20260518T0016Z" + "/tmp/loomweave-b8-elspeth-full-20260518T0016Z" ], "peak_rss_bytes": 185532416, "peak_rss_mb": 176.938, diff --git a/tests/perf/b8_scale_test/results/2026-05-18T0017Z/analyze-with-rss.py b/tests/perf/b8_scale_test/results/2026-05-18T0017Z/analyze-with-rss.py index e44df106..60ab67d3 100644 --- a/tests/perf/b8_scale_test/results/2026-05-18T0017Z/analyze-with-rss.py +++ b/tests/perf/b8_scale_test/results/2026-05-18T0017Z/analyze-with-rss.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Run clarion analyze and sample peak RSS for B.8 measurement. +"""Run loomweave analyze and sample peak RSS for B.8 measurement. Mirrors the analyze-metrics.json schema from the 2026-05-17 B.8 run. """ @@ -48,13 +48,13 @@ def _rss_bytes_for_tree(root_pid: int) -> int: def main() -> int: if len(sys.argv) < 3: - print("usage: analyze-with-rss.py [args...]") + print("usage: analyze-with-rss.py [args...]") return 2 output = Path(sys.argv[1]) cmd = sys.argv[2:] env = os.environ.copy() - plugin_bin_dir = "/home/john/clarion/plugins/python/.venv/bin" + plugin_bin_dir = "/home/john/loomweave/plugins/python/.venv/bin" env["PATH"] = plugin_bin_dir + ":" + env.get("PATH", "") start = time.monotonic() diff --git a/tests/perf/b8_scale_test/results/2026-05-18T0017Z/analyze.stdout b/tests/perf/b8_scale_test/results/2026-05-18T0017Z/analyze.stdout index 3e058e3a..647f9fd5 100644 --- a/tests/perf/b8_scale_test/results/2026-05-18T0017Z/analyze.stdout +++ b/tests/perf/b8_scale_test/results/2026-05-18T0017Z/analyze.stdout @@ -1,11 +1,11 @@ -2026-05-18T00:19:08.063472Z  INFO discovered plugin plugin_id=python executable=/home/john/clarion/plugins/python/.venv/bin/clarion-plugin-python +2026-05-18T00:19:08.063472Z  INFO discovered plugin plugin_id=python executable=/home/john/loomweave/plugins/python/.venv/bin/loomweave-plugin-python 2026-05-18T00:19:08.076379Z  INFO source tree walk complete file_count=1526 2026-05-18T00:19:08.076602Z  INFO processing plugin plugin_id=python file_count=1526 2026-05-18T00:26:52.543145Z  WARN plugin host collected findings plugin_id=python finding_count=6 -2026-05-18T00:26:52.543188Z  WARN plugin host finding plugin_id=python subcode=CLA-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:tests.integration.cli.test_instantiate_plugins_value_source._build_yaml_with_model", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "0", "source_byte_end": "3309", "source_byte_start": "2425"} -2026-05-18T00:26:52.543198Z  WARN plugin host finding plugin_id=python subcode=CLA-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:tests.integration.audit.test_pass_through_violation_persists.TestAuditRoundTrip.test_json_extract_returns_per_token_identifiers", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "15", "source_byte_end": "6229", "source_byte_start": "5123"} -2026-05-18T00:26:52.543205Z  WARN plugin host finding plugin_id=python subcode=CLA-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:tests.unit.scripts.cicd.test_adr019_test_inventory.test_positive_fixture_reports_required_finding_kinds", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "1", "source_byte_end": "1642", "source_byte_start": "613"} -2026-05-18T00:26:52.543212Z  WARN plugin host finding plugin_id=python subcode=CLA-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:tests.unit.web.composer.test_tools.TestPreviewPipeline.test_preview_pipeline_suggests_fork_gate_for_duplicate_consumers", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "0", "source_byte_end": "277113", "source_byte_start": "275103"} -2026-05-18T00:26:52.543218Z  WARN plugin host finding plugin_id=python subcode=CLA-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:tests.unit.web.composer.test_tools.TestPreviewPipeline.test_preview_pipeline_suggests_fork_gate_for_duplicate_consumers", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "1", "source_byte_end": "276976", "source_byte_start": "275103"} -2026-05-18T00:26:52.543224Z  WARN plugin host finding plugin_id=python subcode=CLA-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:tests.unit.web.composer.test_tools.TestPreviewPipeline.test_preview_pipeline_suggests_fork_gate_for_duplicate_consumers", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "2", "source_byte_end": "276221", "source_byte_start": "275103"} +2026-05-18T00:26:52.543188Z  WARN plugin host finding plugin_id=python subcode=LMWV-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:tests.integration.cli.test_instantiate_plugins_value_source._build_yaml_with_model", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "0", "source_byte_end": "3309", "source_byte_start": "2425"} +2026-05-18T00:26:52.543198Z  WARN plugin host finding plugin_id=python subcode=LMWV-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:tests.integration.audit.test_pass_through_violation_persists.TestAuditRoundTrip.test_json_extract_returns_per_token_identifiers", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "15", "source_byte_end": "6229", "source_byte_start": "5123"} +2026-05-18T00:26:52.543205Z  WARN plugin host finding plugin_id=python subcode=LMWV-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:tests.unit.scripts.cicd.test_adr019_test_inventory.test_positive_fixture_reports_required_finding_kinds", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "1", "source_byte_end": "1642", "source_byte_start": "613"} +2026-05-18T00:26:52.543212Z  WARN plugin host finding plugin_id=python subcode=LMWV-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:tests.unit.web.composer.test_tools.TestPreviewPipeline.test_preview_pipeline_suggests_fork_gate_for_duplicate_consumers", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "0", "source_byte_end": "277113", "source_byte_start": "275103"} +2026-05-18T00:26:52.543218Z  WARN plugin host finding plugin_id=python subcode=LMWV-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:tests.unit.web.composer.test_tools.TestPreviewPipeline.test_preview_pipeline_suggests_fork_gate_for_duplicate_consumers", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "1", "source_byte_end": "276976", "source_byte_start": "275103"} +2026-05-18T00:26:52.543224Z  WARN plugin host finding plugin_id=python subcode=LMWV-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:tests.unit.web.composer.test_tools.TestPreviewPipeline.test_preview_pipeline_suggests_fork_gate_for_duplicate_consumers", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "2", "source_byte_end": "276221", "source_byte_start": "275103"} 2026-05-18T00:26:56.106206Z ERROR writer-actor rejected insert; failing run plugin_id=python error=InsertEntity for python:function:elspeth.core.landscape.execution_repository.ExecutionRepository.complete_node_state diff --git a/tests/perf/b8_scale_test/results/2026-05-18T0017Z/corpus-copy.txt b/tests/perf/b8_scale_test/results/2026-05-18T0017Z/corpus-copy.txt index ba8d756c..029f26b8 100644 --- a/tests/perf/b8_scale_test/results/2026-05-18T0017Z/corpus-copy.txt +++ b/tests/perf/b8_scale_test/results/2026-05-18T0017Z/corpus-copy.txt @@ -1,1532 +1,1532 @@ -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/conftest.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/codex_exemption_validator.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/check_contracts.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/codex_test_defect_hunt.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/codex_audit_common.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/generate_test_data.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/validate_deployment.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/run_mutation_testing.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/codex_integration_seam_hunt.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/generate_test_bugs.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/examples/chroma_rag/seed_collection.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/evals/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/examples/large_scale_test/generate_data.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/examples/chroma_rag_qa/seed_collection.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/strategies/json.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/strategies/config.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/strategies/binary.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/strategies/settings.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/strategies/external.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/strategies/ids.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/strategies/mutable.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/strategies/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/helpers/coalesce.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/helpers/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/conftest.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/test_field_collision_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/performance/conftest.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/performance/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/fixtures/stores.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/fixtures/base_classes.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/fixtures/plugins.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/fixtures/chaosweb.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/fixtures/landscape.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/fixtures/test_factories.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/fixtures/multi_run.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/fixtures/chaosllm.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/fixtures/factories.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/fixtures/telemetry.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/fixtures/pipeline.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/fixtures/azurite.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/fixtures/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/conftest.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/_helpers.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/test_adr_019_discard_mode_flip.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/test_adr_019_helpers.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/test_adr_019_sweep_durability.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/_adr019_test_plugins.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/test_adr_019_resume_counter_parity.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/test_adr_019_counter_changes.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/test_adr_019_cross_table_invariants.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/conftest.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/test_cli_helpers_sink_factory.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/test_version.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/invariants/conftest.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/invariants/test_contract_non_fire.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/invariants/test_harness_self_test.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/invariants/test_framework_accepts_second_contract.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/invariants/test_pass_through_invariants.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/invariants/test_contract_negative_examples_fire.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/invariants/test_transform_probe_coverage.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/invariants/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/e2e/conftest.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/e2e/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/eval/_backfill_lib.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/eval/backfill_2026_05_03_outputs.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/eval/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/cicd/plugin_hash.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/cicd/assert_redaction_label.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/cicd/bootstrap_redaction_snapshot.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/cicd/enforce_composer_catch_order.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/cicd/enforce_contract_manifest.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/cicd/enforce_gve_attribution.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/cicd/enforce_guard_symmetry.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/cicd/enforce_frozen_annotations.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/cicd/enforce_component_type.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/cicd/enforce_adapter_budget.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/cicd/enforce_plugin_hashes.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/cicd/check_redaction_direction.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/cicd/enforce_options_metadata.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/cicd/enforce_composer_exception_channel.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/cicd/adr019_test_inventory.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/cicd/enforce_tier_1_decoration.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/cicd/enforce_tier_model.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/cicd/enforce_freeze_guards.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/cicd/adr019_symbol_inventory.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/cicd/enforce_audit_evidence_nominal.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/cicd/check_slot_type_cross_language.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/cicd/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/skill_rgr/harness.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/skill_rgr/run.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/cli_formatters.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/cli_helpers.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/cli.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/evals/composer-rgr/score.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/evals/lib/decode_tools.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/evals/lib/scenario_from_example.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/evals/lib/prompt_drafter.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/evals/lib/composer_rgr_score.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/evals/lib/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tools/pdf/postprocess.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tools/pdf/preprocess.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/engine/test_sink_executor_diversion_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/engine/test_sink_routing_invariant.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/engine/test_executor_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/engine/test_token_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/engine/test_aggregation_state_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/engine/test_expression_safety.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/engine/test_declaration_dispatch_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/engine/test_orchestrator_lifecycle_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/engine/test_processor_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/engine/test_clock_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/engine/test_retry_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/engine/test_trigger_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/engine/test_coalesce_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/engine/test_token_lifecycle_state_machine.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/engine/test_processor_coalesce_equivalence_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/engine/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/canonical/test_freeze_hash_equivalence.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/canonical/test_hash_determinism.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/canonical/test_nan_rejection.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/canonical/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/sinks/test_database_sink_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/sinks/test_json_sink_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/sinks/test_csv_sink_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/sinks/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/archive/migrations/2026-01-fix-allowlist-post-ruff-format.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/performance/scalability/test_large_datasets.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/performance/scalability/test_many_sinks.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/performance/scalability/test_deep_dag.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/performance/scalability/test_wide_rows.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/performance/scalability/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/performance/benchmarks/test_schema_validation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/performance/benchmarks/test_canonical_hashing.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/performance/benchmarks/test_cross_check_overhead.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/performance/benchmarks/_deep_size.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/performance/benchmarks/test_validator_walk.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/performance/benchmarks/test_db_write.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/performance/benchmarks/test_throughput.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/performance/benchmarks/test_token_expansion.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/performance/benchmarks/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/performance/stress/conftest.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/performance/stress/test_call_index_concurrency.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/performance/stress/test_concurrent_sinks.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/performance/stress/test_rate_limiter.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/performance/stress/test_backpressure.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/performance/stress/test_llm_retry.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/performance/stress/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/performance/memory/test_leak_detection.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/performance/memory/test_memory_baseline.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/performance/memory/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/cli/test_cli.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/cli/test_instantiate_plugins_value_source.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/cli/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/telemetry/conftest.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/telemetry/test_wiring.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/telemetry/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/integration/test_cross_module_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/integration/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/telemetry/conftest.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/telemetry/test_emit_completeness.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/telemetry/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/audit/test_terminal_states.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/audit/test_nan_infinity_rejection.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/audit/test_recorder_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/audit/test_fork_coalesce_flow.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/audit/test_fork_join_balance.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/audit/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/contracts/test_context_canonical.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/contracts/test_validation_rejection_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/contracts/test_row_result_sink_invariant.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/contracts/test_routing_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/contracts/test_serialization_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/contracts/test_schema_contract_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/contracts/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/plugins/test_schema_coercion_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/plugins/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/sources/test_field_normalization_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/sources/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/core/test_operations_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/core/test_rate_limit_fairness.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/core/test_retention_monotonicity.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/core/test_lineage_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/core/test_row_data_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/core/test_config_function_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/core/test_exporter_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/core/test_checkpoint_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/core/test_payload_store_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/core/test_rate_limiter_state_machine.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/core/test_helpers_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/core/test_rate_limiter_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/core/test_identifiers_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/core/test_landscape_recording_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/core/test_checkpoint_serialization_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/core/test_templates_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/core/test_branch_transform_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/core/test_fingerprint_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/core/test_reproducibility_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/core/test_dag_complex_topologies.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/core/test_connection_name_fuzz.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/core/test_dag_step_map_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/core/test_dag_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/core/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/core/test_formatters_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/skill_rgr/scenarios/batch1_pressured.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/skill_rgr/scenarios/batch1_refactor_override.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/skill_rgr/scenarios/batch1_refactor_insist.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/skill_rgr/scenarios/batch1.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/skill_rgr/scenarios/calibration.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/skill_rgr/scenarios/batch3_contract_loop_insist.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/skill_rgr/scenarios/_refactor_helpers.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/skill_rgr/scenarios/batch1_refactor_incomplete.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/skill_rgr/scenarios/batch3_bootstrap.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/scripts/skill_rgr/scenarios/batch3_contract_loop.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/mcp/test_server_call_tool.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/mcp/test_query_validation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/mcp/test_outcome_analysis.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/mcp/test_type_literals.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/mcp/test_diagnose_quarantine_count.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/mcp/test_contract_tools.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/mcp/test_analyzer_queries.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/mcp/test_arg_validation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/mcp/test_mcp_init.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/mcp/test_diagnostics.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/mcp/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/test_dependencies.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/conftest.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/test_composer_exception_handlers.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/test_async_workers.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/test_app.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/_sync_asgi_client.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/test_paths.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/test_config.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/e2e/examples/test_shipped_examples.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/e2e/examples/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/e2e/external/test_keyvault.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/e2e/external/test_blob_source.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/e2e/external/test_blob_sink.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/e2e/external/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/e2e/recovery/test_partial_failure.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/e2e/recovery/test_concurrent_resume.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/e2e/recovery/test_crash_and_resume.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/e2e/recovery/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/e2e/audit/test_export_reimport.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/e2e/audit/test_attributability.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/e2e/audit/test_purge_integrity.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/e2e/audit/test_full_lineage.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/e2e/audit/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/e2e/pipelines/test_csv_to_csv.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/e2e/pipelines/test_large_pipeline.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/e2e/pipelines/test_multi_output.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/e2e/pipelines/test_json_to_json.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/e2e/pipelines/test_csv_to_database.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/e2e/pipelines/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/test_check_contracts.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/test_validate_deployment.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/regression/test_phase8_sweep_a_truthiness.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/regression/test_phase8_sweep_d_validation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/regression/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/conftest.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_utils.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_validation_integration.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_integration.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_validation_path_agreement.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_discovery.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_assert_to_raise.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_base_source_contract.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_manager.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_post_init_validations.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_protocols.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_manager_singleton.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_hookimpl_registration.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_sink_header_config.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_validation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_node_id_protocol.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_schemas.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_base_signatures.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_context_types.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_base_sink.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_invariant_probe_execution.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_builtin_plugin_metadata.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_base.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_context.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_base_sink_contract.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_results.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_config_base.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_schema_factory.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/evals/test_convergence_scenarios_mocked_llm.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/evals/test_convergence_scenarios.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/evals/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/composer_mcp/test_call_tool_audit.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/composer_mcp/test_audit.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/composer_mcp/test_server.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/composer_mcp/test_session.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/composer_mcp/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/conftest.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_dag_coalesce_optionality.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_identifiers.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_dependency_config.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_payload_store.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_explicit_sink_routing_safeguards.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_canonical.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_operations.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_dag_divert_coalesce.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_templates.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_dag_integrity_guards.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_token_outcomes.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_secrets_config.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_config_single_rejected.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_dag_schema_propagation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_dag.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_dag_contract_validation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_dag_registry.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_connection_name_validation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_edge_validation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_template_extraction_dual.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_logging.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_resolve_secret_refs.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_config_aggregation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_dag_branch_transforms.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_events.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_sink_settings_on_write_failure.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_config_name_uniqueness.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_canonical_mutation_gaps.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_config.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_config_reserved_connection_names.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_config_alignment.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/deployment/test_elspeth_web_service.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_pass_through_violation_persists.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_fixes.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_schema_config_mode_serialization_roundtrip.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_sqlcipher_pipeline.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_recorder_grades.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_declaration_contract_landscape_serialization_roundtrip.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_recorder_contracts.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_recorder_calls.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_recorder_batches.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_recorder_routing_events.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_can_drop_rows_roundtrip.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_sink_required_fields_serialization_roundtrip.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_export.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_source_boundary_orchestrator.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_recorder_row_data.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_recorder_queries.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_recorder_errors.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_recorder_runs.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_recorder_explain.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_recorder_artifacts.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_source_guaranteed_fields_serialization_roundtrip.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_recorder_tokens.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_contract_audit.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_exporter_batch_queries.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_audit_field_separation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_declared_required_fields_serialization_roundtrip.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_not_null_constraints.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_declared_output_fields_serialization_roundtrip.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_csv_sink_executor_audit.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_error_persistence.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_tier1_integrity.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_recorder_nodes.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_recorder_node_states.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/audit/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/contracts/test_build_runtime_consistency.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/contracts/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/plugins/test_type_coerce_value_transform_pipeline.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/plugins/test_dataverse_pipeline.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/plugins/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/config/test_cli_resume_schema_validation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/config/test_config_contract_drift.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/config/test_template_resolver_integration.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/config/test_cli_resume_sink_capability.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/config/test_cli_schema_validation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/config/test_keyvault_fingerprint.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/config/test_schema_validation_end_to_end.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/config/test_schema_validation_integration.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/config/test_schema_validation_regression.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/config/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/core/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/rate_limit/test_integration.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/rate_limit/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/test_composer_llm_eval_characterization.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/test_resume_edge_ids.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/test_concurrency.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/test_retry.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/test_resume_comprehensive.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/test_explicit_sink_routing.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/test_rag_indexed_smoke.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/test_bootstrap_preflight.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/test_deaggregation_example_smoke.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/test_aggregation_recovery.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/test_aggregation_checkpoint_bug.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/test_report_assemble_aggregation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/test_deaggregation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/test_composer_runtime_agreement.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/test_resume_schema_required.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/test_aggregation_contracts.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/web/conftest.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/web/test_audit_readiness_routes.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/web/test_execute_pipeline.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/web/test_inv_audit_ahead_backward.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/web/test_blobs_ready_hash_postgres.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/web/test_compose_loop_latency_sanity.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/web/test_compose_loop_concurrent_sessions.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/web/test_preferences_routes.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/web/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/checkpoint/conftest.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/checkpoint/test_topology_validation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/checkpoint/test_serialization.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/checkpoint/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/checkpoint/test_recovery.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/conftest.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_plugin_detection.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_retry_policy.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_plugin_retryable_error.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_token_manager_pipeline_row.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_failsink_validation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_dependency_resolver.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_coalesce_contract_bug.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_retry.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_expression_parser.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_pass_through_verification.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_sink_executor_diversion.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_coalesce_executor.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_routing_enums.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_triggers.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_state_guard_audit_evidence_discriminator.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_post_init_validations.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_declaration_contract_bootstrap_drift.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_processor.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_declaration_dispatch.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_commencement.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_batch_token_identity.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_flush_dispatcher_routing.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_bootstrap_preflight.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_adr019_phase2_producer_pairs.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_boundary_dispatch_inputs.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_sink_required_fields_contract.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_dag_navigator.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_pass_through_declaration_contract.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_coalesce_pipeline_row.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_orchestrator_registry_bootstrap.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_schema_config_mode_contract.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_transform_success_reason.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_can_drop_rows_contract.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_best_effort.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_tokens.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_executors.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_batch_adapter.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_clock.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_audit_wrapper_scope.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_cross_check_flush_output.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_declared_output_fields_contract.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_record_flush_violation_failure.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_declared_required_fields_contract.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_row_outcome.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_source_guaranteed_fields_contract.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_spans.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_processor_pipeline_row.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/tui/test_node_detail.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/tui/test_lineage_types.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/tui/test_constants.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/tui/test_explain_app.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/tui/test_lineage_tree.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/tui/test_graceful_degradation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/tui/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/cli/test_secrets_loading.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/cli/test_explain_tui.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/cli/test_cli_formatters.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/cli/test_web_command.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/cli/test_execution_result.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/cli/test_explain_command.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/cli/test_error_boundaries.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/cli/test_cli_helpers.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/cli/test_validate_command.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/cli/test_cli.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/cli/test_purge_command.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/cli/test_cli_preflight.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/cli/test_plugins_command.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/cli/test_cli_helpers_db.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/cli/test_plugin_errors.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/cli/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/fixtures/test_wire_transforms.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/telemetry/conftest.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/telemetry/test_contracts.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/telemetry/test_manager.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/telemetry/test_property_based.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/telemetry/test_factory.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/telemetry/test_reentrance.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/telemetry/test_filtering.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/telemetry/test_plugin_wiring.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/telemetry/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_tier_registry_migration.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/conftest.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_routing.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_plugin_semantics.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_telemetry_contracts.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_plugin_semantics_imports.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_context_protocols.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_plugin_roles.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_contract_propagation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_registry_primitive.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_coalesce_enums.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_plugin_protocols.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_discriminated.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_identity.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_plugin_schema.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_enums.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_token_usage.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_diversion.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_call_data.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_transform_result_contract.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_coalesce_metadata.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_audit_evidence.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_sink_protocol_return.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_declaration_contracts.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_type_normalization.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_secrets.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_field_contract.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_error_edge_label.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_require_int.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_audit_evidence_nominal_scanner.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_cross_check_overhead_benchmark_contract.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_diverted_outcome.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_transform_contract.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_checkpoint_post_init.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_compose_propagation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_schema_contract_factory.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_post_init_validations.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_token_ref.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_contract_violation_error.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_composer_llm_audit.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_engine_contracts.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_contract_builder.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_pipeline_row.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_value_source.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_cli.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_new_errors.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_plugin_protocol_fields.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_coalesce_checkpoint.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_contract_violations.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_errors.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_node_state_context.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_gate_result_contract.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_leaf_boundary.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_run_result.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_schema_config_participates.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_probes.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_contract_narrowing.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_freeze.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_schema_config.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_checkpoint.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_audit_protocols.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_telemetry_config.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_source_row_contract.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_runtime_val_manifest.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_plugin_assistance.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_propagation_walkers_agree.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_freeze_regression.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_update_schemas.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_secret_scrub.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_events.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_registry_snapshot_property.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_data.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_hashing.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_results.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_config.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_tier_registry.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_composer_audit.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_record_call_guards.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_plugin_context_recording.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_header_modes.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_audit.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_tier_decoration_scanner.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_schema_contract.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_contract_records.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/testing/pytest_xdist_auto.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/testing/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/bootstrap.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/commencement.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/batch_adapter.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/spans.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/coalesce_executor.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/_best_effort.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/dag_navigator.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/triggers.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/tokens.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/clock.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/dependency_resolver.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/processor.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/retry.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/tui/types.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/tui/explain_app.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/tui/constants.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/tui/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/telemetry/protocols.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/telemetry/filtering.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/telemetry/hookspecs.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/telemetry/errors.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/telemetry/serialization.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/telemetry/circuit_breaker.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/telemetry/factory.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/telemetry/manager.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/telemetry/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/type_normalization.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/data.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/payload_store.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/reorder_primitives.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/pipeline_runner.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/hashing.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/database_url.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/audit.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/runtime_val_manifest.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/results.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/events.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/secrets.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/errors.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/enums.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/contract_builder.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/discriminated.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/schema_contract.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/advisory_locks.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/field_collision.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/guarantee_propagation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/sink.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/transform_contract.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/engine.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/diversion.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/header_modes.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/auth.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/plugin_semantics.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/export_records.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/freeze.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/url.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/plugin_context.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/registry_primitive.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/run_result.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/routing.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/composer_slots.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/token_usage.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/types.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/plugin_roles.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/composer_audit.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/plugin_protocols.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/coalesce_checkpoint.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/plugin_assistance.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/schema_contract_factory.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/cli.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/identity.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/contexts.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/security.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/contract_propagation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/aggregation_checkpoint.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/secret_scrub.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/declaration_contracts.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/audit_protocols.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/schema.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/audit_evidence.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/call_data.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/contract_records.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/coalesce_enums.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/node_state_context.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/value_source.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/coalesce_metadata.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/composer_llm_audit.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/probes.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/checkpoint.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/tier_registry.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/composer_mcp/audit.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/composer_mcp/server.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/composer_mcp/session.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/composer_mcp/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/payload_store.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/operations.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/events.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/secrets.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/dependency_config.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/templates.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/config.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/expression_parser.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/canonical.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/identifiers.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/logging.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/mcp/server.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/mcp/analyzer.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/mcp/types.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/mcp/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/config.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/dependencies.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/async_workers.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/app.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/paths.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/validation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/plugins/llm/test_response_validation_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/plugins/llm/test_multi_query_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/plugins/llm/test_template_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/plugins/llm/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/plugins/sinks/test_azure_blob_sink_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/plugins/sinks/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/plugins/transforms/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/plugins/web_scrape/test_extraction_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/plugins/web_scrape/test_ssrf_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/plugins/web_scrape/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/plugins/sources/test_azure_blob_source_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/plugins/sources/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/plugins/batching/test_reorder_buffer_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/plugins/batching/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/fixtures/cicd/adr019_symbol_inventory/negative.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/fixtures/cicd/adr019_symbol_inventory/positive.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/web/composer/conftest.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/web/composer/strategies.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/web/composer/test_compose_loop_invariants.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/web/composer/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/blobs/test_service.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/blobs/test_routes.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/blobs/test_composition_references_blob.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/blobs/test_protocol.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/blobs/test_schemas.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/blobs/test_sniff.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/blobs/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_count_tool_responses_for_assistant.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_messages_route_include_tool_rows.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_service.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_routes.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_datetime_timezone.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_failed_turn_handlers.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_chat_messages.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_ownership.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_record_audit_grade_view.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_persist_payload.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_schema.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_fork.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_telemetry.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_converters.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_guided_schema_form_validation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_service_construction.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_models.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_protocol.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_schemas.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_static_direct_writers.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_composer_proposals.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_persist_compose_turn.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_composition_states.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_audit_access_log.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/middleware/test_rate_limit.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/middleware/test_request_id.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/middleware/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/audit_readiness/test_service.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/audit_readiness/test_routes.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/audit_readiness/test_boundary_predicate_parity.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/audit_readiness/test_models.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/audit_readiness/test_explain.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/audit_readiness/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/auth/conftest.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/auth/test_entra_provider.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/auth/test_routes.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/auth/test_provider_type_contract.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/auth/test_middleware.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/auth/test_local_provider.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/auth/test_models.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/auth/test_schemas.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/auth/test_oidc_provider.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/auth/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/catalog/test_eager_lowering.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/catalog/test_service.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/catalog/test_routes.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/catalog/test_service_extended_summary.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/catalog/test_schemas_extended.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/catalog/test_service_audit_derivation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/catalog/test_schemas.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/catalog/test_knob_schema_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/catalog/test_knob_schema_golden.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/catalog/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/catalog/test_audit_characteristic_vocabulary_parity.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/preferences/test_service.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/preferences/test_schema.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/preferences/test_models.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/preferences/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/mcp/analyzers/test_contracts.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/mcp/analyzers/test_reports.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/mcp/analyzers/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/secrets/test_server_store.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/secrets/test_service.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/secrets/test_routes.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/secrets/test_user_store.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/secrets/test_schemas.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/secrets/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/conftest.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_redaction_completeness_property.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_prompts.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_promote_set_source_from_blob.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_promote_apply_pipeline_recipe.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_summarizer_contract_property.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_tool_redaction_policy.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_redact_set_source.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_service.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_state_claim_grounding.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_audit_failure_primacy.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_adequacy_guard_runtime_bound.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_audit_arg_error_validation_errors.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_label_gate_direction.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_knob_schema_recipe_adapter.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_anti_anchor.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_tools.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/_adequacy_helpers.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_semantic_validator.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_redact_tool_call_arguments.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_promote_set_source.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_compose_loop_audit_wiring.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_walk_model_schema.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_promote_set_pipeline.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_compose_loop_persistence.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_declarative_manifest_runtime_smoke.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_handles_no_sensitive_data_reason.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_proposals.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_recipe_intent_routing.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_state.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_compose_loop_anti_anchor.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_redaction_telemetry_sanity.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_knob_schema_discriminated.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_schema_contract_enforcement.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_producer_resolver.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_knob_schema.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_progress.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_skill_drift.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_yaml_generator.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_walker_guard_parity.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_compose_loop_tool_call_cap.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_promote_create_blob.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_redaction_telemetry.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_knob_schema_from_model.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_conftest_drift_guard.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_agent_tooling.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_promote_patch_options.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_adequacy_guard.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_recipes.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_label_gate_yaml.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_advisor_tool.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_compose_loop_llm_audit.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_required_paths_walker.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_compose_loop_envelope.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_compose_loop_unknown_tool_name.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_source_inspection.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_compose_loop_test_driver.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_route_integration.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_skills_loader.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_promote_update_blob.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_audit.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_compose_service_structure.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_knob_schema_visible_when.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_tool_redaction_dataclass.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_provider_cache_markers.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_redact_tool_call_response.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_failure_samples.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_service.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_routes.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_run_accounting_schemas.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_runtime_preflight_coordinator.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_outputs_loader.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_progress.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_validation_value_source.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_validation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_errors.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_schemas.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_run_accounting_projection.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_discard_summary.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_outputs_routes.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_websocket.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_preview.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_identity_node_advisory.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_diagnostics.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_preflight_side_effects.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/batching/test_row_reorder_buffer.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/batching/test_batch_transform_mixin.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/batching/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/config/test_tabular_source_config.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/config/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/security/test_url.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/security/test_web_ssrf_network_failures.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/security/test_config_secrets.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/security/test_fingerprint.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/security/test_secret_loader.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/security/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/plugins/transforms/test_rag_pipeline.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/plugins/transforms/test_output_schema_contract.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/plugins/transforms/test_contract.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/plugins/transforms/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/plugins/sources/test_payload_storage.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/plugins/sources/test_trust_boundary.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/plugins/sources/test_contract.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/plugins/sources/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/core/dag/test_output_schema_pipeline.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/core/dag/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/clients/test_http_redirects.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/clients/test_http_telemetry.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/clients/test_llm_error_classification.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/clients/test_replayer.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/clients/test_audited_client_base.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/clients/test_llm_telemetry.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/clients/test_http.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/clients/test_verifier.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/clients/test_audited_http_client.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/clients/test_audited_llm_client.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/clients/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/evals/lib/test_decode_tools.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/evals/lib/test_composer_rgr_score.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/evals/lib/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/dag/test_graph.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/dag/test_failsink_edges.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/dag/test_output_schema_enforcement.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/dag/test_graph_validation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/dag/test_builder_validation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/dag/test_models_post_init.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/dag/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/retention/test_purge.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/retention/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_token_recording.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_model_loaders.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_execution_repository.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_source_file_hash.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_database_sqlcipher.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_exporter.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_recorder_store_payload.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_data_flow_repository.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_database_compatibility_guards.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_run_lifecycle_repository.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_call_recording.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_schema.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_journal.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_node_state_recording.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_database_ops.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_validation_error_noncanonical.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_factory.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_reproducibility.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_data_flow_nan_rejection.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_query_methods.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_run_recording.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_error_serialization_dispatch.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_models_enums.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_batch_recording.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_error_recording.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_models_mutation_gaps.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_preflight_recording.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_lineage.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_formatters.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_where_exactness_consolidated.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_graph_recording.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_row_data.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/rate_limit/test_registry.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/rate_limit/test_limiter.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/rate_limit/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/checkpoint/test_manager.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/checkpoint/test_serialization.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/checkpoint/test_compatibility.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/checkpoint/test_version_validation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/checkpoint/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/core/checkpoint/test_recovery.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/audit/recorder/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/plugins/sinks/test_chroma_sink_pipeline.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/plugins/sinks/test_durability.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/plugins/sinks/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/plugins/llm/conftest.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/plugins/llm/test_multi_query.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/plugins/llm/test_contract_validation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/plugins/llm/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/orchestrator/test_orchestrator_checkpointing.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/orchestrator/test_completed_outcome_timing.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/orchestrator/test_resume_guardrails.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/orchestrator/test_pass_through_flush.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/orchestrator/test_orchestrator_cleanup.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/orchestrator/test_branch_transforms.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/orchestrator/test_graceful_shutdown.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/orchestrator/test_orchestrator_core.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/orchestrator/test_execution_loop.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/orchestrator/test_declaration_contract_aggregate.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/orchestrator/test_quarantine_routing.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/orchestrator/test_sink_diversion_counters.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/orchestrator/test_gate_to_gate_routing.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/orchestrator/test_export_partial_semantics.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/orchestrator/test_t18_characterization.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/orchestrator/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/web/composer/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/web/catalog/test_startup_lowering.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/orchestrator/test_finalize_source_iteration.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/orchestrator/test_outcomes.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/orchestrator/test_graceful_shutdown.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/orchestrator/test_preflight_pipeline_config.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/orchestrator/test_pending_sink_grouping.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/orchestrator/test_resume_failure.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/orchestrator/test_export.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/orchestrator/test_validation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/orchestrator/test_accumulate_diverted.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/orchestrator/test_phase_error_masking.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/orchestrator/test_types.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/orchestrator/test_aggregation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/engine/orchestrator/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/telemetry/exporters/test_console.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/telemetry/exporters/test_datadog_integration.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/telemetry/exporters/test_azure_monitor.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/telemetry/exporters/test_otlp_integration.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/telemetry/exporters/test_datadog.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/telemetry/exporters/test_azure_monitor_integration.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/telemetry/exporters/test_otlp.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/telemetry/exporters/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/sink_contracts/test_csv_sink_contract.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/sink_contracts/test_sink_protocol.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/sink_contracts/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/config/test_runtime_concurrency.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/config/test_runtime_common.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/config/test_runtime_retry.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/config/test_protocols.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/config/test_runtime_rate_limit.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/config/test_alignment.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/config/test_runtime_checkpoint.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/config/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/transform_contracts/test_transform_protocol.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/transform_contracts/test_keyword_filter_contract.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/transform_contracts/_azure_batch_helpers.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/transform_contracts/test_passthrough_contract.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/transform_contracts/test_batch_transform_protocol.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/transform_contracts/test_azure_prompt_shield_contract.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/transform_contracts/test_truncate_contract.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/transform_contracts/test_azure_multi_query_contract.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/transform_contracts/test_azure_content_safety_contract.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/transform_contracts/test_web_scrape_contract.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/transform_contracts/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/source_contracts/test_source_protocol.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/source_contracts/test_csv_source_contract.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/source_contracts/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/eval/test_common_shell.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/eval/test_backfill_lib.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/eval/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/test_enforce_composer_exception_channel.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/test_enforce_options_metadata.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/test_enforce_composer_catch_order.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/test_enforce_contract_manifest.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/test_runtime_preflight_patch_targets.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/test_adr019_symbol_inventory.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/test_enforce_guard_symmetry.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/test_check_slot_type_cross_language.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/test_enforce_frozen_annotations.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/test_plugin_hash.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/test_enforce_tier_model.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/test_enforce_component_type.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/test_enforce_tier_model_dump_edges.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/test_enforce_gve_attribution.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/test_adr019_test_inventory.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/test_enforce_plugin_hashes.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/test_enforce_freeze_guards.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/test_enforce_tier_1_decoration.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sinks/test_sink_display_headers.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sinks/test_sink_bug_fixes.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sinks/test_azure_blob_sink.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sinks/test_database_sink.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sinks/test_csv_sink.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sinks/test_csv_sink_resume.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sinks/test_dataverse_sink.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sinks/test_chroma_sink.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sinks/test_azure_blob_sink_serialization.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sinks/test_json_sink_resume.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sinks/test_database_sink_resume.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sinks/test_json_sink.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sinks/test_sink_schema_validation_common.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sinks/test_csv_sink_append.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sinks/test_csv_sink_headers.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sinks/test_sink_protocol_compliance.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sinks/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sinks/test_chroma_sink_config.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/test_base_metadata.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/test_templates.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/test_telemetry.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/test_divert_row.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/test_build_output_schema_config.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/test_probe_factory.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/test_display_headers.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/test_base_semantics.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/test_determinism_declaration_contract.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/conftest.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_llm_success_reason.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_tracing_integration.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_reorder_buffer.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_llm_config.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_multi_query.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_capacity_errors.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_transform.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_templates.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_provider_protocol.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_azure.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_langfuse_tracer.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_azure_multi_query.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_p1_bug_fixes.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_azure_multi_query_profiling.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_tracing_config.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_openrouter_multi_query.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_config_schema.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_provider_lifecycle.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_provider_azure.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_plugin_registration.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_validation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_prompt_template_contract.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_provider_openrouter.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_pool_config.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_audit_metadata_functions.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_pooled_executor.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_contract_aware_template.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_aimd_throttle.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_azure_multi_query_retry.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_openrouter.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_safety_utils.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_batch_outlier_annotator.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_batch_paired_preference.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_value_transform.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_batch_stats.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_forward_invariant_probes.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_batch_threshold_summary.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_web_scrape.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_batch_data_quality_report.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_web_scrape_fingerprint.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_batch_effect_size.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_batch_experiment_compare.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_batch_distribution_profile.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_json_explode.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_batch_top_k.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_web_scrape_errors.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_batch_drift_compare.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_batch_replicate.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_passthrough.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_field_collision.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_batch_stats_integration.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_keyword_filter.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_web_scrape_extraction.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_batch_replicate_integration.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_type_coerce.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_report_assemble.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_truncate.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_batch_classifier_metrics.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_backward_invariant_probes.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_field_mapper.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_web_scrape_security.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_line_explode.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sources/test_null_source.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sources/test_text_source.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sources/test_csv_source_metadata.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sources/test_field_normalization.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sources/test_json_source.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sources/test_csv_source_contract.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sources/test_declared_guaranteed_fields.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sources/test_dataverse_source.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sources/test_csv_source.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sources/test_azure_blob_source.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sources/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/pooling/test_pool_config.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/pooling/test_executor_retryable_errors.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/pooling/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/sessions/ownership.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/sessions/_persist_payload.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/sessions/_guided_solve_chain.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/sessions/models.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/sessions/routes.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/sessions/engine.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/sessions/schemas.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/sessions/service.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/sessions/_guided_step_chat.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/sessions/protocol.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/sessions/converters.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/sessions/schema.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/sessions/_auto_title.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/sessions/telemetry.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/sessions/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/middleware/request_id.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/middleware/rate_limit.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/middleware/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/audit_readiness/explain.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/audit_readiness/models.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/audit_readiness/routes.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/audit_readiness/service.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/audit_readiness/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/auth/audit.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/auth/models.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/auth/routes.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/auth/middleware.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/auth/local.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/auth/protocol.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/auth/entra.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/auth/oidc.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/auth/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/orchestrator/core.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/orchestrator/export.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/orchestrator/outcomes.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/orchestrator/preflight.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/orchestrator/aggregation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/orchestrator/types.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/orchestrator/validation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/orchestrator/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/executors/gate.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/executors/pass_through.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/executors/sink.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/executors/state_guard.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/executors/source_guaranteed_fields.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/executors/declaration_dispatch.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/executors/sink_required_fields.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/executors/aggregation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/executors/types.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/executors/declared_required_fields.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/executors/declaration_contract_bootstrap.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/executors/schema_config_mode.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/executors/declared_output_fields.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/executors/transform.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/executors/can_drop_rows.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/executors/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/tui/screens/explain_screen.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/tui/screens/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/tui/widgets/lineage_tree.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/tui/widgets/node_detail.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/tui/widgets/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/telemetry/exporters/azure_monitor.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/telemetry/exporters/otlp.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/telemetry/exporters/console.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/telemetry/exporters/datadog.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/telemetry/exporters/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/config/protocols.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/config/runtime.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/config/defaults.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/config/alignment.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/config/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/catalog/routes.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/catalog/knob_schema.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/catalog/schemas.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/catalog/service.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/catalog/protocol.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/catalog/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/preferences/models.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/preferences/routes.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/preferences/service.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/preferences/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/execution/_semantic_helpers.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/execution/accounting.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/execution/preview.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/execution/errors.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/execution/routes.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/execution/preflight.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/execution/schemas.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/execution/service.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/execution/failure_samples.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/execution/diagnostics.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/execution/progress.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/execution/protocol.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/execution/outputs.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/execution/fanout_guard.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/execution/discard_summary.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/execution/runtime_preflight.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/execution/validation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/execution/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/security/config_secrets.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/security/web.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/security/secret_loader.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/security/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/rate_limit/limiter.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/rate_limit/registry.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/rate_limit/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/checkpoint/serialization.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/checkpoint/compatibility.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/checkpoint/manager.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/checkpoint/recovery.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/checkpoint/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/mcp/analyzers/contracts.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/mcp/analyzers/queries.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/mcp/analyzers/diagnostics.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/mcp/analyzers/reports.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/mcp/analyzers/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/secrets/server_store.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/secrets/routes.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/secrets/schemas.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/secrets/service.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/secrets/ref_policy.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/secrets/user_store.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/secrets/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/audit.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/recipes.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/yaml_generator.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/state.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/proposals.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/_semantic_validator.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/service.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/_producer_resolver.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/recipe_intent_routing.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/progress.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/tools.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/protocol.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/redaction.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/state_claim_grounding.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/source_inspection.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/prompts.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/redaction_telemetry.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/anti_anchor.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/blobs/routes.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/blobs/schemas.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/blobs/service.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/blobs/protocol.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/blobs/sniff.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/blobs/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/sinks/azure_blob_sink.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/sinks/chroma_sink.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/sinks/database_sink.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/sinks/dataverse.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/sinks/csv_sink.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/sinks/json_sink.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/sinks/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/discovery.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/probe_factory.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/hookspecs.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/results.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/templates.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/base.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/preflight.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/display_headers.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/azure_auth.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/sentinels.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/utils.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/manager.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/output_paths.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/config_base.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/schema_factory.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/telemetry.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/validation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/passthrough.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/batch_distribution_profile.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/batch_replicate.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/batch_effect_size.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/batch_top_k.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/batch_threshold_summary.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/batch_experiment_compare.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/batch_stats.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/report_assemble.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/batch_outlier_annotator.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/batch_classifier_metrics.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/keyword_filter.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/_scalar_buckets.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/web_scrape_fingerprint.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/safety_utils.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/truncate.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/web_scrape_errors.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/value_transform.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/batch_paired_preference.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/json_explode.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/web_scrape_extraction.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/type_coerce.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/batch_data_quality_report.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/web_scrape.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/field_mapper.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/batch_drift_compare.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/line_explode.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/sources/azure_blob_source.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/sources/null_source.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/sources/dataverse.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/sources/text_source.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/sources/json_source.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/sources/field_normalization.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/sources/csv_source.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/sources/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/dag/models.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/dag/builder.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/dag/graph.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/dag/coalesce_merge.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/dag/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/retention/purge.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/retention/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/execution_repository.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/_helpers.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/database.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/errors.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/exporter.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/query_repository.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/lineage.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/data_flow_repository.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/journal.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/factory.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/_database_ops.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/row_data.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/run_lifecycle_repository.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/model_loaders.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/reproducibility.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/schema.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/formatters.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/auth_audit_repository.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/plugins/transforms/azure/test_azure_safety_properties.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/property/plugins/transforms/azure/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/guided/test_chat_solver.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/guided/test_skill.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/guided/test_protocol.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/guided/test_state_machine.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/guided/test_mode_transition_prompt.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/guided/test_state_field.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/guided/test_recipe_match.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/guided/test_emitters.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/guided/test_audit.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/guided/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/web/composer/guided/conftest.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/web/composer/guided/test_respond.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/web/composer/guided/test_step_handlers.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/web/composer/guided/test_step_chat.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/web/composer/guided/test_step1_prefill.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/web/composer/guided/test_progressive_disclosure.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/web/composer/guided/test_step_3_e2e.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/web/composer/guided/test_default_guided.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/web/composer/guided/test_hidden_field_rejection.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/web/composer/guided/test_chain_solver.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/web/composer/guided/test_error_paths.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/web/composer/guided/test_auto_drop.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/web/composer/guided/test_get_guided.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/web/composer/guided/test_audit_emission.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/web/composer/guided/test_fixture_smoke.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/integration/web/composer/guided/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/azure/test_auth.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/azure/test_blob_sink_resume.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/azure/test_blob_source.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/azure/test_blob_sink.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/azure/test_content_safety.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/azure/test_prompt_shield.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/azure/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/clients/test_fingerprinting.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/clients/test_dataverse_client.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/clients/test_http_allowed_ranges.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/clients/test_json_utils.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/clients/test_http_call_return.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/clients/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/llm/test_value_sources.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/llm/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/rag/test_transform.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/rag/test_query.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/rag/test_formatter.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/rag/test_config.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/rag/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/llm/templates.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/llm/langfuse.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/llm/base.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/llm/model_catalog.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/llm/multi_query.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/llm/tracing.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/llm/transform.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/llm/provider.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/llm/validation.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/llm/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/rag/config.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/rag/query.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/rag/formatter.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/rag/transform.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/rag/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/azure/errors.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/azure/base.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/azure/content_safety.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/azure/prompt_shield.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/azure/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/guided/chat_solver.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/guided/audit.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/guided/errors.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/guided/steps.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/guided/recipe_match.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/guided/emitters.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/guided/chain_solver.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/guided/state_machine.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/guided/protocol.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/guided/prompts.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/guided/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/skills/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/pooling/executor.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/pooling/config.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/pooling/reorder_buffer.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/pooling/throttle.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/pooling/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/batching/ports.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/batching/row_reorder_buffer.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/batching/mixin.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/batching/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/clients/fingerprinting.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/clients/base.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/clients/http.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/clients/dataverse.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/clients/json_utils.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/clients/llm.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/clients/replayer.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/clients/verifier.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/clients/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/clients/retrieval/test_chroma.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/clients/retrieval/test_types.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/clients/retrieval/test_connection.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/clients/retrieval/test_azure_search.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/clients/retrieval/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/llm/providers/openrouter.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/llm/providers/azure.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/llm/providers/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/guided/skills/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/clients/retrieval/azure_search.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/clients/retrieval/connection.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/clients/retrieval/base.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/clients/retrieval/types.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/clients/retrieval/chroma.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/clients/retrieval/__init__.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/frontend/node_modules/flatted/python/flatted.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/frontend/node_modules/katex/src/metrics/extract_ttfs.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/frontend/node_modules/katex/src/metrics/parse_tfm.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/frontend/node_modules/katex/src/metrics/format_json.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/frontend/node_modules/katex/src/metrics/extract_tfms.py -/tmp/clarion-b8-elspeth-full-20260518T0016Z/src/elspeth/web/frontend/node_modules/katex/src/fonts/generate_fonts.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/conftest.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/codex_exemption_validator.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/check_contracts.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/codex_test_defect_hunt.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/codex_audit_common.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/generate_test_data.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/validate_deployment.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/run_mutation_testing.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/codex_integration_seam_hunt.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/generate_test_bugs.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/examples/chroma_rag/seed_collection.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/evals/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/examples/large_scale_test/generate_data.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/examples/chroma_rag_qa/seed_collection.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/strategies/json.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/strategies/config.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/strategies/binary.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/strategies/settings.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/strategies/external.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/strategies/ids.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/strategies/mutable.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/strategies/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/helpers/coalesce.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/helpers/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/conftest.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/test_field_collision_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/performance/conftest.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/performance/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/fixtures/stores.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/fixtures/base_classes.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/fixtures/plugins.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/fixtures/chaosweb.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/fixtures/landscape.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/fixtures/test_factories.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/fixtures/multi_run.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/fixtures/chaosllm.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/fixtures/factories.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/fixtures/telemetry.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/fixtures/pipeline.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/fixtures/azurite.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/fixtures/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/conftest.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/_helpers.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/test_adr_019_discard_mode_flip.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/test_adr_019_helpers.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/test_adr_019_sweep_durability.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/_adr019_test_plugins.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/test_adr_019_resume_counter_parity.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/test_adr_019_counter_changes.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/test_adr_019_cross_table_invariants.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/conftest.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/test_cli_helpers_sink_factory.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/test_version.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/invariants/conftest.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/invariants/test_contract_non_fire.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/invariants/test_harness_self_test.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/invariants/test_framework_accepts_second_contract.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/invariants/test_pass_through_invariants.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/invariants/test_contract_negative_examples_fire.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/invariants/test_transform_probe_coverage.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/invariants/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/e2e/conftest.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/e2e/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/eval/_backfill_lib.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/eval/backfill_2026_05_03_outputs.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/eval/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/cicd/plugin_hash.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/cicd/assert_redaction_label.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/cicd/bootstrap_redaction_snapshot.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/cicd/enforce_composer_catch_order.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/cicd/enforce_contract_manifest.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/cicd/enforce_gve_attribution.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/cicd/enforce_guard_symmetry.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/cicd/enforce_frozen_annotations.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/cicd/enforce_component_type.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/cicd/enforce_adapter_budget.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/cicd/enforce_plugin_hashes.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/cicd/check_redaction_direction.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/cicd/enforce_options_metadata.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/cicd/enforce_composer_exception_channel.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/cicd/adr019_test_inventory.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/cicd/enforce_tier_1_decoration.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/cicd/enforce_tier_model.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/cicd/enforce_freeze_guards.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/cicd/adr019_symbol_inventory.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/cicd/enforce_audit_evidence_nominal.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/cicd/check_slot_type_cross_language.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/cicd/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/skill_rgr/harness.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/skill_rgr/run.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/cli_formatters.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/cli_helpers.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/cli.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/evals/composer-rgr/score.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/evals/lib/decode_tools.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/evals/lib/scenario_from_example.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/evals/lib/prompt_drafter.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/evals/lib/composer_rgr_score.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/evals/lib/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tools/pdf/postprocess.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tools/pdf/preprocess.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/engine/test_sink_executor_diversion_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/engine/test_sink_routing_invariant.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/engine/test_executor_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/engine/test_token_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/engine/test_aggregation_state_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/engine/test_expression_safety.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/engine/test_declaration_dispatch_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/engine/test_orchestrator_lifecycle_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/engine/test_processor_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/engine/test_clock_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/engine/test_retry_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/engine/test_trigger_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/engine/test_coalesce_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/engine/test_token_lifecycle_state_machine.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/engine/test_processor_coalesce_equivalence_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/engine/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/canonical/test_freeze_hash_equivalence.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/canonical/test_hash_determinism.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/canonical/test_nan_rejection.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/canonical/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/sinks/test_database_sink_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/sinks/test_json_sink_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/sinks/test_csv_sink_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/sinks/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/archive/migrations/2026-01-fix-allowlist-post-ruff-format.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/performance/scalability/test_large_datasets.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/performance/scalability/test_many_sinks.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/performance/scalability/test_deep_dag.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/performance/scalability/test_wide_rows.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/performance/scalability/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/performance/benchmarks/test_schema_validation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/performance/benchmarks/test_canonical_hashing.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/performance/benchmarks/test_cross_check_overhead.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/performance/benchmarks/_deep_size.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/performance/benchmarks/test_validator_walk.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/performance/benchmarks/test_db_write.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/performance/benchmarks/test_throughput.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/performance/benchmarks/test_token_expansion.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/performance/benchmarks/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/performance/stress/conftest.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/performance/stress/test_call_index_concurrency.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/performance/stress/test_concurrent_sinks.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/performance/stress/test_rate_limiter.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/performance/stress/test_backpressure.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/performance/stress/test_llm_retry.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/performance/stress/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/performance/memory/test_leak_detection.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/performance/memory/test_memory_baseline.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/performance/memory/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/cli/test_cli.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/cli/test_instantiate_plugins_value_source.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/cli/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/telemetry/conftest.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/telemetry/test_wiring.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/telemetry/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/integration/test_cross_module_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/integration/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/telemetry/conftest.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/telemetry/test_emit_completeness.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/telemetry/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/audit/test_terminal_states.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/audit/test_nan_infinity_rejection.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/audit/test_recorder_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/audit/test_fork_coalesce_flow.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/audit/test_fork_join_balance.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/audit/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/contracts/test_context_canonical.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/contracts/test_validation_rejection_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/contracts/test_row_result_sink_invariant.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/contracts/test_routing_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/contracts/test_serialization_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/contracts/test_schema_contract_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/contracts/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/plugins/test_schema_coercion_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/plugins/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/sources/test_field_normalization_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/sources/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/core/test_operations_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/core/test_rate_limit_fairness.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/core/test_retention_monotonicity.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/core/test_lineage_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/core/test_row_data_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/core/test_config_function_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/core/test_exporter_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/core/test_checkpoint_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/core/test_payload_store_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/core/test_rate_limiter_state_machine.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/core/test_helpers_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/core/test_rate_limiter_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/core/test_identifiers_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/core/test_landscape_recording_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/core/test_checkpoint_serialization_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/core/test_templates_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/core/test_branch_transform_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/core/test_fingerprint_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/core/test_reproducibility_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/core/test_dag_complex_topologies.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/core/test_connection_name_fuzz.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/core/test_dag_step_map_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/core/test_dag_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/core/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/core/test_formatters_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/skill_rgr/scenarios/batch1_pressured.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/skill_rgr/scenarios/batch1_refactor_override.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/skill_rgr/scenarios/batch1_refactor_insist.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/skill_rgr/scenarios/batch1.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/skill_rgr/scenarios/calibration.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/skill_rgr/scenarios/batch3_contract_loop_insist.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/skill_rgr/scenarios/_refactor_helpers.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/skill_rgr/scenarios/batch1_refactor_incomplete.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/skill_rgr/scenarios/batch3_bootstrap.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/scripts/skill_rgr/scenarios/batch3_contract_loop.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/mcp/test_server_call_tool.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/mcp/test_query_validation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/mcp/test_outcome_analysis.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/mcp/test_type_literals.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/mcp/test_diagnose_quarantine_count.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/mcp/test_contract_tools.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/mcp/test_analyzer_queries.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/mcp/test_arg_validation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/mcp/test_mcp_init.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/mcp/test_diagnostics.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/mcp/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/test_dependencies.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/conftest.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/test_composer_exception_handlers.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/test_async_workers.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/test_app.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/_sync_asgi_client.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/test_paths.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/test_config.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/e2e/examples/test_shipped_examples.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/e2e/examples/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/e2e/external/test_keyvault.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/e2e/external/test_blob_source.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/e2e/external/test_blob_sink.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/e2e/external/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/e2e/recovery/test_partial_failure.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/e2e/recovery/test_concurrent_resume.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/e2e/recovery/test_crash_and_resume.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/e2e/recovery/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/e2e/audit/test_export_reimport.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/e2e/audit/test_attributability.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/e2e/audit/test_purge_integrity.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/e2e/audit/test_full_lineage.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/e2e/audit/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/e2e/pipelines/test_csv_to_csv.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/e2e/pipelines/test_large_pipeline.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/e2e/pipelines/test_multi_output.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/e2e/pipelines/test_json_to_json.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/e2e/pipelines/test_csv_to_database.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/e2e/pipelines/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/test_check_contracts.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/test_validate_deployment.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/regression/test_phase8_sweep_a_truthiness.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/regression/test_phase8_sweep_d_validation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/regression/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/conftest.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_utils.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_validation_integration.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_integration.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_validation_path_agreement.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_discovery.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_assert_to_raise.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_base_source_contract.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_manager.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_post_init_validations.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_protocols.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_manager_singleton.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_hookimpl_registration.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_sink_header_config.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_validation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_node_id_protocol.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_schemas.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_base_signatures.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_context_types.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_base_sink.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_invariant_probe_execution.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_builtin_plugin_metadata.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_base.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_context.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_base_sink_contract.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_results.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_config_base.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/test_schema_factory.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/evals/test_convergence_scenarios_mocked_llm.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/evals/test_convergence_scenarios.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/evals/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/composer_mcp/test_call_tool_audit.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/composer_mcp/test_audit.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/composer_mcp/test_server.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/composer_mcp/test_session.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/composer_mcp/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/conftest.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_dag_coalesce_optionality.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_identifiers.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_dependency_config.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_payload_store.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_explicit_sink_routing_safeguards.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_canonical.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_operations.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_dag_divert_coalesce.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_templates.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_dag_integrity_guards.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_token_outcomes.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_secrets_config.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_config_single_rejected.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_dag_schema_propagation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_dag.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_dag_contract_validation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_dag_registry.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_connection_name_validation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_edge_validation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_template_extraction_dual.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_logging.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_resolve_secret_refs.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_config_aggregation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_dag_branch_transforms.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_events.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_sink_settings_on_write_failure.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_config_name_uniqueness.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_canonical_mutation_gaps.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_config.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_config_reserved_connection_names.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/test_config_alignment.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/deployment/test_elspeth_web_service.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_pass_through_violation_persists.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_fixes.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_schema_config_mode_serialization_roundtrip.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_sqlcipher_pipeline.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_recorder_grades.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_declaration_contract_landscape_serialization_roundtrip.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_recorder_contracts.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_recorder_calls.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_recorder_batches.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_recorder_routing_events.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_can_drop_rows_roundtrip.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_sink_required_fields_serialization_roundtrip.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_export.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_source_boundary_orchestrator.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_recorder_row_data.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_recorder_queries.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_recorder_errors.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_recorder_runs.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_recorder_explain.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_recorder_artifacts.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_source_guaranteed_fields_serialization_roundtrip.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_recorder_tokens.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_contract_audit.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_exporter_batch_queries.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_audit_field_separation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_declared_required_fields_serialization_roundtrip.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_not_null_constraints.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_declared_output_fields_serialization_roundtrip.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_csv_sink_executor_audit.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_error_persistence.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_tier1_integrity.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_recorder_nodes.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/audit/test_recorder_node_states.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/audit/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/contracts/test_build_runtime_consistency.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/contracts/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/plugins/test_type_coerce_value_transform_pipeline.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/plugins/test_dataverse_pipeline.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/plugins/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/config/test_cli_resume_schema_validation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/config/test_config_contract_drift.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/config/test_template_resolver_integration.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/config/test_cli_resume_sink_capability.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/config/test_cli_schema_validation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/config/test_keyvault_fingerprint.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/config/test_schema_validation_end_to_end.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/config/test_schema_validation_integration.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/config/test_schema_validation_regression.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/config/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/core/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/rate_limit/test_integration.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/rate_limit/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/test_composer_llm_eval_characterization.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/test_resume_edge_ids.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/test_concurrency.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/test_retry.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/test_resume_comprehensive.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/test_explicit_sink_routing.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/test_rag_indexed_smoke.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/test_bootstrap_preflight.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/test_deaggregation_example_smoke.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/test_aggregation_recovery.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/test_aggregation_checkpoint_bug.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/test_report_assemble_aggregation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/test_deaggregation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/test_composer_runtime_agreement.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/test_resume_schema_required.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/test_aggregation_contracts.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/web/conftest.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/web/test_audit_readiness_routes.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/web/test_execute_pipeline.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/web/test_inv_audit_ahead_backward.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/web/test_blobs_ready_hash_postgres.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/web/test_compose_loop_latency_sanity.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/web/test_compose_loop_concurrent_sessions.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/web/test_preferences_routes.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/web/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/checkpoint/conftest.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/checkpoint/test_topology_validation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/checkpoint/test_serialization.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/checkpoint/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/checkpoint/test_recovery.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/conftest.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_plugin_detection.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_retry_policy.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_plugin_retryable_error.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_token_manager_pipeline_row.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_failsink_validation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_dependency_resolver.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_coalesce_contract_bug.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_retry.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_expression_parser.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_pass_through_verification.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_sink_executor_diversion.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_coalesce_executor.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_routing_enums.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_triggers.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_state_guard_audit_evidence_discriminator.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_post_init_validations.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_declaration_contract_bootstrap_drift.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_processor.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_declaration_dispatch.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_commencement.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_batch_token_identity.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_flush_dispatcher_routing.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_bootstrap_preflight.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_adr019_phase2_producer_pairs.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_boundary_dispatch_inputs.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_sink_required_fields_contract.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_dag_navigator.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_pass_through_declaration_contract.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_coalesce_pipeline_row.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_orchestrator_registry_bootstrap.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_schema_config_mode_contract.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_transform_success_reason.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_can_drop_rows_contract.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_best_effort.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_tokens.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_executors.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_batch_adapter.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_clock.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_audit_wrapper_scope.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_cross_check_flush_output.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_declared_output_fields_contract.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_record_flush_violation_failure.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_declared_required_fields_contract.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_row_outcome.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_source_guaranteed_fields_contract.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_spans.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/test_processor_pipeline_row.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/tui/test_node_detail.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/tui/test_lineage_types.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/tui/test_constants.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/tui/test_explain_app.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/tui/test_lineage_tree.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/tui/test_graceful_degradation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/tui/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/cli/test_secrets_loading.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/cli/test_explain_tui.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/cli/test_cli_formatters.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/cli/test_web_command.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/cli/test_execution_result.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/cli/test_explain_command.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/cli/test_error_boundaries.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/cli/test_cli_helpers.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/cli/test_validate_command.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/cli/test_cli.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/cli/test_purge_command.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/cli/test_cli_preflight.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/cli/test_plugins_command.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/cli/test_cli_helpers_db.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/cli/test_plugin_errors.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/cli/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/fixtures/test_wire_transforms.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/telemetry/conftest.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/telemetry/test_contracts.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/telemetry/test_manager.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/telemetry/test_property_based.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/telemetry/test_factory.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/telemetry/test_reentrance.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/telemetry/test_filtering.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/telemetry/test_plugin_wiring.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/telemetry/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_tier_registry_migration.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/conftest.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_routing.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_plugin_semantics.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_telemetry_contracts.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_plugin_semantics_imports.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_context_protocols.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_plugin_roles.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_contract_propagation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_registry_primitive.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_coalesce_enums.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_plugin_protocols.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_discriminated.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_identity.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_plugin_schema.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_enums.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_token_usage.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_diversion.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_call_data.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_transform_result_contract.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_coalesce_metadata.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_audit_evidence.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_sink_protocol_return.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_declaration_contracts.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_type_normalization.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_secrets.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_field_contract.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_error_edge_label.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_require_int.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_audit_evidence_nominal_scanner.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_cross_check_overhead_benchmark_contract.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_diverted_outcome.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_transform_contract.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_checkpoint_post_init.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_compose_propagation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_schema_contract_factory.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_post_init_validations.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_token_ref.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_contract_violation_error.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_composer_llm_audit.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_engine_contracts.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_contract_builder.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_pipeline_row.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_value_source.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_cli.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_new_errors.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_plugin_protocol_fields.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_coalesce_checkpoint.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_contract_violations.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_errors.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_node_state_context.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_gate_result_contract.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_leaf_boundary.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_run_result.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_schema_config_participates.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_probes.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_contract_narrowing.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_freeze.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_schema_config.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_checkpoint.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_audit_protocols.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_telemetry_config.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_source_row_contract.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_runtime_val_manifest.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_plugin_assistance.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_propagation_walkers_agree.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_freeze_regression.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_update_schemas.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_secret_scrub.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_events.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_registry_snapshot_property.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_data.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_hashing.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_results.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_config.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_tier_registry.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_composer_audit.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_record_call_guards.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_plugin_context_recording.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_header_modes.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_audit.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_tier_decoration_scanner.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_schema_contract.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/test_contract_records.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/testing/pytest_xdist_auto.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/testing/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/bootstrap.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/commencement.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/batch_adapter.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/spans.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/coalesce_executor.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/_best_effort.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/dag_navigator.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/triggers.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/tokens.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/clock.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/dependency_resolver.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/processor.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/retry.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/tui/types.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/tui/explain_app.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/tui/constants.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/tui/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/telemetry/protocols.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/telemetry/filtering.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/telemetry/hookspecs.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/telemetry/errors.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/telemetry/serialization.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/telemetry/circuit_breaker.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/telemetry/factory.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/telemetry/manager.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/telemetry/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/type_normalization.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/data.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/payload_store.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/reorder_primitives.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/pipeline_runner.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/hashing.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/database_url.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/audit.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/runtime_val_manifest.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/results.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/events.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/secrets.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/errors.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/enums.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/contract_builder.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/discriminated.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/schema_contract.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/advisory_locks.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/field_collision.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/guarantee_propagation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/sink.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/transform_contract.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/engine.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/diversion.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/header_modes.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/auth.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/plugin_semantics.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/export_records.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/freeze.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/url.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/plugin_context.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/registry_primitive.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/run_result.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/routing.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/composer_slots.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/token_usage.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/types.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/plugin_roles.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/composer_audit.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/plugin_protocols.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/coalesce_checkpoint.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/plugin_assistance.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/schema_contract_factory.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/cli.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/identity.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/contexts.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/security.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/contract_propagation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/aggregation_checkpoint.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/secret_scrub.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/declaration_contracts.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/audit_protocols.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/schema.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/audit_evidence.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/call_data.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/contract_records.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/coalesce_enums.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/node_state_context.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/value_source.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/coalesce_metadata.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/composer_llm_audit.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/probes.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/checkpoint.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/tier_registry.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/composer_mcp/audit.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/composer_mcp/server.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/composer_mcp/session.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/composer_mcp/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/payload_store.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/operations.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/events.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/secrets.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/dependency_config.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/templates.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/config.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/expression_parser.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/canonical.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/identifiers.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/logging.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/mcp/server.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/mcp/analyzer.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/mcp/types.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/mcp/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/config.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/dependencies.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/async_workers.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/app.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/paths.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/validation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/plugins/llm/test_response_validation_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/plugins/llm/test_multi_query_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/plugins/llm/test_template_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/plugins/llm/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/plugins/sinks/test_azure_blob_sink_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/plugins/sinks/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/plugins/transforms/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/plugins/web_scrape/test_extraction_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/plugins/web_scrape/test_ssrf_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/plugins/web_scrape/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/plugins/sources/test_azure_blob_source_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/plugins/sources/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/plugins/batching/test_reorder_buffer_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/plugins/batching/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/fixtures/cicd/adr019_symbol_inventory/negative.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/fixtures/cicd/adr019_symbol_inventory/positive.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/web/composer/conftest.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/web/composer/strategies.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/web/composer/test_compose_loop_invariants.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/web/composer/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/blobs/test_service.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/blobs/test_routes.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/blobs/test_composition_references_blob.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/blobs/test_protocol.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/blobs/test_schemas.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/blobs/test_sniff.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/blobs/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_count_tool_responses_for_assistant.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_messages_route_include_tool_rows.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_service.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_routes.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_datetime_timezone.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_failed_turn_handlers.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_chat_messages.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_ownership.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_record_audit_grade_view.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_persist_payload.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_schema.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_fork.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_telemetry.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_converters.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_guided_schema_form_validation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_service_construction.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_models.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_protocol.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_schemas.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_static_direct_writers.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_composer_proposals.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_persist_compose_turn.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_composition_states.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/test_audit_access_log.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/sessions/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/middleware/test_rate_limit.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/middleware/test_request_id.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/middleware/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/audit_readiness/test_service.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/audit_readiness/test_routes.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/audit_readiness/test_boundary_predicate_parity.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/audit_readiness/test_models.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/audit_readiness/test_explain.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/audit_readiness/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/auth/conftest.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/auth/test_entra_provider.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/auth/test_routes.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/auth/test_provider_type_contract.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/auth/test_middleware.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/auth/test_local_provider.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/auth/test_models.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/auth/test_schemas.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/auth/test_oidc_provider.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/auth/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/catalog/test_eager_lowering.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/catalog/test_service.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/catalog/test_routes.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/catalog/test_service_extended_summary.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/catalog/test_schemas_extended.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/catalog/test_service_audit_derivation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/catalog/test_schemas.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/catalog/test_knob_schema_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/catalog/test_knob_schema_golden.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/catalog/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/catalog/test_audit_characteristic_vocabulary_parity.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/preferences/test_service.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/preferences/test_schema.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/preferences/test_models.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/preferences/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/mcp/analyzers/test_contracts.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/mcp/analyzers/test_reports.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/mcp/analyzers/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/secrets/test_server_store.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/secrets/test_service.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/secrets/test_routes.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/secrets/test_user_store.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/secrets/test_schemas.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/secrets/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/conftest.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_redaction_completeness_property.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_prompts.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_promote_set_source_from_blob.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_promote_apply_pipeline_recipe.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_summarizer_contract_property.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_tool_redaction_policy.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_redact_set_source.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_service.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_state_claim_grounding.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_audit_failure_primacy.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_adequacy_guard_runtime_bound.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_audit_arg_error_validation_errors.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_label_gate_direction.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_knob_schema_recipe_adapter.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_anti_anchor.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_tools.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/_adequacy_helpers.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_semantic_validator.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_redact_tool_call_arguments.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_promote_set_source.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_compose_loop_audit_wiring.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_walk_model_schema.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_promote_set_pipeline.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_compose_loop_persistence.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_declarative_manifest_runtime_smoke.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_handles_no_sensitive_data_reason.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_proposals.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_recipe_intent_routing.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_state.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_compose_loop_anti_anchor.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_redaction_telemetry_sanity.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_knob_schema_discriminated.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_schema_contract_enforcement.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_producer_resolver.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_knob_schema.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_progress.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_skill_drift.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_yaml_generator.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_walker_guard_parity.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_compose_loop_tool_call_cap.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_promote_create_blob.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_redaction_telemetry.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_knob_schema_from_model.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_conftest_drift_guard.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_agent_tooling.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_promote_patch_options.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_adequacy_guard.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_recipes.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_label_gate_yaml.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_advisor_tool.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_compose_loop_llm_audit.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_required_paths_walker.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_compose_loop_envelope.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_compose_loop_unknown_tool_name.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_source_inspection.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_compose_loop_test_driver.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_route_integration.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_skills_loader.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_promote_update_blob.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_audit.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_compose_service_structure.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_knob_schema_visible_when.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_tool_redaction_dataclass.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_provider_cache_markers.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/test_redact_tool_call_response.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_failure_samples.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_service.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_routes.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_run_accounting_schemas.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_runtime_preflight_coordinator.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_outputs_loader.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_progress.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_validation_value_source.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_validation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_errors.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_schemas.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_run_accounting_projection.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_discard_summary.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_outputs_routes.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_websocket.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_preview.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_identity_node_advisory.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_diagnostics.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/test_preflight_side_effects.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/execution/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/batching/test_row_reorder_buffer.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/batching/test_batch_transform_mixin.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/batching/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/config/test_tabular_source_config.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/config/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/security/test_url.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/security/test_web_ssrf_network_failures.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/security/test_config_secrets.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/security/test_fingerprint.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/security/test_secret_loader.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/security/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/plugins/transforms/test_rag_pipeline.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/plugins/transforms/test_output_schema_contract.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/plugins/transforms/test_contract.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/plugins/transforms/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/plugins/sources/test_payload_storage.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/plugins/sources/test_trust_boundary.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/plugins/sources/test_contract.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/plugins/sources/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/core/dag/test_output_schema_pipeline.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/core/dag/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/clients/test_http_redirects.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/clients/test_http_telemetry.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/clients/test_llm_error_classification.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/clients/test_replayer.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/clients/test_audited_client_base.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/clients/test_llm_telemetry.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/clients/test_http.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/clients/test_verifier.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/clients/test_audited_http_client.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/clients/test_audited_llm_client.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/clients/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/evals/lib/test_decode_tools.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/evals/lib/test_composer_rgr_score.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/evals/lib/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/dag/test_graph.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/dag/test_failsink_edges.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/dag/test_output_schema_enforcement.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/dag/test_graph_validation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/dag/test_builder_validation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/dag/test_models_post_init.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/dag/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/retention/test_purge.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/retention/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_token_recording.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_model_loaders.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_execution_repository.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_source_file_hash.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_database_sqlcipher.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_exporter.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_recorder_store_payload.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_data_flow_repository.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_database_compatibility_guards.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_run_lifecycle_repository.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_call_recording.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_schema.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_journal.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_node_state_recording.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_database_ops.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_validation_error_noncanonical.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_factory.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_reproducibility.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_data_flow_nan_rejection.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_query_methods.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_run_recording.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_error_serialization_dispatch.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_models_enums.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_batch_recording.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_error_recording.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_models_mutation_gaps.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_preflight_recording.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_lineage.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_formatters.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_where_exactness_consolidated.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_graph_recording.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/test_row_data.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/landscape/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/rate_limit/test_registry.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/rate_limit/test_limiter.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/rate_limit/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/checkpoint/test_manager.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/checkpoint/test_serialization.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/checkpoint/test_compatibility.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/checkpoint/test_version_validation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/checkpoint/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/core/checkpoint/test_recovery.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/audit/recorder/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/plugins/sinks/test_chroma_sink_pipeline.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/plugins/sinks/test_durability.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/plugins/sinks/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/plugins/llm/conftest.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/plugins/llm/test_multi_query.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/plugins/llm/test_contract_validation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/plugins/llm/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/orchestrator/test_orchestrator_checkpointing.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/orchestrator/test_completed_outcome_timing.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/orchestrator/test_resume_guardrails.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/orchestrator/test_pass_through_flush.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/orchestrator/test_orchestrator_cleanup.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/orchestrator/test_branch_transforms.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/orchestrator/test_graceful_shutdown.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/orchestrator/test_orchestrator_core.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/orchestrator/test_execution_loop.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/orchestrator/test_declaration_contract_aggregate.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/orchestrator/test_quarantine_routing.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/orchestrator/test_sink_diversion_counters.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/orchestrator/test_gate_to_gate_routing.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/orchestrator/test_export_partial_semantics.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/orchestrator/test_t18_characterization.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/pipeline/orchestrator/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/web/composer/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/web/catalog/test_startup_lowering.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/orchestrator/test_finalize_source_iteration.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/orchestrator/test_outcomes.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/orchestrator/test_graceful_shutdown.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/orchestrator/test_preflight_pipeline_config.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/orchestrator/test_pending_sink_grouping.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/orchestrator/test_resume_failure.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/orchestrator/test_export.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/orchestrator/test_validation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/orchestrator/test_accumulate_diverted.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/orchestrator/test_phase_error_masking.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/orchestrator/test_types.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/orchestrator/test_aggregation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/engine/orchestrator/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/telemetry/exporters/test_console.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/telemetry/exporters/test_datadog_integration.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/telemetry/exporters/test_azure_monitor.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/telemetry/exporters/test_otlp_integration.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/telemetry/exporters/test_datadog.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/telemetry/exporters/test_azure_monitor_integration.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/telemetry/exporters/test_otlp.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/telemetry/exporters/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/sink_contracts/test_csv_sink_contract.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/sink_contracts/test_sink_protocol.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/sink_contracts/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/config/test_runtime_concurrency.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/config/test_runtime_common.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/config/test_runtime_retry.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/config/test_protocols.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/config/test_runtime_rate_limit.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/config/test_alignment.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/config/test_runtime_checkpoint.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/config/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/transform_contracts/test_transform_protocol.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/transform_contracts/test_keyword_filter_contract.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/transform_contracts/_azure_batch_helpers.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/transform_contracts/test_passthrough_contract.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/transform_contracts/test_batch_transform_protocol.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/transform_contracts/test_azure_prompt_shield_contract.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/transform_contracts/test_truncate_contract.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/transform_contracts/test_azure_multi_query_contract.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/transform_contracts/test_azure_content_safety_contract.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/transform_contracts/test_web_scrape_contract.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/transform_contracts/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/source_contracts/test_source_protocol.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/source_contracts/test_csv_source_contract.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/contracts/source_contracts/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/eval/test_common_shell.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/eval/test_backfill_lib.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/eval/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/test_enforce_composer_exception_channel.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/test_enforce_options_metadata.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/test_enforce_composer_catch_order.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/test_enforce_contract_manifest.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/test_runtime_preflight_patch_targets.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/test_adr019_symbol_inventory.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/test_enforce_guard_symmetry.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/test_check_slot_type_cross_language.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/test_enforce_frozen_annotations.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/test_plugin_hash.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/test_enforce_tier_model.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/test_enforce_component_type.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/test_enforce_tier_model_dump_edges.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/test_enforce_gve_attribution.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/test_adr019_test_inventory.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/test_enforce_plugin_hashes.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/test_enforce_freeze_guards.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/test_enforce_tier_1_decoration.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/scripts/cicd/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sinks/test_sink_display_headers.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sinks/test_sink_bug_fixes.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sinks/test_azure_blob_sink.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sinks/test_database_sink.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sinks/test_csv_sink.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sinks/test_csv_sink_resume.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sinks/test_dataverse_sink.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sinks/test_chroma_sink.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sinks/test_azure_blob_sink_serialization.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sinks/test_json_sink_resume.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sinks/test_database_sink_resume.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sinks/test_json_sink.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sinks/test_sink_schema_validation_common.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sinks/test_csv_sink_append.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sinks/test_csv_sink_headers.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sinks/test_sink_protocol_compliance.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sinks/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sinks/test_chroma_sink_config.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/test_base_metadata.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/test_templates.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/test_telemetry.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/test_divert_row.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/test_build_output_schema_config.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/test_probe_factory.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/test_display_headers.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/test_base_semantics.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/test_determinism_declaration_contract.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/conftest.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_llm_success_reason.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_tracing_integration.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_reorder_buffer.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_llm_config.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_multi_query.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_capacity_errors.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_transform.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_templates.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_provider_protocol.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_azure.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_langfuse_tracer.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_azure_multi_query.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_p1_bug_fixes.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_azure_multi_query_profiling.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_tracing_config.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_openrouter_multi_query.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_config_schema.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_provider_lifecycle.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_provider_azure.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_plugin_registration.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_validation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_prompt_template_contract.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_provider_openrouter.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_pool_config.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_audit_metadata_functions.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_pooled_executor.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_contract_aware_template.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_aimd_throttle.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_azure_multi_query_retry.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/test_openrouter.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/llm/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_safety_utils.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_batch_outlier_annotator.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_batch_paired_preference.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_value_transform.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_batch_stats.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_forward_invariant_probes.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_batch_threshold_summary.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_web_scrape.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_batch_data_quality_report.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_web_scrape_fingerprint.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_batch_effect_size.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_batch_experiment_compare.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_batch_distribution_profile.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_json_explode.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_batch_top_k.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_web_scrape_errors.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_batch_drift_compare.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_batch_replicate.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_passthrough.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_field_collision.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_batch_stats_integration.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_keyword_filter.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_web_scrape_extraction.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_batch_replicate_integration.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_type_coerce.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_report_assemble.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_truncate.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_batch_classifier_metrics.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_backward_invariant_probes.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_field_mapper.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_web_scrape_security.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/test_line_explode.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sources/test_null_source.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sources/test_text_source.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sources/test_csv_source_metadata.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sources/test_field_normalization.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sources/test_json_source.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sources/test_csv_source_contract.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sources/test_declared_guaranteed_fields.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sources/test_dataverse_source.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sources/test_csv_source.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sources/test_azure_blob_source.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/sources/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/pooling/test_pool_config.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/pooling/test_executor_retryable_errors.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/pooling/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/sessions/ownership.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/sessions/_persist_payload.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/sessions/_guided_solve_chain.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/sessions/models.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/sessions/routes.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/sessions/engine.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/sessions/schemas.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/sessions/service.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/sessions/_guided_step_chat.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/sessions/protocol.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/sessions/converters.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/sessions/schema.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/sessions/_auto_title.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/sessions/telemetry.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/sessions/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/middleware/request_id.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/middleware/rate_limit.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/middleware/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/audit_readiness/explain.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/audit_readiness/models.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/audit_readiness/routes.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/audit_readiness/service.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/audit_readiness/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/auth/audit.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/auth/models.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/auth/routes.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/auth/middleware.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/auth/local.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/auth/protocol.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/auth/entra.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/auth/oidc.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/auth/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/orchestrator/core.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/orchestrator/export.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/orchestrator/outcomes.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/orchestrator/preflight.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/orchestrator/aggregation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/orchestrator/types.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/orchestrator/validation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/orchestrator/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/executors/gate.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/executors/pass_through.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/executors/sink.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/executors/state_guard.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/executors/source_guaranteed_fields.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/executors/declaration_dispatch.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/executors/sink_required_fields.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/executors/aggregation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/executors/types.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/executors/declared_required_fields.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/executors/declaration_contract_bootstrap.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/executors/schema_config_mode.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/executors/declared_output_fields.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/executors/transform.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/executors/can_drop_rows.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/engine/executors/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/tui/screens/explain_screen.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/tui/screens/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/tui/widgets/lineage_tree.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/tui/widgets/node_detail.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/tui/widgets/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/telemetry/exporters/azure_monitor.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/telemetry/exporters/otlp.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/telemetry/exporters/console.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/telemetry/exporters/datadog.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/telemetry/exporters/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/config/protocols.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/config/runtime.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/config/defaults.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/config/alignment.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/contracts/config/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/catalog/routes.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/catalog/knob_schema.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/catalog/schemas.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/catalog/service.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/catalog/protocol.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/catalog/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/preferences/models.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/preferences/routes.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/preferences/service.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/preferences/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/execution/_semantic_helpers.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/execution/accounting.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/execution/preview.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/execution/errors.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/execution/routes.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/execution/preflight.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/execution/schemas.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/execution/service.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/execution/failure_samples.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/execution/diagnostics.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/execution/progress.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/execution/protocol.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/execution/outputs.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/execution/fanout_guard.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/execution/discard_summary.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/execution/runtime_preflight.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/execution/validation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/execution/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/security/config_secrets.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/security/web.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/security/secret_loader.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/security/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/rate_limit/limiter.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/rate_limit/registry.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/rate_limit/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/checkpoint/serialization.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/checkpoint/compatibility.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/checkpoint/manager.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/checkpoint/recovery.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/checkpoint/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/mcp/analyzers/contracts.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/mcp/analyzers/queries.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/mcp/analyzers/diagnostics.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/mcp/analyzers/reports.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/mcp/analyzers/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/secrets/server_store.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/secrets/routes.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/secrets/schemas.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/secrets/service.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/secrets/ref_policy.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/secrets/user_store.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/secrets/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/audit.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/recipes.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/yaml_generator.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/state.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/proposals.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/_semantic_validator.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/service.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/_producer_resolver.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/recipe_intent_routing.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/progress.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/tools.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/protocol.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/redaction.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/state_claim_grounding.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/source_inspection.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/prompts.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/redaction_telemetry.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/anti_anchor.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/blobs/routes.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/blobs/schemas.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/blobs/service.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/blobs/protocol.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/blobs/sniff.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/blobs/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/sinks/azure_blob_sink.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/sinks/chroma_sink.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/sinks/database_sink.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/sinks/dataverse.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/sinks/csv_sink.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/sinks/json_sink.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/sinks/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/discovery.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/probe_factory.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/hookspecs.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/results.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/templates.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/base.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/preflight.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/display_headers.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/azure_auth.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/sentinels.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/utils.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/manager.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/output_paths.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/config_base.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/schema_factory.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/telemetry.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/validation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/passthrough.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/batch_distribution_profile.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/batch_replicate.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/batch_effect_size.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/batch_top_k.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/batch_threshold_summary.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/batch_experiment_compare.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/batch_stats.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/report_assemble.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/batch_outlier_annotator.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/batch_classifier_metrics.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/keyword_filter.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/_scalar_buckets.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/web_scrape_fingerprint.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/safety_utils.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/truncate.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/web_scrape_errors.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/value_transform.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/batch_paired_preference.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/json_explode.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/web_scrape_extraction.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/type_coerce.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/batch_data_quality_report.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/web_scrape.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/field_mapper.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/batch_drift_compare.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/line_explode.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/sources/azure_blob_source.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/sources/null_source.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/sources/dataverse.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/sources/text_source.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/sources/json_source.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/sources/field_normalization.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/sources/csv_source.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/sources/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/dag/models.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/dag/builder.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/dag/graph.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/dag/coalesce_merge.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/dag/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/retention/purge.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/retention/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/execution_repository.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/_helpers.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/database.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/errors.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/exporter.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/query_repository.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/lineage.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/data_flow_repository.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/journal.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/factory.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/_database_ops.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/row_data.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/run_lifecycle_repository.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/model_loaders.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/reproducibility.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/schema.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/formatters.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/auth_audit_repository.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/core/landscape/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/plugins/transforms/azure/test_azure_safety_properties.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/property/plugins/transforms/azure/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/guided/test_chat_solver.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/guided/test_skill.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/guided/test_protocol.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/guided/test_state_machine.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/guided/test_mode_transition_prompt.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/guided/test_state_field.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/guided/test_recipe_match.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/guided/test_emitters.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/guided/test_audit.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/web/composer/guided/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/web/composer/guided/conftest.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/web/composer/guided/test_respond.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/web/composer/guided/test_step_handlers.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/web/composer/guided/test_step_chat.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/web/composer/guided/test_step1_prefill.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/web/composer/guided/test_progressive_disclosure.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/web/composer/guided/test_step_3_e2e.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/web/composer/guided/test_default_guided.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/web/composer/guided/test_hidden_field_rejection.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/web/composer/guided/test_chain_solver.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/web/composer/guided/test_error_paths.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/web/composer/guided/test_auto_drop.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/web/composer/guided/test_get_guided.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/web/composer/guided/test_audit_emission.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/web/composer/guided/test_fixture_smoke.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/integration/web/composer/guided/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/azure/test_auth.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/azure/test_blob_sink_resume.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/azure/test_blob_source.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/azure/test_blob_sink.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/azure/test_content_safety.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/azure/test_prompt_shield.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/azure/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/clients/test_fingerprinting.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/clients/test_dataverse_client.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/clients/test_http_allowed_ranges.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/clients/test_json_utils.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/clients/test_http_call_return.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/clients/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/llm/test_value_sources.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/llm/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/rag/test_transform.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/rag/test_query.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/rag/test_formatter.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/rag/test_config.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/transforms/rag/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/llm/templates.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/llm/langfuse.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/llm/base.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/llm/model_catalog.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/llm/multi_query.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/llm/tracing.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/llm/transform.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/llm/provider.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/llm/validation.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/llm/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/rag/config.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/rag/query.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/rag/formatter.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/rag/transform.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/rag/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/azure/errors.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/azure/base.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/azure/content_safety.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/azure/prompt_shield.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/azure/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/guided/chat_solver.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/guided/audit.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/guided/errors.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/guided/steps.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/guided/recipe_match.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/guided/emitters.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/guided/chain_solver.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/guided/state_machine.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/guided/protocol.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/guided/prompts.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/guided/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/skills/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/pooling/executor.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/pooling/config.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/pooling/reorder_buffer.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/pooling/throttle.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/pooling/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/batching/ports.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/batching/row_reorder_buffer.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/batching/mixin.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/batching/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/clients/fingerprinting.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/clients/base.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/clients/http.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/clients/dataverse.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/clients/json_utils.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/clients/llm.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/clients/replayer.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/clients/verifier.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/clients/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/clients/retrieval/test_chroma.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/clients/retrieval/test_types.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/clients/retrieval/test_connection.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/clients/retrieval/test_azure_search.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/tests/unit/plugins/infrastructure/clients/retrieval/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/llm/providers/openrouter.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/llm/providers/azure.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/transforms/llm/providers/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/composer/guided/skills/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/clients/retrieval/azure_search.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/clients/retrieval/connection.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/clients/retrieval/base.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/clients/retrieval/types.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/clients/retrieval/chroma.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/plugins/infrastructure/clients/retrieval/__init__.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/frontend/node_modules/flatted/python/flatted.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/frontend/node_modules/katex/src/metrics/extract_ttfs.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/frontend/node_modules/katex/src/metrics/parse_tfm.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/frontend/node_modules/katex/src/metrics/format_json.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/frontend/node_modules/katex/src/metrics/extract_tfms.py +/tmp/loomweave-b8-elspeth-full-20260518T0016Z/src/elspeth/web/frontend/node_modules/katex/src/fonts/generate_fonts.py diff --git a/tests/perf/b8_scale_test/results/2026-05-18T0017Z/extract-db-metrics.py b/tests/perf/b8_scale_test/results/2026-05-18T0017Z/extract-db-metrics.py index 9be0b353..1684bd5a 100644 --- a/tests/perf/b8_scale_test/results/2026-05-18T0017Z/extract-db-metrics.py +++ b/tests/perf/b8_scale_test/results/2026-05-18T0017Z/extract-db-metrics.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Extract analyze-time measurements from clarion.db for the B.8 memo.""" +"""Extract analyze-time measurements from loomweave.db for the B.8 memo.""" from __future__ import annotations @@ -12,7 +12,7 @@ def main() -> int: if len(sys.argv) != 3: - print("usage: extract-metrics.py ") + print("usage: extract-metrics.py ") return 2 db_path = Path(sys.argv[1]) out_path = Path(sys.argv[2]) diff --git a/tests/perf/b8_scale_test/results/2026-05-18T0114Z/analyze-metrics.json b/tests/perf/b8_scale_test/results/2026-05-18T0114Z/analyze-metrics.json index fbaea5d9..7861b897 100644 --- a/tests/perf/b8_scale_test/results/2026-05-18T0114Z/analyze-metrics.json +++ b/tests/perf/b8_scale_test/results/2026-05-18T0114Z/analyze-metrics.json @@ -1,8 +1,8 @@ { "command": [ - "/home/john/clarion/target/release/clarion", + "/home/john/loomweave/target/release/loomweave", "analyze", - "/tmp/clarion-b8-elspeth-full-20260518T0016Z" + "/tmp/loomweave-b8-elspeth-full-20260518T0016Z" ], "peak_rss_bytes": 197865472, "peak_rss_mb": 188.699, diff --git a/tests/perf/b8_scale_test/results/2026-05-18T0114Z/analyze-with-rss.py b/tests/perf/b8_scale_test/results/2026-05-18T0114Z/analyze-with-rss.py index e44df106..60ab67d3 100644 --- a/tests/perf/b8_scale_test/results/2026-05-18T0114Z/analyze-with-rss.py +++ b/tests/perf/b8_scale_test/results/2026-05-18T0114Z/analyze-with-rss.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Run clarion analyze and sample peak RSS for B.8 measurement. +"""Run loomweave analyze and sample peak RSS for B.8 measurement. Mirrors the analyze-metrics.json schema from the 2026-05-17 B.8 run. """ @@ -48,13 +48,13 @@ def _rss_bytes_for_tree(root_pid: int) -> int: def main() -> int: if len(sys.argv) < 3: - print("usage: analyze-with-rss.py [args...]") + print("usage: analyze-with-rss.py [args...]") return 2 output = Path(sys.argv[1]) cmd = sys.argv[2:] env = os.environ.copy() - plugin_bin_dir = "/home/john/clarion/plugins/python/.venv/bin" + plugin_bin_dir = "/home/john/loomweave/plugins/python/.venv/bin" env["PATH"] = plugin_bin_dir + ":" + env.get("PATH", "") start = time.monotonic() diff --git a/tests/perf/b8_scale_test/results/2026-05-18T0114Z/analyze.stdout b/tests/perf/b8_scale_test/results/2026-05-18T0114Z/analyze.stdout index 8f12b1f7..ead420b1 100644 --- a/tests/perf/b8_scale_test/results/2026-05-18T0114Z/analyze.stdout +++ b/tests/perf/b8_scale_test/results/2026-05-18T0114Z/analyze.stdout @@ -1,12 +1,12 @@ -2026-05-18T01:51:06.155391Z  INFO discovered plugin plugin_id=python executable=/home/john/clarion/plugins/python/.venv/bin/clarion-plugin-python +2026-05-18T01:51:06.155391Z  INFO discovered plugin plugin_id=python executable=/home/john/loomweave/plugins/python/.venv/bin/loomweave-plugin-python 2026-05-18T01:51:06.168911Z  INFO source tree walk complete file_count=1526 2026-05-18T01:51:06.169049Z  INFO processing plugin plugin_id=python file_count=1526 2026-05-18T01:59:03.194694Z  WARN plugin host collected findings plugin_id=python finding_count=6 -2026-05-18T01:59:03.194720Z  WARN plugin host finding plugin_id=python subcode=CLA-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:tests.integration.cli.test_instantiate_plugins_value_source._build_yaml_with_model", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "0", "source_byte_end": "3309", "source_byte_start": "2425"} -2026-05-18T01:59:03.194730Z  WARN plugin host finding plugin_id=python subcode=CLA-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:tests.integration.audit.test_pass_through_violation_persists.TestAuditRoundTrip.test_json_extract_returns_per_token_identifiers", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "15", "source_byte_end": "6229", "source_byte_start": "5123"} -2026-05-18T01:59:03.194738Z  WARN plugin host finding plugin_id=python subcode=CLA-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:tests.unit.scripts.cicd.test_adr019_test_inventory.test_positive_fixture_reports_required_finding_kinds", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "1", "source_byte_end": "1642", "source_byte_start": "613"} -2026-05-18T01:59:03.194745Z  WARN plugin host finding plugin_id=python subcode=CLA-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:tests.unit.web.composer.test_tools.TestPreviewPipeline.test_preview_pipeline_suggests_fork_gate_for_duplicate_consumers", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "0", "source_byte_end": "277113", "source_byte_start": "275103"} -2026-05-18T01:59:03.194751Z  WARN plugin host finding plugin_id=python subcode=CLA-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:tests.unit.web.composer.test_tools.TestPreviewPipeline.test_preview_pipeline_suggests_fork_gate_for_duplicate_consumers", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "1", "source_byte_end": "276976", "source_byte_start": "275103"} -2026-05-18T01:59:03.194757Z  WARN plugin host finding plugin_id=python subcode=CLA-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:tests.unit.web.composer.test_tools.TestPreviewPipeline.test_preview_pipeline_suggests_fork_gate_for_duplicate_consumers", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "2", "source_byte_end": "276221", "source_byte_start": "275103"} +2026-05-18T01:59:03.194720Z  WARN plugin host finding plugin_id=python subcode=LMWV-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:tests.integration.cli.test_instantiate_plugins_value_source._build_yaml_with_model", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "0", "source_byte_end": "3309", "source_byte_start": "2425"} +2026-05-18T01:59:03.194730Z  WARN plugin host finding plugin_id=python subcode=LMWV-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:tests.integration.audit.test_pass_through_violation_persists.TestAuditRoundTrip.test_json_extract_returns_per_token_identifiers", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "15", "source_byte_end": "6229", "source_byte_start": "5123"} +2026-05-18T01:59:03.194738Z  WARN plugin host finding plugin_id=python subcode=LMWV-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:tests.unit.scripts.cicd.test_adr019_test_inventory.test_positive_fixture_reports_required_finding_kinds", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "1", "source_byte_end": "1642", "source_byte_start": "613"} +2026-05-18T01:59:03.194745Z  WARN plugin host finding plugin_id=python subcode=LMWV-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:tests.unit.web.composer.test_tools.TestPreviewPipeline.test_preview_pipeline_suggests_fork_gate_for_duplicate_consumers", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "0", "source_byte_end": "277113", "source_byte_start": "275103"} +2026-05-18T01:59:03.194751Z  WARN plugin host finding plugin_id=python subcode=LMWV-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:tests.unit.web.composer.test_tools.TestPreviewPipeline.test_preview_pipeline_suggests_fork_gate_for_duplicate_consumers", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "1", "source_byte_end": "276976", "source_byte_start": "275103"} +2026-05-18T01:59:03.194757Z  WARN plugin host finding plugin_id=python subcode=LMWV-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:tests.unit.web.composer.test_tools.TestPreviewPipeline.test_preview_pipeline_suggests_fork_gate_for_duplicate_consumers", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "2", "source_byte_end": "276221", "source_byte_start": "275103"} 2026-05-18T01:59:10.585548Z  INFO plugin complete plugin_id=python entity_count=33250 edge_count=93963 analyze complete: run 3461ded9-5ba9-4b44-9f28-dd7dab7028d4 completed (33250 entities, 93963 edges) diff --git a/tests/perf/b8_scale_test/results/2026-05-18T0114Z/mcp-driver-output-live.json b/tests/perf/b8_scale_test/results/2026-05-18T0114Z/mcp-driver-output-live.json index abc3811b..5e5f595d 100644 --- a/tests/perf/b8_scale_test/results/2026-05-18T0114Z/mcp-driver-output-live.json +++ b/tests/perf/b8_scale_test/results/2026-05-18T0114Z/mcp-driver-output-live.json @@ -1,6 +1,6 @@ { - "clarion_bin": "/home/john/clarion/target/release/clarion", - "config": "/tmp/clarion-b8-elspeth-full-20260518T0016Z/clarion-b8-live.yaml", + "loomweave_bin": "/home/john/loomweave/target/release/loomweave", + "config": "/tmp/loomweave-b8-elspeth-full-20260518T0016Z/loomweave-b8-live.yaml", "generated_at_unix": 1779071021, "initialize": { "id": "init", @@ -11,7 +11,7 @@ }, "protocolVersion": "2025-11-25", "serverInfo": { - "name": "clarion", + "name": "loomweave", "version": "0.1.0-dev" } } @@ -1085,7 +1085,7 @@ "tool": "callers_of" } ], - "project": "/tmp/clarion-b8-elspeth-full-20260518T0016Z", + "project": "/tmp/loomweave-b8-elspeth-full-20260518T0016Z", "records": [ { "cache_hit": null, diff --git a/tests/perf/b8_scale_test/results/2026-05-18T0114Z/mcp-driver-output-storage-backed.json b/tests/perf/b8_scale_test/results/2026-05-18T0114Z/mcp-driver-output-storage-backed.json index 320eea1b..81cdc4d0 100644 --- a/tests/perf/b8_scale_test/results/2026-05-18T0114Z/mcp-driver-output-storage-backed.json +++ b/tests/perf/b8_scale_test/results/2026-05-18T0114Z/mcp-driver-output-storage-backed.json @@ -1,6 +1,6 @@ { - "clarion_bin": "/home/john/clarion/target/release/clarion", - "config": "/tmp/clarion-b8-elspeth-full-20260518T0016Z/clarion-b8-live.yaml", + "loomweave_bin": "/home/john/loomweave/target/release/loomweave", + "config": "/tmp/loomweave-b8-elspeth-full-20260518T0016Z/loomweave-b8-live.yaml", "generated_at_unix": 1779070001, "initialize": { "id": "init", @@ -11,7 +11,7 @@ }, "protocolVersion": "2025-11-25", "serverInfo": { - "name": "clarion", + "name": "loomweave", "version": "0.1.0-dev" } } @@ -1030,7 +1030,7 @@ "tool": "callers_of" } ], - "project": "/tmp/clarion-b8-elspeth-full-20260518T0016Z", + "project": "/tmp/loomweave-b8-elspeth-full-20260518T0016Z", "records": [ { "cache_hit": null, diff --git a/tests/perf/b8_scale_test/results/2026-05-18T0114Z/scratch.txt b/tests/perf/b8_scale_test/results/2026-05-18T0114Z/scratch.txt index c3a8a0de..febb843a 100644 --- a/tests/perf/b8_scale_test/results/2026-05-18T0114Z/scratch.txt +++ b/tests/perf/b8_scale_test/results/2026-05-18T0114Z/scratch.txt @@ -1 +1 @@ -/tmp/clarion-b8-fix2-tvEsI6 +/tmp/loomweave-b8-fix2-tvEsI6 diff --git a/tests/perf/b8_scale_test/results/2026-05-18T1138Z-phase3/analyze-metrics.json b/tests/perf/b8_scale_test/results/2026-05-18T1138Z-phase3/analyze-metrics.json index 6506288a..625f07b2 100644 --- a/tests/perf/b8_scale_test/results/2026-05-18T1138Z-phase3/analyze-metrics.json +++ b/tests/perf/b8_scale_test/results/2026-05-18T1138Z-phase3/analyze-metrics.json @@ -1,8 +1,8 @@ { "command": [ - "/home/john/clarion/target/release/clarion", + "/home/john/loomweave/target/release/loomweave", "analyze", - "/tmp/clarion-b8-elspeth-full-20260518T0016Z" + "/tmp/loomweave-b8-elspeth-full-20260518T0016Z" ], "peak_rss_bytes": 207720448, "peak_rss_mb": 198.098, diff --git a/tests/perf/b8_scale_test/results/2026-05-18T1138Z-phase3/analyze.stdout b/tests/perf/b8_scale_test/results/2026-05-18T1138Z-phase3/analyze.stdout index e4aef916..7f442365 100644 --- a/tests/perf/b8_scale_test/results/2026-05-18T1138Z-phase3/analyze.stdout +++ b/tests/perf/b8_scale_test/results/2026-05-18T1138Z-phase3/analyze.stdout @@ -1,13 +1,13 @@ -2026-05-18T11:48:06.603083Z INFO discovered plugin plugin_id=python executable=/home/john/clarion/plugins/python/.venv/bin/clarion-plugin-python +2026-05-18T11:48:06.603083Z INFO discovered plugin plugin_id=python executable=/home/john/loomweave/plugins/python/.venv/bin/loomweave-plugin-python 2026-05-18T11:48:06.757233Z INFO source tree walk complete file_count=1526 2026-05-18T11:48:06.757406Z INFO processing plugin plugin_id=python file_count=1526 2026-05-18T11:54:15.353451Z WARN plugin host collected findings plugin_id=python finding_count=6 -2026-05-18T11:54:15.353483Z WARN plugin host finding plugin_id=python subcode=CLA-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:tests.integration.cli.test_instantiate_plugins_value_source._build_yaml_with_model", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "0", "source_byte_end": "3309", "source_byte_start": "2425"} -2026-05-18T11:54:15.353492Z WARN plugin host finding plugin_id=python subcode=CLA-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:tests.integration.audit.test_pass_through_violation_persists.TestAuditRoundTrip.test_json_extract_returns_per_token_identifiers", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "15", "source_byte_end": "6229", "source_byte_start": "5123"} -2026-05-18T11:54:15.353497Z WARN plugin host finding plugin_id=python subcode=CLA-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:tests.unit.scripts.cicd.test_adr019_test_inventory.test_positive_fixture_reports_required_finding_kinds", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "1", "source_byte_end": "1642", "source_byte_start": "613"} -2026-05-18T11:54:15.353503Z WARN plugin host finding plugin_id=python subcode=CLA-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:tests.unit.web.composer.test_tools.TestPreviewPipeline.test_preview_pipeline_suggests_fork_gate_for_duplicate_consumers", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "0", "source_byte_end": "277113", "source_byte_start": "275103"} -2026-05-18T11:54:15.353508Z WARN plugin host finding plugin_id=python subcode=CLA-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:tests.unit.web.composer.test_tools.TestPreviewPipeline.test_preview_pipeline_suggests_fork_gate_for_duplicate_consumers", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "1", "source_byte_end": "276976", "source_byte_start": "275103"} -2026-05-18T11:54:15.353512Z WARN plugin host finding plugin_id=python subcode=CLA-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:tests.unit.web.composer.test_tools.TestPreviewPipeline.test_preview_pipeline_suggests_fork_gate_for_duplicate_consumers", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "2", "source_byte_end": "276221", "source_byte_start": "275103"} +2026-05-18T11:54:15.353483Z WARN plugin host finding plugin_id=python subcode=LMWV-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:tests.integration.cli.test_instantiate_plugins_value_source._build_yaml_with_model", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "0", "source_byte_end": "3309", "source_byte_start": "2425"} +2026-05-18T11:54:15.353492Z WARN plugin host finding plugin_id=python subcode=LMWV-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:tests.integration.audit.test_pass_through_violation_persists.TestAuditRoundTrip.test_json_extract_returns_per_token_identifiers", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "15", "source_byte_end": "6229", "source_byte_start": "5123"} +2026-05-18T11:54:15.353497Z WARN plugin host finding plugin_id=python subcode=LMWV-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:tests.unit.scripts.cicd.test_adr019_test_inventory.test_positive_fixture_reports_required_finding_kinds", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "1", "source_byte_end": "1642", "source_byte_start": "613"} +2026-05-18T11:54:15.353503Z WARN plugin host finding plugin_id=python subcode=LMWV-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:tests.unit.web.composer.test_tools.TestPreviewPipeline.test_preview_pipeline_suggests_fork_gate_for_duplicate_consumers", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "0", "source_byte_end": "277113", "source_byte_start": "275103"} +2026-05-18T11:54:15.353508Z WARN plugin host finding plugin_id=python subcode=LMWV-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:tests.unit.web.composer.test_tools.TestPreviewPipeline.test_preview_pipeline_suggests_fork_gate_for_duplicate_consumers", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "1", "source_byte_end": "276976", "source_byte_start": "275103"} +2026-05-18T11:54:15.353512Z WARN plugin host finding plugin_id=python subcode=LMWV-INFRA-PLUGIN-MALFORMED-UNRESOLVED-CALL-SITE plugin emitted malformed unresolved call site: callee_expr exceeds 512 bytes metadata={"caller_entity_id": "python:function:tests.unit.web.composer.test_tools.TestPreviewPipeline.test_preview_pipeline_suggests_fork_gate_for_duplicate_consumers", "reason": "callee_expr exceeds 512 bytes", "site_ordinal": "2", "source_byte_end": "276221", "source_byte_start": "275103"} 2026-05-18T11:54:23.822339Z INFO plugin complete plugin_id=python entity_count=33250 edge_count=107568 2026-05-18T11:54:24.104686Z INFO phase3 emitted weak-modularity finding run_id=5bfeacdd-b93e-4a02-b974-978c3d645bb2 analyze complete: run 5bfeacdd-b93e-4a02-b974-978c3d645bb2 completed (33350 entities, 108690 edges) diff --git a/tests/perf/b8_scale_test/results/2026-05-18T1138Z-phase3/corpus-provenance.md b/tests/perf/b8_scale_test/results/2026-05-18T1138Z-phase3/corpus-provenance.md index 45dd22d9..c258fa13 100644 --- a/tests/perf/b8_scale_test/results/2026-05-18T1138Z-phase3/corpus-provenance.md +++ b/tests/perf/b8_scale_test/results/2026-05-18T1138Z-phase3/corpus-provenance.md @@ -3,7 +3,7 @@ The 2026-05-18T1138Z Phase 3 perf run used the B.8 elspeth corpus at: ```text -/tmp/clarion-b8-elspeth-full-20260518T0016Z +/tmp/loomweave-b8-elspeth-full-20260518T0016Z ``` That temporary directory is not committed. The committed provenance from the @@ -21,8 +21,8 @@ ignored virtualenv or frontend dependency trees. ```bash bash tests/perf/b8_scale_test/derive-elspeth-corpus.sh \ /path/to/elspeth \ - /tmp/clarion-b8-elspeth-corpus-$(date -u +%Y%m%dT%H%M%SZ) + /tmp/loomweave-b8-elspeth-corpus-$(date -u +%Y%m%dT%H%M%SZ) ``` -Then use the emitted output directory as the `clarion install --path` and -`clarion analyze` target. +Then use the emitted output directory as the `loomweave install --path` and +`loomweave analyze` target. diff --git a/tests/perf/b8_scale_test/results/2026-05-18T1138Z-phase3/phase3-results.md b/tests/perf/b8_scale_test/results/2026-05-18T1138Z-phase3/phase3-results.md index 34c84465..59c7058d 100644 --- a/tests/perf/b8_scale_test/results/2026-05-18T1138Z-phase3/phase3-results.md +++ b/tests/perf/b8_scale_test/results/2026-05-18T1138Z-phase3/phase3-results.md @@ -2,7 +2,7 @@ Date: 2026-05-18 -Corpus: `/tmp/clarion-b8-elspeth-full-20260518T0016Z` +Corpus: `/tmp/loomweave-b8-elspeth-full-20260518T0016Z` Reproducibility status: directional historical measurement. The temporary corpus path is not committed; see `corpus-provenance.md` and @@ -14,11 +14,11 @@ Command: ```bash corpus_dir=$(bash tests/perf/b8_scale_test/derive-elspeth-corpus.sh \ /path/to/elspeth \ - /tmp/clarion-b8-elspeth-corpus-$(date -u +%Y%m%dT%H%M%SZ)) -target/release/clarion install --force --path "$corpus_dir" + /tmp/loomweave-b8-elspeth-corpus-$(date -u +%Y%m%dT%H%M%SZ)) +target/release/loomweave install --force --path "$corpus_dir" python3 tests/perf/b8_scale_test/results/2026-05-18T0114Z/analyze-with-rss.py \ tests/perf/b8_scale_test/results/2026-05-18T1138Z-phase3/analyze-metrics.json \ - /home/john/clarion/target/release/clarion analyze \ + /home/john/loomweave/target/release/loomweave analyze \ "$corpus_dir" ``` @@ -44,6 +44,6 @@ Acceptance: - RSS overhead: PASS. Whole-run peak RSS is +9.399 MiB relative to the B.8 baseline, below the 500 MiB limit. -The run emitted `CLA-FACT-CLUSTERING-WEAK-MODULARITY` because the full elspeth +The run emitted `LMWV-FACT-CLUSTERING-WEAK-MODULARITY` because the full elspeth graph modularity score was 0.020884003737243844, below the v0.1 threshold of 0.3. diff --git a/wardline.yaml b/wardline.yaml index 3d3e6c9e..dfc57f0b 100644 --- a/wardline.yaml +++ b/wardline.yaml @@ -1,4 +1,4 @@ -clarion: +loomweave: url: http://127.0.0.1:9111 filigree: - url: http://127.0.0.1:8542/api/loom/scan-results + url: http://127.0.0.1:8542/api/weft/scan-results diff --git a/web/docs/CNAME b/web/docs/CNAME index 965bd45e..3322c61f 100644 --- a/web/docs/CNAME +++ b/web/docs/CNAME @@ -1 +1 @@ -clarion.foundryside.dev +loomweave.foundryside.dev diff --git a/web/docs/concepts/entity-model.md b/web/docs/concepts/entity-model.md index 05036028..7bdd8dea 100644 --- a/web/docs/concepts/entity-model.md +++ b/web/docs/concepts/entity-model.md @@ -1,12 +1,12 @@ # The entity model -Clarion's job is to turn a tree of source files into a queryable graph of +Loomweave's job is to turn a tree of source files into a queryable graph of **entities** and **edges**. Everything an agent asks is answered against that graph. ## Entities -An entity is a named thing Clarion extracted from the source: a function, a +An entity is a named thing Loomweave extracted from the source: a function, a class, a module, or a clustered **subsystem**. Every entity carries a stable **entity ID** with three colon-separated segments: @@ -34,11 +34,11 @@ after a rename or move. | `function` | A function or method, addressed by its L7 qualified name | | `class` | A class definition | | `module` | A source module (file-level) | -| `subsystem` | A cluster of modules, produced by Clarion's clustering pass | +| `subsystem` | A cluster of modules, produced by Loomweave's clustering pass | ## Edges -Entities are connected by typed edges. Clarion extracts four: +Entities are connected by typed edges. Loomweave extracts four: | Edge | Meaning | | --- | --- | @@ -55,7 +55,7 @@ excluded unless a query explicitly asks for them, so a clean answer stays clean. Static analysis has blind spots — calls made through an attribute receiver, for instance, can't always be resolved statically. Rather than silently dropping -them, Clarion's traversal results carry a `scope_excludes` block naming exactly +them, Loomweave's traversal results carry a `scope_excludes` block naming exactly what was **not** searched. An empty `callers` list with `scope_excludes: ["attribute-receiver-calls"]` means "none found among the edges I can see," never "guaranteed nobody calls this." That distinction is what @@ -63,7 +63,7 @@ lets an agent reason safely about a negative result. ## Subsystems -Beyond the raw graph, Clarion clusters modules into **subsystems** and persists +Beyond the raw graph, Loomweave clusters modules into **subsystems** and persists them as first-class entities. `subsystem_members(id)` lists a subsystem's modules; `subsystem_of(id)` is the reverse — given any entity, find the subsystem it belongs to (a function resolves through its containing module). @@ -71,6 +71,6 @@ subsystem it belongs to (a function resolves through its containing module). ## Storage The whole graph is persisted to a project-local SQLite database at -`.clarion/clarion.db`, written by a single writer-actor with a reader pool -(ADR-011). There is no mandatory cloud component: Clarion is local-first, and the +`.loomweave/loomweave.db`, written by a single writer-actor with a reader pool +(ADR-011). There is no mandatory cloud component: Loomweave is local-first, and the only network egress is the LLM provider during `summary(id)` calls. diff --git a/web/docs/concepts/mcp-tools.md b/web/docs/concepts/mcp-tools.md index 795328c3..6a61032b 100644 --- a/web/docs/concepts/mcp-tools.md +++ b/web/docs/concepts/mcp-tools.md @@ -1,16 +1,16 @@ # MCP consult tools -Clarion is a **consult-mode** tool. It does not write your code or run your +Loomweave is a **consult-mode** tool. It does not write your code or run your tests; it answers questions about the codebase so a coding agent doesn't have to -re-derive the same structural facts on every turn. `clarion serve` exposes those +re-derive the same structural facts on every turn. `loomweave serve` exposes those answers as MCP tools. ## The consult loop -Without Clarion, an agent answering "who calls this, and what does it touch?" +Without Loomweave, an agent answering "who calls this, and what does it touch?" greps the tree, opens files, and reconstructs the call graph from scratch — -every time, burning context. With Clarion, the same question is a couple of tool -calls against a graph that was already built once by `clarion analyze`: +every time, burning context. With Loomweave, the same question is a couple of tool +calls against a graph that was already built once by `loomweave analyze`: ```text agent ──▶ entity_at(file, line) ──▶ "which entity is here?" @@ -25,7 +25,7 @@ trust it. ## Core tool families -Clarion exposes a 39-tool MCP surface. Start with the navigation and briefing +Loomweave exposes a 39-tool MCP surface. Start with the navigation and briefing tools, then reach for catalogue shortcuts when you need a targeted structural query: @@ -52,13 +52,13 @@ shape of each response. The running server also exposes analyze lifecycle tools, freshness checks, faceted search, guidance/finding inspection, source/call-site evidence, and exploration-elimination shortcuts. Connect an MCP client to a live -`clarion serve` to see the full, current `tools/list`. +`loomweave serve` to see the full, current `tools/list`. ## Enrich-only by design -`issues_for(id)` reaches into Filigree, a sibling Loom product, to attach issues +`issues_for(id)` reaches into Filigree, a sibling Weft product, to attach issues to an entity. That binding is strictly **enrich-only**: if Filigree is unavailable, the tool returns an `unavailable` envelope instead of failing the -call, and Clarion's own answers never depend on it. This is the Loom federation -axiom in practice — a sibling may *add* information to Clarion's view, but is -never *required* for Clarion to make sense. +call, and Loomweave's own answers never depend on it. This is the Weft federation +axiom in practice — a sibling may *add* information to Loomweave's view, but is +never *required* for Loomweave to make sense. diff --git a/web/docs/getting-started.md b/web/docs/getting-started.md index da40b6a3..478bbffd 100644 --- a/web/docs/getting-started.md +++ b/web/docs/getting-started.md @@ -1,33 +1,33 @@ # Getting Started -This walks you from nothing to a running Clarion MCP server that a consult-mode +This walks you from nothing to a running Loomweave MCP server that a consult-mode agent can query. Everything except `summary(id)` works without any LLM credentials. ## 1. Install the binary and the Python plugin -Clarion is a single Rust binary; Python support ships as a separate language +Loomweave is a single Rust binary; Python support ships as a separate language plugin. Pull both from the latest GitHub Release: ```bash TAG=v1.2.0 -curl -L -o clarion.tar.gz \ - "https://github.com/tachyon-beep/clarion/releases/download/${TAG}/clarion-x86_64-unknown-linux-gnu.tar.gz" -tar xzf clarion.tar.gz -install clarion-x86_64-unknown-linux-gnu/clarion ~/.local/bin/ +curl -L -o loomweave.tar.gz \ + "https://github.com/foundryside-dev/loomweave/releases/download/${TAG}/loomweave-x86_64-unknown-linux-gnu.tar.gz" +tar xzf loomweave.tar.gz +install loomweave-x86_64-unknown-linux-gnu/loomweave ~/.local/bin/ pipx install \ - "https://github.com/tachyon-beep/clarion/releases/download/${TAG}/clarion-plugin-python-1.2.0.tar.gz" + "https://github.com/foundryside-dev/loomweave/releases/download/${TAG}/loomweave-plugin-python-1.2.0.tar.gz" ``` Confirm the binary is on your `PATH`: ```bash -clarion --version +loomweave --version ``` The Python plugin is discovered on `$PATH` at analyze time. If no plugin is -found, `clarion analyze` exits `0` with a warning and status +found, `loomweave analyze` exits `0` with a warning and status `skipped_no_plugins` rather than failing. ## 2. Initialise a project @@ -36,27 +36,27 @@ From inside the repository you want to map: ```bash cd /path/to/your/python/repo -clarion install --path . +loomweave install --path . ``` -`install` creates a project-local `.clarion/` directory (the SQLite store lives -at `.clarion/clarion.db`) and, optionally, installs agent-orientation assets: +`install` creates a project-local `.loomweave/` directory (the SQLite store lives +at `.loomweave/loomweave.db`) and, optionally, installs agent-orientation assets: -- `clarion install --skills` — drop the bundled `clarion-workflow` skill pack +- `loomweave install --skills` — drop the bundled `loomweave-workflow` skill pack into `.claude/skills/` and `.agents/skills/`. -- `clarion install --hooks` — merge a `SessionStart` hook into +- `loomweave install --hooks` — merge a `SessionStart` hook into `.claude/settings.json`. -- `clarion install --all` — do everything (equivalent to a bare `install`). +- `loomweave install --all` — do everything (equivalent to a bare `install`). !!! note "Where state lives" - `clarion analyze` always persists to the project root's `.clarion/`, not to + `loomweave analyze` always persists to the project root's `.loomweave/`, not to wherever `--path` pointed at install time. To re-index a corpus cleanly, - remove the stale `.clarion/` first (or pass `--no-incremental`). + remove the stale `.loomweave/` first (or pass `--no-incremental`). ## 3. Build the graph ```bash -clarion analyze +loomweave analyze ``` `analyze` walks the source tree, dispatches the discovered language plugin to @@ -70,7 +70,7 @@ install end-to-end. ## 4. Serve the graph over MCP ```bash -clarion serve +loomweave serve ``` This starts the MCP stdio server. Point your MCP client at it — for Claude @@ -79,15 +79,15 @@ Code, register it in `.mcp.json`: ```json { "mcpServers": { - "clarion": { - "command": "clarion", + "loomweave": { + "command": "loomweave", "args": ["serve", "--path", "."] } } } ``` -Your agent can now call Clarion's consult tools instead of re-exploring the +Your agent can now call Loomweave's consult tools instead of re-exploring the tree. See [MCP consult tools](concepts/mcp-tools.md) for the workflow and [the MCP tool reference](reference/mcp-tools.md) for the core tools. @@ -103,8 +103,8 @@ only path that makes network calls to a model provider. | Symptom | Cause | Fix | | --- | --- | --- | | `analyze` exits with `skipped_no_plugins` | No language plugin on `$PATH` | `pipx install` the Python plugin (step 1) | -| Stale entities after a big refactor | Incremental skip kept old rows | `clarion analyze --no-incremental` | -| Agent can't reach the server | MCP registration missing | `clarion doctor --fix` | +| Stale entities after a big refactor | Incremental skip kept old rows | `loomweave analyze --no-incremental` | +| Agent can't reach the server | MCP registration missing | `loomweave doctor --fix` | -`clarion doctor` verifies the skill pack, the `SessionStart` hook, and the +`loomweave doctor` verifies the skill pack, the `SessionStart` hook, and the `.mcp.json` registration, and repairs them in place with `--fix`. diff --git a/web/docs/index.md b/web/docs/index.md index 1d71604a..690388bf 100644 --- a/web/docs/index.md +++ b/web/docs/index.md @@ -7,17 +7,17 @@ hide: ## Install -Clarion is a single Rust binary plus a Python language plugin. Grab both from +Loomweave is a single Rust binary plus a Python language plugin. Grab both from the latest GitHub Release: ```bash TAG=v1.2.0 -curl -L -o clarion.tar.gz \ - "https://github.com/tachyon-beep/clarion/releases/download/${TAG}/clarion-x86_64-unknown-linux-gnu.tar.gz" -tar xzf clarion.tar.gz -install clarion-x86_64-unknown-linux-gnu/clarion ~/.local/bin/ +curl -L -o loomweave.tar.gz \ + "https://github.com/foundryside-dev/loomweave/releases/download/${TAG}/loomweave-x86_64-unknown-linux-gnu.tar.gz" +tar xzf loomweave.tar.gz +install loomweave-x86_64-unknown-linux-gnu/loomweave ~/.local/bin/ pipx install \ - "https://github.com/tachyon-beep/clarion/releases/download/${TAG}/clarion-plugin-python-1.2.0.tar.gz" + "https://github.com/foundryside-dev/loomweave/releases/download/${TAG}/loomweave-plugin-python-1.2.0.tar.gz" ``` The [Getting Started](getting-started.md) guide covers a fresh-machine install, @@ -25,20 +25,20 @@ running against a real Python project, and connecting an MCP client. ## 30-second example -Point Clarion at a Python repo, build the graph, and serve it: +Point Loomweave at a Python repo, build the graph, and serve it: ```bash cd /path/to/your/python/repo -clarion install --path . # initialise the project's .clarion/ store -clarion analyze # walk the corpus, persist entities + edges -clarion serve # expose the graph to your agent over MCP +loomweave install --path . # initialise the project's .loomweave/ store +loomweave analyze # walk the corpus, persist entities + edges +loomweave serve # expose the graph to your agent over MCP ``` -`clarion analyze` runs with **no LLM credentials** and is the fastest way to +`loomweave analyze` runs with **no LLM credentials** and is the fastest way to verify the install — it walks the corpus and writes the structural graph. Only `summary(id)` calls dispatch the LLM, lazily and one entity at a time. -Once `clarion serve` is running, a consult-mode agent reaches a graph-aware tool +Once `loomweave serve` is running, a consult-mode agent reaches a graph-aware tool instead of grep-and-read. Ask which entity covers a source location, then expand its one-hop neighborhood: @@ -60,4 +60,4 @@ empty section is never mistaken for a guaranteed true negative. - [Getting Started](getting-started.md) — install, analyze a repo, connect an agent. - [The entity model](concepts/entity-model.md) — entity IDs, kinds, and the edge graph. -- [MCP consult tools](concepts/mcp-tools.md) — how an agent uses Clarion instead of re-exploring. +- [MCP consult tools](concepts/mcp-tools.md) — how an agent uses Loomweave instead of re-exploring. diff --git a/web/docs/reference/cli.md b/web/docs/reference/cli.md index bc771f4f..d24b9a77 100644 --- a/web/docs/reference/cli.md +++ b/web/docs/reference/cli.md @@ -1,63 +1,63 @@ # CLI reference -The `clarion` binary has a small, focused command set. Run `clarion +The `loomweave` binary has a small, focused command set. Run `loomweave --help` for the authoritative, version-matched flags. ```text -clarion [options] +loomweave [options] ``` -## `clarion install` +## `loomweave install` -Initialise `.clarion/` and install agent-orientation assets. +Initialise `.loomweave/` and install agent-orientation assets. -A bare `clarion install` does everything: `.clarion/` init plus the skill pack -and the `SessionStart` hook. If `.clarion/` already exists, init is skipped and +A bare `loomweave install` does everything: `.loomweave/` init plus the skill pack +and the `SessionStart` hook. If `.loomweave/` already exists, init is skipped and skills/hooks are applied idempotently. | Flag | Effect | | --- | --- | | `--path ` | Directory to install into (default: current directory) | -| `--force` | Overwrite an existing `.clarion/` directory | -| `--skills` | Install only the bundled `clarion-workflow` skill pack | +| `--force` | Overwrite an existing `.loomweave/` directory | +| `--skills` | Install only the bundled `loomweave-workflow` skill pack | | `--hooks` | Merge only a `SessionStart` hook into `.claude/settings.json` | | `--all` | Do everything (equivalent to a bare install) | -## `clarion analyze` +## `loomweave analyze` Walk the source tree, dispatch discovered language plugins to extract -entities/edges, and persist results to `.clarion/clarion.db`. Re-runs are +entities/edges, and persist results to `.loomweave/loomweave.db`. Re-runs are idempotent (UPSERT on the entity id) and incremental by default. If no plugins are on `$PATH`, exits `0` with a warning and status `skipped_no_plugins`. | Flag | Effect | | --- | --- | | `[PATH]` | Path to analyse (default: current directory) | -| `--config ` | Path to `clarion.yaml` (default: project-root if present) | +| `--config ` | Path to `loomweave.yaml` (default: project-root if present) | | `--no-incremental` | Force a full re-index, disabling the unchanged-file skip | | `--resume ` | Reuse a prior run id; re-emit findings without flipping the peer's prior findings to `unseen_in_latest` | -| `--prune-unseen` | Ask Filigree to soft-archive stale Clarion findings (enrich-only) | +| `--prune-unseen` | Ask Filigree to soft-archive stale Loomweave findings (enrich-only) | | `--no-sei` | Skip the stable-entity-identity mint pass (diagnostic escape hatch) | | `--allow-unredacted-secrets` | Allow analysis of files with unredacted secrets (requires confirmation) | !!! warning "Analyze writes to the project root" - `analyze` always persists to the project root's `.clarion/`, regardless of - where `--path` pointed during `install`. Wipe a stale `.clarion/` before + `analyze` always persists to the project root's `.loomweave/`, regardless of + where `--path` pointed during `install`. Wipe a stale `.loomweave/` before re-analysing the same corpus from scratch, or use `--no-incremental`. -## `clarion serve` +## `loomweave serve` Run the MCP stdio server, exposing the consult tools over MCP. | Flag | Effect | | --- | --- | -| `--path ` | Project directory containing `.clarion/clarion.db` (default: current directory) | -| `--config ` | Path to `clarion.yaml` | +| `--path ` | Project directory containing `.loomweave/loomweave.db` (default: current directory) | +| `--config ` | Path to `loomweave.yaml` | -## `clarion doctor` +## `loomweave doctor` Verify (and optionally repair) the installed agent-orientation surfaces: the -`clarion-workflow` skill pack, the `SessionStart` hook, and the `.mcp.json` MCP +`loomweave-workflow` skill pack, the `SessionStart` hook, and the `.mcp.json` MCP registration. Prints a per-surface report; exits non-zero if any problem remains, so it works as a CI or pre-commit gate. @@ -66,11 +66,11 @@ remains, so it works as a CI or pre-commit gate. | `--path ` | Project directory to check (default: current directory) | | `--fix` | Repair problems in place (idempotent). Without it, doctor only reports | -## `clarion db backup` +## `loomweave db backup` -Take a consistent, WAL-safe online backup of `.clarion/clarion.db`. Unlike `cp`, +Take a consistent, WAL-safe online backup of `.loomweave/loomweave.db`. Unlike `cp`, this captures outstanding WAL frames into a standalone single-file copy, so it is -safe to run during a live `clarion analyze`. +safe to run during a live `loomweave analyze`. | Flag | Effect | | --- | --- | diff --git a/web/docs/reference/mcp-tools.md b/web/docs/reference/mcp-tools.md index 8c9b0179..ff74d7ba 100644 --- a/web/docs/reference/mcp-tools.md +++ b/web/docs/reference/mcp-tools.md @@ -1,6 +1,6 @@ # MCP tool reference -The tools below are the core consult tools served by `clarion serve` over the +The tools below are the core consult tools served by `loomweave serve` over the MCP stdio transport. The live 1.2.x surface exposes 39 tools, including navigation, briefing, source inspection, guidance/finding enrichment, analyze lifecycle, freshness, faceted search, and structural shortcuts. Connect an MCP diff --git a/web/docs/stylesheets/extra.css b/web/docs/stylesheets/extra.css index e648ac28..aaa77df5 100644 --- a/web/docs/stylesheets/extra.css +++ b/web/docs/stylesheets/extra.css @@ -1,29 +1,29 @@ /* ========================================================================== - Clarion — Loom design system + Clarion accent, layered on Material. + Loomweave — Weft design system + Loomweave accent, layered on Material. Convergence strategy (shared with Wardline, Filigree, Legis): - Material owns the light/dark toggle. We do NOT add a custom toggle and we do NOT use light-dark()/[data-theme]; semantic tokens are mapped under Material's own scheme selectors ([data-md-color-scheme="default"|"slate"]) and we override Material's --md-primary/--md-accent there. - - The `--loom-*` primitives, the spacing scale, the fluid type, the radius, - and the `.cl-*` landing structure are the SHARED Loom layer — byte-for-byte + - The `--weft-*` primitives, the spacing scale, the fluid type, the radius, + and the `.cl-*` landing structure are the SHARED Weft layer — byte-for-byte portable across the suite. Only the accent ramp differs per product. - - Wardline's accent is teal (~195). Clarion's accent is amber/brass (~58–78): - a clarion is a brass trumpet — a clear call, illumination, light shed on + - Wardline's accent is teal (~195). Loomweave's accent is amber/brass (~58–78): + a loomweave is a brass trumpet — a clear call, illumination, light shed on the codebase. The deep shades are tuned to clear WCAG AA on white. ========================================================================== */ :root { - /* ---- Loom primitive ramp (OKLCH, perceptually uniform) — SHARED ---- */ - /* Slate/ink — shared Loom neutral hue (~255) */ - --loom-ink-50: oklch(0.97 0.010 255); - --loom-ink-100: oklch(0.93 0.018 255); - --loom-ink-200: oklch(0.86 0.028 255); - --loom-ink-700: oklch(0.40 0.060 255); - --loom-ink-900: oklch(0.22 0.040 255); - - /* Clarion accent — amber/brass. Hue rotates 78→56 as it deepens so the dark + /* ---- Weft primitive ramp (OKLCH, perceptually uniform) — SHARED ---- */ + /* Slate/ink — shared Weft neutral hue (~255) */ + --weft-ink-50: oklch(0.97 0.010 255); + --weft-ink-100: oklch(0.93 0.018 255); + --weft-ink-200: oklch(0.86 0.028 255); + --weft-ink-700: oklch(0.40 0.060 255); + --weft-ink-900: oklch(0.22 0.040 255); + + /* Loomweave accent — amber/brass. Hue rotates 78→56 as it deepens so the dark end reads as brass, not yellow, and the link shade clears AA on white. */ --cl-amber-200: oklch(0.88 0.070 78); --cl-amber-300: oklch(0.78 0.100 72); /* dark-scheme links: #dfad6d, 8.7:1 */ @@ -63,9 +63,9 @@ --cl-bg: oklch(0.99 0.003 255); --cl-bg-subtle: oklch(0.975 0.006 255); --cl-bg-card: oklch(1 0 0); - --cl-text: var(--loom-ink-900); + --cl-text: var(--weft-ink-900); --cl-text-muted: oklch(0.46 0.020 255); - --cl-border: var(--loom-ink-200); + --cl-border: var(--weft-ink-200); --cl-accent: var(--cl-amber-700); --cl-accent-soft: oklch(0.95 0.045 80); --cl-on-accent: oklch(0.99 0.010 80); @@ -232,7 +232,7 @@ font-size: 0.85em; } -/* ---- Agent-exchange snippet (shared Loom improvement: a terminal-styled +/* ---- Agent-exchange snippet (shared Weft improvement: a terminal-styled illustration of the consult loop, portable to every product) ---- */ .cl-snippet { margin: var(--cl-space-8) auto var(--cl-space-6); @@ -293,7 +293,7 @@ margin-bottom: var(--cl-space-6); } -/* ---- Loom suite ---- */ +/* ---- Weft suite ---- */ .cl-suite { margin-top: var(--cl-space-10); padding-top: var(--cl-space-6); @@ -374,7 +374,7 @@ } /* ========================================================================== - Footer Loom suite links (copyright partial override) — site-wide + Footer Weft suite links (copyright partial override) — site-wide ========================================================================== */ .cl-footer-suite { display: flex; diff --git a/web/mkdocs.yml b/web/mkdocs.yml index 6b79249d..be1c13a2 100644 --- a/web/mkdocs.yml +++ b/web/mkdocs.yml @@ -1,8 +1,8 @@ -site_name: Clarion +site_name: Loomweave site_description: Code archaeology for coding agents — entity-level facts about your codebase, served over MCP -site_url: https://clarion.foundryside.dev -repo_url: https://github.com/tachyon-beep/clarion -repo_name: tachyon-beep/clarion +site_url: https://loomweave.foundryside.dev +repo_url: https://github.com/foundryside-dev/loomweave +repo_name: foundryside-dev/loomweave edit_uri: edit/main/web/docs/ # Source lives under web/; the default `site/` name is reserved for the build @@ -14,9 +14,9 @@ theme: custom_dir: overrides palette: # The named palette only seeds Material's on-primary text colour (white). - # The visible accent is remapped to Clarion amber/brass in + # The visible accent is remapped to Loomweave amber/brass in # docs/stylesheets/extra.css under each [data-md-color-scheme] selector, - # exactly as Wardline remaps the shared Loom ramp to teal. + # exactly as Wardline remaps the shared Weft ramp to teal. - scheme: default primary: indigo accent: indigo @@ -61,6 +61,6 @@ nav: - MCP tool reference: reference/mcp-tools.md - CLI: reference/cli.md - About: - - Changelog: https://github.com/tachyon-beep/clarion/blob/main/CHANGELOG.md - - Contributing: https://github.com/tachyon-beep/clarion/blob/main/CLAUDE.md - - License: https://github.com/tachyon-beep/clarion/blob/main/LICENSE + - Changelog: https://github.com/foundryside-dev/loomweave/blob/main/CHANGELOG.md + - Contributing: https://github.com/foundryside-dev/loomweave/blob/main/CLAUDE.md + - License: https://github.com/foundryside-dev/loomweave/blob/main/LICENSE diff --git a/web/overrides/home.html b/web/overrides/home.html index 1c58f88a..2b2ac7f5 100644 --- a/web/overrides/home.html +++ b/web/overrides/home.html @@ -1,9 +1,9 @@ {% extends "main.html" %} {# - Clarion product landing page. + Loomweave product landing page. Applied only to docs/index.md via front-matter `template: home.html`. - The hero, feature cards, agent-exchange snippet, and Loom-suite grid live + The hero, feature cards, agent-exchange snippet, and Weft-suite grid live here; the markdown body of index.md (install + the real consult example) is rendered via {{ page.content }} so Material's syntax highlighting and content.code.copy buttons keep working on the real fenced code blocks. @@ -14,9 +14,9 @@
Code archaeology for coding agents -

Clarion

+

Loomweave

- Clarion ingests a codebase, extracts its entities and relationships into a + Loomweave ingests a codebase, extracts its entities and relationships into a graph, and serves entity-level facts to consult-mode agents over MCP — so an agent asks a graph-aware tool instead of re-grepping the tree on every question. @@ -32,11 +32,11 @@

Clarion

-
+

Structural extraction

- clarion analyze walks the corpus, extracts functions, + loomweave analyze walks the corpus, extracts functions, classes, and modules, and persists the contains, calls, and references graph to a local SQLite store. @@ -45,7 +45,7 @@

Structural extraction

Consult tools over MCP

- clarion serve exposes a catalogue of MCP tools — + loomweave serve exposes a catalogue of MCP tools — entity_at, callers_of, neighborhood, execution_paths_from and more — that answer graph questions in one hop. @@ -62,8 +62,8 @@

Lazy LLM summaries

Local-first & enrich-only

- All state lives in a project-local .clarion/ directory. As a - Loom citizen, Clarion only ever adds facts to a sibling's view + All state lives in a project-local .loomweave/ directory. As a + Weft citizen, Loomweave only ever adds facts to a sibling's view — never required for one to make sense.

@@ -72,12 +72,12 @@

Local-first & enrich-only

{# A consult-loop illustration. Decorative (raw HTML, not Material-highlighted) so it stays a clean "what your agent sees" picture; the real, copyable examples live in the markdown body below. #} -
+
# Agent: "Who calls the token refresher, and what does it touch?"
  entity_at(file="auth/tokens.py", line=42)
@@ -96,17 +96,17 @@ 

Local-first & enrich-only

-

The Loom suite

+

The Weft suite

- Clarion is one of four Loom citizens — agent-first tooling built on + Loomweave is one of four Weft citizens — agent-first tooling built on “humans on the loop, not in the loop.” Each is solo-useful and composes with the others on its own: enterprise-class rigour for one-to-two-developer teams, without the enterprise weight.

- -

Clarion

+

Loomweave

Code intelligence — entity-level facts about your codebase.

You are here
diff --git a/web/overrides/partials/copyright.html b/web/overrides/partials/copyright.html index cad2256a..6d7a09e9 100644 --- a/web/overrides/partials/copyright.html +++ b/web/overrides/partials/copyright.html @@ -1,12 +1,12 @@ {#- - Clarion override of Material's copyright partial. - Adds the Loom suite links to the footer site-wide so suite presence is + Loomweave override of Material's copyright partial. + Adds the Weft suite links to the footer site-wide so suite presence is reachable from every docs page, then preserves Material's default content. -#}