From 302f035aad5c027307dd785aa766edd0bbf885ac Mon Sep 17 00:00:00 2001 From: codecaaron Date: Mon, 17 Aug 2026 10:23:11 -0400 Subject: [PATCH 1/5] feat: anti-slop wave + contract-change alignment sweep MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two stacked campaigns over the non-critical surfaces. Anti-slop lint campaign (waves I/J/K + shape): 849 -> 366 findings; ledger docs/anti-slop-curiosities.md; system core freeze intact. Contract-change alignment sweep: 271 authority claims audited across six lanes (vocabulary, boundary, flow, state, policy, proof), 16 remediation clusters, unresolved = 0. Ledger docs/contract-change-ledger.md carries per-iteration instruments, collision re-scans, and every flagged owner decision. Highlights: shared JSON + lane-receipt vocabulary in _assertions; verification harnesses driven through the production engine adapter; producer-owned ProjectManifest (dead snake/camel tolerance removed); session publication exclusivity + ExtractionSession.close(); one owner each for the commit/lock wire contracts (EPERM=alive); engine-transform extension-gate unification (.mjs divergence fixed); fail-loud parse policy for internal wires; C-016 loader seam (no-module-mocking now 0); hygiene live-gate gitignore-vacuity fix; build graph de-duplication (toolchain gate + double-writer race); withAnimus type portability (TS2742) repairing the next-app/next16-app lanes. Ratchet: .abstraction-owners.json (16 canonical names, exceptions calibrated non-vacuous) + .collision-report.json (hooks remain OFF; enabling is an owner decision). Verification: compile/types/canary/unit-ts(1808)/unit-rust/clippy/ workers/hygiene:rust/parity(65/65, baselines untouched)/integration plus 7 consumer owner lanes green. Known reds: verify:lint (standing 366 anti-slop debt) and verify:packed (environmental next 15.5.23 vs @types/react 18 drift; instrument unchanged — see ledger closeout). Co-Authored-By: Claude Fable 5 --- .abstraction-owners.json | 135 ++ .collision-report.json | 34 + .gitignore | 10 +- AGENTS.md | 1 + bun.lock | 94 +- docs/contract-change-ledger.md | 1104 +++++++++++++++++ e2e/next-app/scripts/assert-build.ts | 56 +- e2e/next16-app/scripts/assert-build.ts | 43 +- e2e/packed-app/scripts/assert-build.ts | 85 +- e2e/react-router-app/scripts/assert-build.ts | 28 +- e2e/rollup-app/fixtures/error-root/ds.ts | 7 +- e2e/rollup-app/fixtures/watch-root/src/ds.ts | 7 +- e2e/rollup-app/scripts/assert-artifacts.mjs | 2 +- e2e/svelte-app/scripts/assert-build.ts | 104 +- e2e/vinext-app/pages/_app.tsx | 12 +- e2e/vinext-app/scripts/assert-build.ts | 30 +- e2e/vite-app/scripts/assert-build.ts | 82 +- .../specs/includes-driven-discovery/spec.md | 2 +- openspec/specs/vite-extraction-plugin/spec.md | 2 +- package.json | 2 + .../__tests__/assert-appearance.test.ts | 75 +- .../_assertions/__tests__/assert-css.test.ts | 28 + .../_assertions/__tests__/receipt.test.ts | 155 ++- packages/_assertions/src/assert-appearance.ts | 7 +- packages/_assertions/src/assert-css.ts | 27 +- packages/_assertions/src/index.ts | 1 + packages/_assertions/src/json.ts | 61 + packages/_assertions/src/receipt.ts | 106 +- packages/_integration/CLAUDE.md | 20 +- .../__tests__/cascade-round-trip.test.ts | 10 +- .../_integration/__tests__/extraction.test.ts | 13 +- .../keyframes-binding-substitution.test.ts | 20 +- .../__tests__/keyframes-parity.test.ts | 18 +- .../__tests__/manifest-shape.test.ts | 329 ++++- .../_integration/__tests__/run-pipeline.ts | 198 ++- .../__tests__/selector-rules.test.ts | 7 +- .../__tests__/serialization.test.ts | 55 +- .../__tests__/svelte-source-lifecycle.test.ts | 11 +- .../__tests__/svelte-usage-extraction.test.ts | 9 +- .../_integration/__tests__/usage-facts.ts | 23 + packages/_integration/package.json | 1 + packages/_parity/package.json | 1 + packages/_parity/src/cli.ts | 21 + packages/_parity/src/engine-run.ts | 376 ++++-- packages/_parity/src/register.ts | 18 +- packages/_parity/src/types.ts | 1 - packages/cli/src/build.ts | 27 +- packages/cli/src/config.ts | 267 +++- packages/cli/src/index.ts | 37 +- packages/cli/src/watch.ts | 104 +- packages/cli/src/writer.ts | 130 +- packages/cli/tests/cli-unit.test.ts | 167 ++- packages/extract/CLAUDE.md | 5 +- packages/extract/pipeline/core-options.ts | 36 +- .../pipeline/correlate-external-tokens.ts | 16 +- packages/extract/pipeline/engine-adapter.ts | 7 +- .../extract/pipeline/external-keyframes.ts | 26 +- packages/extract/pipeline/index.ts | 12 +- packages/extract/pipeline/internal-wire.ts | 43 + .../extract/pipeline/manifest-diagnostics.ts | 16 +- packages/extract/pipeline/manifest-schema.ts | 181 +++ packages/extract/pipeline/mdx-preprocessor.ts | 34 +- packages/extract/pipeline/resolve-asset.ts | 30 +- packages/extract/pipeline/run-analysis.ts | 14 +- packages/extract/pipeline/source-ingestion.ts | 99 ++ packages/extract/pipeline/static-css.ts | 18 +- .../extract/pipeline/svelte-source-adapter.ts | 239 +++- packages/extract/pipeline/utils.ts | 22 + .../extract/session/extraction-session.ts | 204 ++- packages/extract/session/index.ts | 32 +- packages/extract/session/published-set.ts | 212 +++- packages/extract/session/session-paths.ts | 28 +- packages/extract/session/singleton.ts | 251 ++-- .../extract/session/turbopack-orchestrator.ts | 68 +- .../tests/correlate-external-tokens.test.ts | 26 +- .../extract/tests/engine-prerequisites.ts | 82 ++ .../extract/tests/external-keyframes.test.ts | 28 + .../extract/tests/files-json-decode.test.ts | 148 +++ .../tests/manifest-diagnostics.test.ts | 18 +- packages/extract/tests/resolve-asset.test.ts | 35 +- packages/next-plugin/package.json | 3 + packages/next-plugin/src/index.ts | 8 + packages/next-plugin/src/loader-core.ts | 45 +- packages/next-plugin/src/loader.ts | 10 +- packages/next-plugin/src/plugin.ts | 12 +- packages/next-plugin/src/turbopack-config.ts | 12 +- packages/next-plugin/src/turbopack-loader.ts | 116 +- packages/next-plugin/src/with-animus.ts | 217 +++- .../next-plugin/tests/engine-devmode.test.ts | 19 +- .../tests/geological-reset-failure.test.ts | 45 +- .../tests/loader-css-import.test.ts | 46 +- .../tests/loader-epoch-guard.test.ts | 66 +- .../tests/manifest-diagnostics.test.ts | 5 +- .../tests/plugin-needbuild.test.ts | 144 ++- .../next-plugin/tests/plugin-pipeline.test.ts | 166 ++- .../tests/replacement-epoch.test.ts | 66 +- .../tests/session-artifacts.test.ts | 512 ++++++-- .../next-plugin/tests/singleton-fixtures.ts | 99 +- .../tests/singleton-v2-transform.test.ts | 59 +- .../tests/svelte-source-lifecycle.test.ts | 73 +- .../tests/turbopack-config.test.ts | 11 +- .../tests/turbopack-external-watchers.test.ts | 189 +-- .../tests/turbopack-loader.test.ts | 75 +- .../tests/turbopack-orchestrator.test.ts | 311 +++-- .../tests/turbopack-protocol-gauntlet.test.ts | 211 ++-- .../turbopack-watcher-registration.test.ts | 252 +++- .../tests/watch-asset-batch.test.ts | 45 +- .../tests/watch-external-ingestion.test.ts | 87 +- .../tests/watch-transaction.test.ts | 32 +- .../webpack-gauntlet/cache-epoch.test.ts | 41 +- .../webpack-gauntlet/differential.test.ts | 61 +- .../external-ingestion.test.ts | 52 +- .../harness-scheduling.test.ts | 35 +- .../tests/webpack-gauntlet/harness.ts | 286 ++++- .../tests/webpack-gauntlet/prerequisites.ts | 70 +- .../webpack-gauntlet/real-engine.test.ts | 5 +- .../next-plugin/tests/with-animus.test.ts | 127 +- packages/oracle/DESIGN.md | 2 +- .../__tests__/artifact-name-parity.test.ts | 43 + packages/oracle/__tests__/cli.test.ts | 63 +- .../oracle/__tests__/core-identity.test.ts | 2 +- packages/oracle/__tests__/e2e-demo.test.ts | 12 +- .../oracle/__tests__/engine-cascade.test.ts | 281 +---- .../__tests__/engine-inspect-explain.test.ts | 366 +----- .../__tests__/engine-prove-refine.test.ts | 202 +-- .../__tests__/engine-simulate-diff.test.ts | 443 +------ packages/oracle/__tests__/fixture-world.ts | 366 ++++++ .../oracle/__tests__/host-universe.test.ts | 50 +- .../oracle/__tests__/places-compare.test.ts | 21 +- .../oracle/__tests__/places-session.test.ts | 131 +- packages/oracle/__tests__/places-warm.test.ts | 9 +- packages/oracle/src/cli/render.ts | 19 +- packages/oracle/src/cli/run.ts | 27 +- packages/oracle/src/cli/session.ts | 583 +++++++-- packages/oracle/src/core/identity.ts | 253 +++- packages/oracle/src/core/predicate.ts | 54 +- packages/oracle/src/core/probe.ts | 35 +- packages/oracle/src/core/scenario.ts | 63 +- packages/oracle/src/core/value.ts | 20 +- packages/oracle/src/core/world.ts | 8 +- packages/oracle/src/engines/cascade.ts | 57 +- packages/oracle/src/engines/cells.ts | 11 +- packages/oracle/src/engines/diff.ts | 239 ++-- packages/oracle/src/engines/explain.ts | 18 +- packages/oracle/src/engines/prove.ts | 97 +- packages/oracle/src/engines/result.ts | 20 +- packages/oracle/src/engines/runtime.ts | 10 +- packages/oracle/src/engines/simulate.ts | 318 ++--- packages/oracle/src/engines/speculate.ts | 3 +- packages/oracle/src/host/animus/conditions.ts | 19 +- packages/oracle/src/host/animus/css-parse.ts | 29 +- packages/oracle/src/host/animus/dependency.ts | 7 +- packages/oracle/src/host/animus/host.ts | 46 +- packages/oracle/src/host/animus/identity.ts | 70 +- packages/oracle/src/host/animus/index.ts | 2 +- packages/oracle/src/host/animus/loader.ts | 38 +- .../oracle/src/host/animus/manifest-types.ts | 135 +- .../oracle/src/host/animus/obligations.ts | 21 +- .../oracle/src/host/animus/replacement.ts | 115 +- packages/oracle/src/host/animus/scenario.ts | 21 +- packages/oracle/src/host/animus/selector.ts | 33 +- packages/oracle/src/host/animus/universe.ts | 114 +- packages/oracle/src/index.ts | 14 +- packages/oracle/src/places/analysis.ts | 129 +- packages/oracle/src/places/axes.ts | 6 +- packages/oracle/src/places/check.ts | 10 +- packages/oracle/src/places/compare.ts | Bin 6209 -> 6808 bytes packages/oracle/src/places/resolve.ts | 7 +- packages/oracle/src/places/snapshot.ts | 13 +- packages/oracle/src/places/source.ts | 140 +-- .../src/providers/component-contract.ts | 4 +- packages/oracle/src/providers/host.ts | 4 +- packages/oracle/src/providers/in-memory.ts | 6 +- .../{render-shape.ts => render-tree.ts} | 38 +- packages/showcase/src/App.tsx | 4 +- .../src/components/docs/BeforeAfter.tsx | 4 +- .../showcase/src/components/docs/Callout.tsx | 8 +- .../src/components/docs/ChainStep.tsx | 22 +- .../src/components/docs/ColorPalette.tsx | 13 +- .../showcase/src/components/docs/Heading.tsx | 18 +- .../src/components/docs/LivePreview.tsx | 39 +- .../src/components/docs/MDXProvider.tsx | 32 +- .../showcase/src/components/docs/PageToc.tsx | 4 +- .../src/components/surfaces/SyntaxBlock.tsx | 39 +- packages/showcase/src/layout/Shell.tsx | 12 +- packages/showcase/src/pages/Examples.tsx | 8 +- packages/showcase/src/pages/Home.tsx | 51 +- packages/system/__tests__/appearance.test.ts | 2 +- .../__tests__/bootstrap-generator.test.ts | 50 +- .../__tests__/bootstrap-packaging.test.ts | 62 +- .../system/__tests__/color-validation.test.ts | 16 +- packages/system/__tests__/compose.test.tsx | 21 +- .../system/__tests__/composed-family.test.tsx | 6 +- .../__tests__/createClassResolver.test.ts | 2 +- packages/system/__tests__/deep-merge.test.ts | 10 +- .../system/__tests__/drop-diagnostic.test.ts | 123 +- .../__tests__/dynamic-unit-fallback.test.ts | 6 +- packages/system/__tests__/extend.test.ts | 94 +- packages/system/__tests__/is-dev.test.ts | 22 +- packages/system/__tests__/manifest-v2.test.ts | 89 +- .../__tests__/serialized-config.test.ts | 279 ++++- packages/system/__tests__/snippet-harness.ts | 23 +- .../__tests__/system-scheme-emission.test.ts | 32 +- .../system/__tests__/theme-extend.test.ts | 36 +- .../system/__tests__/theme-resolver.test.ts | 40 +- .../__tests__/theme-state-isolation.test.ts | 19 +- packages/system/__tests__/theme.test.ts | 66 +- packages/system/__tests__/transforms.test.ts | 6 +- packages/system/__tests__/witness.test.ts | 57 +- packages/unplugin/src/core.ts | 65 +- packages/unplugin/src/options.ts | 26 +- packages/unplugin/tests/host-unit.test.ts | 46 +- packages/vite-plugin/package.json | 3 + packages/vite-plugin/src/build-start.ts | 12 +- packages/vite-plugin/src/config.ts | 52 +- packages/vite-plugin/src/context.ts | 278 +++-- packages/vite-plugin/src/hmr.ts | 49 +- packages/vite-plugin/src/hot-update-events.ts | 69 +- packages/vite-plugin/src/index.ts | 11 +- packages/vite-plugin/src/rediscovery.ts | 93 +- packages/vite-plugin/src/reset-coalescer.ts | 20 +- packages/vite-plugin/src/transform.ts | 65 +- packages/vite-plugin/src/virtual-modules.ts | 53 +- .../appearance-bootstrap-injection.test.ts | 129 +- .../tests/bridge-sheet-registry.test.ts | 80 ++ .../tests/build-start-assets.test.ts | 27 +- .../tests/compatibility-barrier.test.ts | 132 +- packages/vite-plugin/tests/context-probe.ts | 141 ++- packages/vite-plugin/tests/dev-define.test.ts | 10 +- .../tests/dev-lane/dev-server.test.ts | 12 +- .../vite-plugin/tests/dev-lane/fixture.ts | 2 +- .../tests/dev-lane/prerequisites.ts | 69 -- .../tests/dev-lane/vite-adapter.ts | 71 +- .../tests/engine-retirement.test.ts | 11 +- .../tests/external-token-contracts.test.ts | 82 +- .../tests/hmr-bridge-injection.test.ts | 64 +- .../tests/hot-update-events.test.ts | 49 +- packages/vite-plugin/tests/hot-update.test.ts | 303 +++-- .../vite-plugin/tests/index-html-context.ts | 28 +- .../tests/module-invalidation.test.ts | 9 +- .../vite-plugin/tests/plugin-context.test.ts | 128 +- .../vite-plugin/tests/rediscovery.test.ts | 101 +- .../vite-plugin/tests/reset-coalescer.test.ts | 35 +- .../tests/self-verify-includes.test.ts | 13 +- .../tests/svelte-source-lifecycle.test.ts | 147 ++- .../tests/transform-source.test.ts | 66 +- scripts/assert-showcase-build.ts | 41 +- scripts/hygiene/_ast.ts | 46 + scripts/hygiene/_emit-knip-receipts.ts | 44 +- scripts/hygiene/_emit-oxlint-receipts.ts | 68 +- scripts/hygiene/_tool-reports.ts | 251 ++++ scripts/hygiene/delete-unused.test.ts | 158 ++- scripts/hygiene/delete-unused.ts | 124 +- scripts/hygiene/reconcile-after-knip.ts | 35 +- .../verify/extract-test-enumeration.test.ts | 11 +- scripts/verify/manifest-model.ts | 75 ++ scripts/verify/owner-graph.test.ts | 34 +- scripts/verify/packed-graph.ts | 8 + scripts/verify/packed.sh | 55 +- scripts/verify/rust-policy.ts | 13 +- scripts/verify/topology.ts | 13 +- scripts/verify/workers-config.test.ts | 10 +- scripts/verify/workspace-graph.ts | 26 +- tools/oxlint/anti-slop/index.ts | 41 + .../rules/no-chained-type-assertions.ts | 77 ++ .../no-conditional-empty-object-spread.ts | 49 + .../rules/no-known-value-widening.ts | 247 ++++ .../anti-slop/rules/no-module-mocking.ts | 91 ++ .../anti-slop/rules/no-object-parameters.ts | 126 ++ .../anti-slop/rules/no-reflect-apply.ts | 28 + .../oxlint/anti-slop/rules/no-reflect-get.ts | 28 + .../anti-slop/rules/no-runtime-typeof.ts | 67 + .../rules/no-shape-in-symbol-names.ts | 39 + .../anti-slop/rules/no-unknown-parameters.ts | 106 ++ .../anti-slop/rules/no-unknown-returns.ts | 115 ++ .../rules/no-unknown-type-aliases.ts | 70 ++ .../rules/no-unsafe-dictionary-type.ts | 134 ++ .../anti-slop/rules/no-widen-then-assert.ts | 366 ++++++ ...quire-safety-comment-for-type-assertion.ts | 62 + .../anti-slop/shared/dictionary-types.ts | 502 ++++++++ .../shared/lexical-type-parameters.ts | 61 + .../oxlint/anti-slop/shared/reflect-method.ts | 35 + vite.config.ts | 149 ++- 283 files changed, 15723 insertions(+), 6101 deletions(-) create mode 100644 .abstraction-owners.json create mode 100644 .collision-report.json create mode 100644 docs/contract-change-ledger.md create mode 100644 packages/_assertions/src/json.ts create mode 100644 packages/_integration/__tests__/usage-facts.ts create mode 100644 packages/extract/pipeline/internal-wire.ts create mode 100644 packages/extract/pipeline/manifest-schema.ts create mode 100644 packages/extract/tests/engine-prerequisites.ts create mode 100644 packages/extract/tests/files-json-decode.test.ts create mode 100644 packages/oracle/__tests__/artifact-name-parity.test.ts create mode 100644 packages/oracle/__tests__/fixture-world.ts rename packages/oracle/src/providers/{render-shape.ts => render-tree.ts} (62%) create mode 100644 packages/vite-plugin/tests/bridge-sheet-registry.test.ts delete mode 100644 packages/vite-plugin/tests/dev-lane/prerequisites.ts create mode 100644 scripts/hygiene/_ast.ts create mode 100644 scripts/hygiene/_tool-reports.ts create mode 100644 scripts/verify/manifest-model.ts create mode 100644 tools/oxlint/anti-slop/index.ts create mode 100644 tools/oxlint/anti-slop/rules/no-chained-type-assertions.ts create mode 100644 tools/oxlint/anti-slop/rules/no-conditional-empty-object-spread.ts create mode 100644 tools/oxlint/anti-slop/rules/no-known-value-widening.ts create mode 100644 tools/oxlint/anti-slop/rules/no-module-mocking.ts create mode 100644 tools/oxlint/anti-slop/rules/no-object-parameters.ts create mode 100644 tools/oxlint/anti-slop/rules/no-reflect-apply.ts create mode 100644 tools/oxlint/anti-slop/rules/no-reflect-get.ts create mode 100644 tools/oxlint/anti-slop/rules/no-runtime-typeof.ts create mode 100644 tools/oxlint/anti-slop/rules/no-shape-in-symbol-names.ts create mode 100644 tools/oxlint/anti-slop/rules/no-unknown-parameters.ts create mode 100644 tools/oxlint/anti-slop/rules/no-unknown-returns.ts create mode 100644 tools/oxlint/anti-slop/rules/no-unknown-type-aliases.ts create mode 100644 tools/oxlint/anti-slop/rules/no-unsafe-dictionary-type.ts create mode 100644 tools/oxlint/anti-slop/rules/no-widen-then-assert.ts create mode 100644 tools/oxlint/anti-slop/rules/require-safety-comment-for-type-assertion.ts create mode 100644 tools/oxlint/anti-slop/shared/dictionary-types.ts create mode 100644 tools/oxlint/anti-slop/shared/lexical-type-parameters.ts create mode 100644 tools/oxlint/anti-slop/shared/reflect-method.ts diff --git a/.abstraction-owners.json b/.abstraction-owners.json new file mode 100644 index 00000000..0c476e57 --- /dev/null +++ b/.abstraction-owners.json @@ -0,0 +1,135 @@ +{ + "version": 1, + "owners": [ + { + "name": "JsonValue", + "kind": "type", + "file": "packages/_assertions/src/json.ts", + "exceptions": [ + { + "file": "packages/system/__tests__/serialized-config.test.ts", + "reason": "frozen anti-slop core-freeze surface; migrate to @animus-ui/assertions when the freeze lifts" + }, + { + "file": "packages/system/__tests__/bootstrap-packaging.test.ts", + "reason": "frozen anti-slop core-freeze surface; migrate to @animus-ui/assertions when the freeze lifts" + } + ] + }, + { + "name": "JsonObject", + "file": "packages/_assertions/src/json.ts", + "exceptions": [ + { + "file": "packages/system/__tests__/serialized-config.test.ts", + "reason": "frozen anti-slop core-freeze surface" + }, + { + "file": "packages/system/__tests__/bootstrap-packaging.test.ts", + "reason": "frozen anti-slop core-freeze surface" + } + ] + }, + { + "name": "isJsonObject", + "kind": "function", + "file": "packages/_assertions/src/json.ts", + "exceptions": [ + { + "file": "packages/system/__tests__/serialized-config.test.ts", + "reason": "frozen anti-slop core-freeze surface" + }, + { + "file": "packages/system/__tests__/bootstrap-packaging.test.ts", + "reason": "frozen anti-slop core-freeze surface" + } + ] + }, + { + "name": "isJsonString", + "kind": "function", + "file": "packages/_assertions/src/json.ts", + "exceptions": [ + { + "file": "packages/system/__tests__/serialized-config.test.ts", + "reason": "frozen anti-slop core-freeze surface" + }, + { + "file": "packages/system/__tests__/bootstrap-packaging.test.ts", + "reason": "frozen anti-slop core-freeze surface" + } + ] + }, + { + "name": "isJsonBoolean", + "kind": "function", + "file": "packages/_assertions/src/json.ts", + "exceptions": [ + { + "file": "packages/system/__tests__/serialized-config.test.ts", + "reason": "frozen anti-slop core-freeze surface" + } + ] + }, + { + "name": "isJsonNumber", + "kind": "function", + "file": "packages/_assertions/src/json.ts" + }, + { + "name": "parseJsonObject", + "kind": "function", + "file": "packages/_assertions/src/json.ts" + }, + { + "name": "canonicalJson", + "file": "packages/_parity/src/content-hash.ts", + "exceptions": [ + { + "file": "packages/oracle/src/core/identity.ts", + "reason": "package boundary: oracle cannot depend on _parity; independent canonical form for identity hashing (verified distinct concern, W5/W2 campaign)" + } + ] + }, + { + "name": "stableStringify", + "kind": "function", + "file": "packages/extract/pipeline/utils.ts" + }, + { + "name": "isEngineTransformExtension", + "kind": "function", + "file": "packages/extract/pipeline/mdx-preprocessor.ts" + }, + { + "name": "classifyUnusedVar", + "kind": "function", + "file": "scripts/hygiene/_tool-reports.ts" + }, + { + "name": "writeLaneReceipt", + "kind": "function", + "file": "packages/_assertions/src/receipt.ts" + }, + { + "name": "ProjectManifest", + "kind": "interface", + "file": "packages/extract/pipeline/manifest-schema.ts" + }, + { + "name": "parseFilesJson", + "kind": "function", + "file": "packages/extract/pipeline/source-ingestion.ts" + }, + { + "name": "parseInternalWire", + "kind": "function", + "file": "packages/extract/pipeline/internal-wire.ts" + }, + { + "name": "makeTempRoot", + "kind": "function", + "file": "packages/next-plugin/tests/singleton-fixtures.ts" + } + ] +} diff --git a/.collision-report.json b/.collision-report.json new file mode 100644 index 00000000..98c34ad2 --- /dev/null +++ b/.collision-report.json @@ -0,0 +1,34 @@ +{ + "version": 1, + "reportDirectory": "git:contract-change/reports", + "workflows": { + "agent-session": { + "changeScope": "worktree", + "sessionDelta": true, + "candidateAction": "warn", + "ownerViolationAction": "fail", + "errorAction": "fail" + }, + "agent-stop": { + "changeScope": "worktree", + "candidateAction": "warn", + "ownerViolationAction": "fail", + "errorAction": "fail" + }, + "pre-commit": { + "changeScope": "staged", + "candidateAction": "warn", + "ownerViolationAction": "fail", + "errorAction": "fail" + }, + "ci": { + "changeScope": "branch", + "baseEnv": "GITHUB_BASE_REF", + "base": "origin/main", + "candidateAction": "warn", + "ownerViolationAction": "fail", + "ownersFull": true, + "errorAction": "fail" + } + } +} diff --git a/.gitignore b/.gitignore index c200a697..20ce1afb 100644 --- a/.gitignore +++ b/.gitignore @@ -59,8 +59,13 @@ tsconfig.tsbuildinfo tmp -# live oxlint test fixtures (transient, mkdtempSync per test) -.live-test-fixtures-* +# Live oxlint test fixtures (scripts/hygiene/delete-unused.test.ts) are +# DELIBERATELY NOT ignored here. `vp lint --no-ignore` disables `.eslintignore` +# / `--ignore-path` / `--ignore-pattern` only — it does NOT disable .gitignore, +# so any fixture path listed here is skipped by the linter ("No files found to +# lint", number_of_files: 0) and the live-integration gate silently inspects +# nothing. The former `.live-test-fixtures-*` entry did exactly that. The tests +# create `livetestfixtures-*` at the repo root and remove them in a `finally`. # packed consumer lane staging (verify:packed) e2e/packed-app/.staging/ @@ -72,6 +77,7 @@ e2e/packed-app/.staging/ # made the deliverable un-commitable; children-glob + negation instead) docs/* !docs/standalone-extraction.md +!docs/contract-change-ledger.md .codex .repowise/ diff --git a/AGENTS.md b/AGENTS.md index 0a2403c4..02355001 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -129,6 +129,7 @@ This map routes an edit to the smallest sufficient claim plus any source-owned d | `packages/test-ds/src/**` | `vp run verify:unit:ts && vp run --fail-if-no-match -F '...@animus-ui/test-ds' verify` | | `e2e/packed-app/**` or `scripts/verify/packed.sh` | `vp run verify:packed` | | `scripts/verify/topology.*` | `vp run verify:lint` | +| `tools/oxlint/anti-slop/**` | `vp run verify:lint && vp run verify:compile` | | `packages/{properties,system,extract,vite-plugin,next-plugin,cli,unplugin}/package.json` (deps, peers, exports, files) | `vp run verify:packed` | | `.github/workflows/ci.yaml`, `scripts/**`, `.tool-versions` | `vp run verify:full` | | Worker orchestration (`vite.config.ts`, `scripts/verify/**`, root deploy scripts, Worker ignores) | `vp run verify:full` | diff --git a/bun.lock b/bun.lock index f3bc080a..3a598362 100644 --- a/bun.lock +++ b/bun.lock @@ -8,6 +8,7 @@ "@animus-ui/assertions": "workspace:*", "@arethetypeswrong/cli": "^0.18.5", "@jridgewell/trace-mapping": "^0.3.31", + "@oxlint/plugins": "1.78.0", "@types/bun": "^1.3.11", "@types/lodash": "^4.14.178", "@types/node": "^18.15.0", @@ -18,6 +19,7 @@ "happy-dom": "^20.10.6", "knip": "^6", "oxc-parser": "0.140.0", + "oxlint": "1.78.0", "publint": "^0.3.21", "react": "18.3.1", "react-dom": "18.3.1", @@ -157,6 +159,7 @@ "name": "@animus-ui/integration", "version": "0.0.0", "devDependencies": { + "@animus-ui/assertions": "workspace:*", "@animus-ui/extract": "workspace:*", "@mdx-js/mdx": "^3.0.0", "esbuild": "^0.25.0", @@ -168,6 +171,7 @@ "name": "@animus-ui/parity", "version": "0.0.1", "devDependencies": { + "@animus-ui/assertions": "workspace:*", "@animus-ui/extract": "workspace:*", "lightningcss": "^1.30.2", "oxc-parser": "^0.139.0", @@ -212,6 +216,9 @@ "dependencies": { "@animus-ui/extract": "workspace:*", }, + "devDependencies": { + "@animus-ui/assertions": "workspace:*", + }, "peerDependencies": { "@mdx-js/mdx": "^3.0.0", "next": ">=15 <17", @@ -302,6 +309,9 @@ "browserslist": "^4.24.0", "lightningcss": "^1.29.0", }, + "devDependencies": { + "@animus-ui/assertions": "workspace:*", + }, "peerDependencies": { "@mdx-js/mdx": "^3.0.0", "vite": ">=8 <9", @@ -902,45 +912,45 @@ "@oxlint-tsgolint/win32-x64": ["@oxlint-tsgolint/win32-x64@0.23.0", "", { "os": "win32", "cpu": "x64" }, "sha512-5MyjFuqf+g8OUPJBSGWHJtmoWnzFJYyOg4To9WMQshZYEWig/vtu7JtJ03VWnzHv9LJkAUeApY0gVCOywFR/iQ=="], - "@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.67.0", "", { "os": "android", "cpu": "arm" }, "sha512-VrSi571rDv1N8HaEDM+DEX8nmT0y9jJo8tzzW13vsOWTx59xQczCIJx68n2zWOXRT5YKZsOZXp4qkHN/10x4mw=="], + "@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.78.0", "", { "os": "android", "cpu": "arm" }, "sha512-Bu819lmAfZMUHErrpe0cEWj3iaefuUODHSU8+UbXy67V/r7/7f4K3FL0NmbD85E+wiFLDYuhP8Zlv0XnVeXshw=="], - "@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.67.0", "", { "os": "android", "cpu": "arm64" }, "sha512-l6+NdYxMoRohix5r5bbigW16LPicceCwGcQ6LKKuE1kUdjgFfQolJjrJsQYPFetIs78Gxj/G/f5TEGoTCwj9nQ=="], + "@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.78.0", "", { "os": "android", "cpu": "arm64" }, "sha512-CDfxZgB61B7buRdY2FJoAYYPPXCZ1EoC1LKscnC5dg3kjobdxiconvAvvN1BmHyW4PyFT3jRLDag/BY/roSNBQ=="], - "@oxlint/binding-darwin-arm64": ["@oxlint/binding-darwin-arm64@1.67.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-jOzXxS1AxFxhImLIRbtGIMrEwaXcgMw3gR57WB1cRk8ai+vpr6726kxXqVvlNsrXtJ/FrmOm8RxlC0m8SW24Qg=="], + "@oxlint/binding-darwin-arm64": ["@oxlint/binding-darwin-arm64@1.78.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-2Y2U9Ahrz+OO0Ej88f9SJYq51/jUBp1Mc7iZu0ukrbeeZ3gpRGfzIFnoqfHDY96xr0GEfNrPUBFEy0nN5aD7HA=="], - "@oxlint/binding-darwin-x64": ["@oxlint/binding-darwin-x64@1.67.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-3DFAVY94OqjIZHXIPz37yGRSWwOFTAqChQ64/M69GYLawzP0KiwdhDNfqdKKYT0bTR/DNxmMnQsj3ns+8+X/Lg=="], + "@oxlint/binding-darwin-x64": ["@oxlint/binding-darwin-x64@1.78.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-rpych6eJq6m9jDRypTEaPD1xysaEW5h9+xuxhGK/QhOg+/xaqPZrCrTNoIl/f3nEjuJeCEmstNDlrE9rJi/3/g=="], - "@oxlint/binding-freebsd-x64": ["@oxlint/binding-freebsd-x64@1.67.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-e4dDKZuLu8TR9DEBssWSDahlPgZBwojTTHZUvnjBRJfJJbpxYCjfjKfi0Z1+CSLMiJBwI2yCDtRM1XJQaARjmg=="], + "@oxlint/binding-freebsd-x64": ["@oxlint/binding-freebsd-x64@1.78.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-IcMGrQT3QizkOESUJd5et+rOhVqSkNDfNik1cvrKDqIbzqx9KMtRswpFgkCuNTSwylCFLKhGUu8KmqY1ZnC0Dg=="], - "@oxlint/binding-linux-arm-gnueabihf": ["@oxlint/binding-linux-arm-gnueabihf@1.67.0", "", { "os": "linux", "cpu": "arm" }, "sha512-BKytFdcQzbITV3xlnzDUDTEDtbUMCCiC4EaNTDZ4FyT8gdNvBC4gfiLucXp/sQl0XU3p7syTlorUWVVVBZab2g=="], + "@oxlint/binding-linux-arm-gnueabihf": ["@oxlint/binding-linux-arm-gnueabihf@1.78.0", "", { "os": "linux", "cpu": "arm" }, "sha512-/uLdoJ0IXE6vo/0f0LKjinQAp+re+VMaCWaNT8ENIv2EOCkSsc8SGaflXAuW0Jua2dq5+GLVWm1NQK7P3UFSNQ=="], - "@oxlint/binding-linux-arm-musleabihf": ["@oxlint/binding-linux-arm-musleabihf@1.67.0", "", { "os": "linux", "cpu": "arm" }, "sha512-XYAv0esBDX7BpTzRDjVX2Vdj+zndd8ll2dFQiaeQ6zTZr7A8GRDTN7fH3FP3jU+O0vCDx85oH/EtG7BzPgAXuw=="], + "@oxlint/binding-linux-arm-musleabihf": ["@oxlint/binding-linux-arm-musleabihf@1.78.0", "", { "os": "linux", "cpu": "arm" }, "sha512-7xi4Wb/O8NRJhLoUXmDJMUVpNYvB5kefdhFU1Jb8rtae4QoXlTiLwI14X4YvAXVZLNZChP8m5qO9SQAlWQTbkQ=="], - "@oxlint/binding-linux-arm64-gnu": ["@oxlint/binding-linux-arm64-gnu@1.67.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-zizRMjA0i6u/2B0evgda04iycu+MoNuf1pBy6Eh+1CjC5wMEG7qN5zdDKTCvFc0KSYSDM9QTG3gjZHirgtQuKg=="], + "@oxlint/binding-linux-arm64-gnu": ["@oxlint/binding-linux-arm64-gnu@1.78.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-4hFW0+fVXa3OIh1Y4A5SPkmvI4wuuBSrCVKzOyE7PTjhc7yEqZ1pmvEEeS5Lj/MaqvegFxXyF33N+6jkehxdyg=="], - "@oxlint/binding-linux-arm64-musl": ["@oxlint/binding-linux-arm64-musl@1.67.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-zB/Tf6sUjmmvvbva9Gj3JTJ8rJ9t4I8/U0o6vSRtd0DRIsIuyegBwJAzhSUFQHdMijIRJkW0exs/yBhpw2S20w=="], + "@oxlint/binding-linux-arm64-musl": ["@oxlint/binding-linux-arm64-musl@1.78.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-oC0mvsgBJjlMijSDEhx9KuvR9zYeHXceA9MjbuXB1F8NSR78Yj2unOBrstEvTVaq+pko+kuue6DajC00eqvTdg=="], - "@oxlint/binding-linux-ppc64-gnu": ["@oxlint/binding-linux-ppc64-gnu@1.67.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-kgU40Gt74CK0TCsF51KZymkIwN9U0BajKsMijB52zPqOeZU9NAHkA/NSQkZDHEaCakx42DxhXkODiAqf2b4Gug=="], + "@oxlint/binding-linux-ppc64-gnu": ["@oxlint/binding-linux-ppc64-gnu@1.78.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-XAllT5SUZS+ohjuZ3/5S0cwe0r7eboiuigeStCZ5DXRYx/2KVM2UvQXvAfyzXEimtQjAB7cDQ2YxDe2Zl2WNQQ=="], - "@oxlint/binding-linux-riscv64-gnu": ["@oxlint/binding-linux-riscv64-gnu@1.67.0", "", { "os": "linux", "cpu": "none" }, "sha512-tOYhkk/iaG9aD3FvGpBFd1Lrw0x0RaVoJBxjUkfNzS50rC5NS5BteNCwgr8A2zCdADrIIoze6D7u6U5Ic++/iQ=="], + "@oxlint/binding-linux-riscv64-gnu": ["@oxlint/binding-linux-riscv64-gnu@1.78.0", "", { "os": "linux", "cpu": "none" }, "sha512-trucMER/0QtecoXvc1y/UVqE3kwJipDwrx4oHfj+nNm3dq2zjP44WT0CfHNDPM3G1DXIkx/gY6lAD21NSCZVhA=="], - "@oxlint/binding-linux-riscv64-musl": ["@oxlint/binding-linux-riscv64-musl@1.67.0", "", { "os": "linux", "cpu": "none" }, "sha512-sEtywrPb+0b+tHYl1SDCrw903fiC4eyKoNqzP3v+f2JT3Xcv4NEYG+P8rj+eEnX7IWhqV/xj8/JmcmVj21CXaA=="], + "@oxlint/binding-linux-riscv64-musl": ["@oxlint/binding-linux-riscv64-musl@1.78.0", "", { "os": "linux", "cpu": "none" }, "sha512-cm3O4F/HQbdzOUX5mKHqG5KDL6E5w0pnlZ+fbBy2rmLryPOowkuLagFHTopQsEIpjcaZoPOrL+BmmAytAG9HFg=="], - "@oxlint/binding-linux-s390x-gnu": ["@oxlint/binding-linux-s390x-gnu@1.67.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-BvR8Moa0zCLxroOx4vZaZN9nUfwAUpSTwjZdxZyKy4bv3PrzrXrxKR/ZQ0L9wNSvlPhnMJeZfa3q5w6ZCTuN6Q=="], + "@oxlint/binding-linux-s390x-gnu": ["@oxlint/binding-linux-s390x-gnu@1.78.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-33wRf6HqGNsybJ3qX4cGaQN2ODPxNmc1rMa0mrTmx3eFq1VzOnvQooi9bIGVYakW8a/wmqVx1mgsUm8R2xfTiw=="], - "@oxlint/binding-linux-x64-gnu": ["@oxlint/binding-linux-x64-gnu@1.67.0", "", { "os": "linux", "cpu": "x64" }, "sha512-mm2cxM6fksOpq6l0uFws8BUGKAR4dNa/cZCn37Npq7PFbhD5HDJqWfnoIvTaeRKMy5XdS2tO0MA0qbHDrnXAAA=="], + "@oxlint/binding-linux-x64-gnu": ["@oxlint/binding-linux-x64-gnu@1.78.0", "", { "os": "linux", "cpu": "x64" }, "sha512-rRdISSYegj6VganMZ9tjRjijowfHJ09IZU01i0toBAqr6n5LEtwHq2IeS4FjW2RoskOHlb6efB26H5izYb3GEQ=="], - "@oxlint/binding-linux-x64-musl": ["@oxlint/binding-linux-x64-musl@1.67.0", "", { "os": "linux", "cpu": "x64" }, "sha512-WmbMuLapKyDlobMkXAaAL0Y+Uczh4LETfIfQsUpbId4Ip8Ai82/jqeYTOoUCkuuhBFapgqP253+d83tLKOksJg=="], + "@oxlint/binding-linux-x64-musl": ["@oxlint/binding-linux-x64-musl@1.78.0", "", { "os": "linux", "cpu": "x64" }, "sha512-GmsP4rW0xTL6u5CVdcDsaN5Fbc7hBc382Wmar1kttbnwSEviM+rSINKOMQ+UQ6iH+AGwC+8gaAiwu134Tgh6Lg=="], - "@oxlint/binding-openharmony-arm64": ["@oxlint/binding-openharmony-arm64@1.67.0", "", { "os": "none", "cpu": "arm64" }, "sha512-9g/PqxYJelzzTAOR5Y+RiRqdeydhEuXv2KxNeFcAKQ7UsvnWSY1OP4MsuPMbTO2Pf70tz7mFhl1j13H3fyh+8g=="], + "@oxlint/binding-openharmony-arm64": ["@oxlint/binding-openharmony-arm64@1.78.0", "", { "os": "none", "cpu": "arm64" }, "sha512-sy9yeYuADc8a+n4TLBayzMCZiHPW78DcIFVpOXTmdKHWQeM9xe5uzkqIIZmi326D5hY9XVwacipEB1p7tQjPAg=="], - "@oxlint/binding-win32-arm64-msvc": ["@oxlint/binding-win32-arm64-msvc@1.67.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-2VhwE6Gatb0vJGnN0TBuQMbKCOiZlSQ/zJvVWYLK4a9d4iDiJOen/yVQkGpmsJ90MuH66fzi0kEKI0jRQMDxGA=="], + "@oxlint/binding-win32-arm64-msvc": ["@oxlint/binding-win32-arm64-msvc@1.78.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-rjc2hF1KfMi8fZj1X/m3AmnHbdsF3rL0v6KQg0Uc880Yb2khjz+3U14sfdZ7jWTpRnN1m1NQa/TT7uU9lJWPrA=="], - "@oxlint/binding-win32-ia32-msvc": ["@oxlint/binding-win32-ia32-msvc@1.67.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-EQ3VExXfeM1InbE5+JjufhZZTWy+kHUwgt3yZR7gQ47Je/mE0WspQPan0OJznh493L5anM210YNJtH1PXjTSFg=="], + "@oxlint/binding-win32-ia32-msvc": ["@oxlint/binding-win32-ia32-msvc@1.78.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-zcuXFVrEFHIafRfkCQT8w/Xe41o07ozl/vwHq7p94vB29xVzsB0sZGYORU1jhcYKv3Lr0J3HbJ2T4fHH5rWmvA=="], - "@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.67.0", "", { "os": "win32", "cpu": "x64" }, "sha512-bw24y+/1MHS4QDkons3YyHkPT9uCMoLHHgQhb+mb8NOjTYwub1CZ+K9Ngr8aO5DMrDrkqHwTzlTwFP2vS8Y/ZQ=="], + "@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.78.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Sb5ocmLSuYeOuXd+CFOToGKp/gjXUEWDnvIGwhnh8aq8wY4TMmEnKnvbogSW7RdMZv77JSARduS7/gv+khYEjA=="], - "@oxlint/plugins": ["@oxlint/plugins@1.61.0", "", {}, "sha512-nkOyZEF1vH527CkdQtOp1HMrVFEM4ResURvI2JFeGoup+h+43J/k/FgdOR9b9Isxg+Yae7qVDa7y3nssE8b3TQ=="], + "@oxlint/plugins": ["@oxlint/plugins@1.78.0", "", {}, "sha512-Ypt8KeRYw+4jUtlPirfcHWMrn5ms12VrrFPD+Mds477/7tJxG1Kcz2Yrg2nVcTQEUx/GdlhS+BUg1kmxNm04Ug=="], "@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="], @@ -1964,7 +1974,7 @@ "oxfmt": ["oxfmt@0.52.0", "", { "dependencies": { "tinypool": "2.1.0" }, "optionalDependencies": { "@oxfmt/binding-android-arm-eabi": "0.52.0", "@oxfmt/binding-android-arm64": "0.52.0", "@oxfmt/binding-darwin-arm64": "0.52.0", "@oxfmt/binding-darwin-x64": "0.52.0", "@oxfmt/binding-freebsd-x64": "0.52.0", "@oxfmt/binding-linux-arm-gnueabihf": "0.52.0", "@oxfmt/binding-linux-arm-musleabihf": "0.52.0", "@oxfmt/binding-linux-arm64-gnu": "0.52.0", "@oxfmt/binding-linux-arm64-musl": "0.52.0", "@oxfmt/binding-linux-ppc64-gnu": "0.52.0", "@oxfmt/binding-linux-riscv64-gnu": "0.52.0", "@oxfmt/binding-linux-riscv64-musl": "0.52.0", "@oxfmt/binding-linux-s390x-gnu": "0.52.0", "@oxfmt/binding-linux-x64-gnu": "0.52.0", "@oxfmt/binding-linux-x64-musl": "0.52.0", "@oxfmt/binding-openharmony-arm64": "0.52.0", "@oxfmt/binding-win32-arm64-msvc": "0.52.0", "@oxfmt/binding-win32-ia32-msvc": "0.52.0", "@oxfmt/binding-win32-x64-msvc": "0.52.0" }, "peerDependencies": { "svelte": "^5.0.0", "vite-plus": "*" }, "optionalPeers": ["svelte", "vite-plus"], "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-nJlYM35F64zTDMecCNhoHNkf+D/eHv7xcjj9XDSj+bFAVtN93m7v8DQMdHd6nDG6Akf/kEYYHmDUBs2Dz27Sug=="], - "oxlint": ["oxlint@1.67.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.67.0", "@oxlint/binding-android-arm64": "1.67.0", "@oxlint/binding-darwin-arm64": "1.67.0", "@oxlint/binding-darwin-x64": "1.67.0", "@oxlint/binding-freebsd-x64": "1.67.0", "@oxlint/binding-linux-arm-gnueabihf": "1.67.0", "@oxlint/binding-linux-arm-musleabihf": "1.67.0", "@oxlint/binding-linux-arm64-gnu": "1.67.0", "@oxlint/binding-linux-arm64-musl": "1.67.0", "@oxlint/binding-linux-ppc64-gnu": "1.67.0", "@oxlint/binding-linux-riscv64-gnu": "1.67.0", "@oxlint/binding-linux-riscv64-musl": "1.67.0", "@oxlint/binding-linux-s390x-gnu": "1.67.0", "@oxlint/binding-linux-x64-gnu": "1.67.0", "@oxlint/binding-linux-x64-musl": "1.67.0", "@oxlint/binding-openharmony-arm64": "1.67.0", "@oxlint/binding-win32-arm64-msvc": "1.67.0", "@oxlint/binding-win32-ia32-msvc": "1.67.0", "@oxlint/binding-win32-x64-msvc": "1.67.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.22.1", "vite-plus": "*" }, "optionalPeers": ["oxlint-tsgolint", "vite-plus"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-blwwaHPdoH8piQ5/z0KHeoHFR7FZgl12WluKJfu4qFLPkZl6mK04PkLE45Fw1NxfBRSlh40Gu7MkxHUw++ociQ=="], + "oxlint": ["oxlint@1.78.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.78.0", "@oxlint/binding-android-arm64": "1.78.0", "@oxlint/binding-darwin-arm64": "1.78.0", "@oxlint/binding-darwin-x64": "1.78.0", "@oxlint/binding-freebsd-x64": "1.78.0", "@oxlint/binding-linux-arm-gnueabihf": "1.78.0", "@oxlint/binding-linux-arm-musleabihf": "1.78.0", "@oxlint/binding-linux-arm64-gnu": "1.78.0", "@oxlint/binding-linux-arm64-musl": "1.78.0", "@oxlint/binding-linux-ppc64-gnu": "1.78.0", "@oxlint/binding-linux-riscv64-gnu": "1.78.0", "@oxlint/binding-linux-riscv64-musl": "1.78.0", "@oxlint/binding-linux-s390x-gnu": "1.78.0", "@oxlint/binding-linux-x64-gnu": "1.78.0", "@oxlint/binding-linux-x64-musl": "1.78.0", "@oxlint/binding-openharmony-arm64": "1.78.0", "@oxlint/binding-win32-arm64-msvc": "1.78.0", "@oxlint/binding-win32-ia32-msvc": "1.78.0", "@oxlint/binding-win32-x64-msvc": "1.78.0" }, "peerDependencies": { "oxlint-tsgolint": ">=7.0.2001", "vite-plus": "*" }, "optionalPeers": ["oxlint-tsgolint", "vite-plus"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-QgQePuxIqKOzo1KSjG2EnITEeWvWnKAm77eq8nrMtf6AGoA+zyGc4PFYtDNJSD25g/ibOwfQ851hZ4/SPkMVoA=="], "oxlint-tsgolint": ["oxlint-tsgolint@0.23.0", "", { "optionalDependencies": { "@oxlint-tsgolint/darwin-arm64": "0.23.0", "@oxlint-tsgolint/darwin-x64": "0.23.0", "@oxlint-tsgolint/linux-arm64": "0.23.0", "@oxlint-tsgolint/linux-x64": "0.23.0", "@oxlint-tsgolint/win32-arm64": "0.23.0", "@oxlint-tsgolint/win32-x64": "0.23.0" }, "bin": { "tsgolint": "bin/tsgolint.js" } }, "sha512-3mBv3CoPbh8dFbzfDGIWa2ytZjn2v+3EX4aKRXjIhsoGFzG8GCjfRirz3rwZf1wYbZzsNLTSgpw8VjQuWdp/jA=="], @@ -2456,6 +2466,10 @@ "vite-plus/@oxc-project/types": ["@oxc-project/types@0.133.0", "", {}, "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA=="], + "vite-plus/@oxlint/plugins": ["@oxlint/plugins@1.61.0", "", {}, "sha512-nkOyZEF1vH527CkdQtOp1HMrVFEM4ResURvI2JFeGoup+h+43J/k/FgdOR9b9Isxg+Yae7qVDa7y3nssE8b3TQ=="], + + "vite-plus/oxlint": ["oxlint@1.67.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.67.0", "@oxlint/binding-android-arm64": "1.67.0", "@oxlint/binding-darwin-arm64": "1.67.0", "@oxlint/binding-darwin-x64": "1.67.0", "@oxlint/binding-freebsd-x64": "1.67.0", "@oxlint/binding-linux-arm-gnueabihf": "1.67.0", "@oxlint/binding-linux-arm-musleabihf": "1.67.0", "@oxlint/binding-linux-arm64-gnu": "1.67.0", "@oxlint/binding-linux-arm64-musl": "1.67.0", "@oxlint/binding-linux-ppc64-gnu": "1.67.0", "@oxlint/binding-linux-riscv64-gnu": "1.67.0", "@oxlint/binding-linux-riscv64-musl": "1.67.0", "@oxlint/binding-linux-s390x-gnu": "1.67.0", "@oxlint/binding-linux-x64-gnu": "1.67.0", "@oxlint/binding-linux-x64-musl": "1.67.0", "@oxlint/binding-openharmony-arm64": "1.67.0", "@oxlint/binding-win32-arm64-msvc": "1.67.0", "@oxlint/binding-win32-ia32-msvc": "1.67.0", "@oxlint/binding-win32-x64-msvc": "1.67.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.22.1", "vite-plus": "*" }, "optionalPeers": ["oxlint-tsgolint", "vite-plus"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-blwwaHPdoH8piQ5/z0KHeoHFR7FZgl12WluKJfu4qFLPkZl6mK04PkLE45Fw1NxfBRSlh40Gu7MkxHUw++ociQ=="], + "vitest/obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], "vitest/picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="], @@ -2614,6 +2628,44 @@ "rolldown-plugin-dts/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0-rc.3", "", {}, "sha512-AmwWFx1m8G/a5cXkxLxTiWl+YEoWuoFLUCwqMlNuWO1tqAYITQAbCRPUkyBHv1VOFgfjVOqEj6L3u15J5ZCzTA=="], + "vite-plus/oxlint/@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.67.0", "", { "os": "android", "cpu": "arm" }, "sha512-VrSi571rDv1N8HaEDM+DEX8nmT0y9jJo8tzzW13vsOWTx59xQczCIJx68n2zWOXRT5YKZsOZXp4qkHN/10x4mw=="], + + "vite-plus/oxlint/@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.67.0", "", { "os": "android", "cpu": "arm64" }, "sha512-l6+NdYxMoRohix5r5bbigW16LPicceCwGcQ6LKKuE1kUdjgFfQolJjrJsQYPFetIs78Gxj/G/f5TEGoTCwj9nQ=="], + + "vite-plus/oxlint/@oxlint/binding-darwin-arm64": ["@oxlint/binding-darwin-arm64@1.67.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-jOzXxS1AxFxhImLIRbtGIMrEwaXcgMw3gR57WB1cRk8ai+vpr6726kxXqVvlNsrXtJ/FrmOm8RxlC0m8SW24Qg=="], + + "vite-plus/oxlint/@oxlint/binding-darwin-x64": ["@oxlint/binding-darwin-x64@1.67.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-3DFAVY94OqjIZHXIPz37yGRSWwOFTAqChQ64/M69GYLawzP0KiwdhDNfqdKKYT0bTR/DNxmMnQsj3ns+8+X/Lg=="], + + "vite-plus/oxlint/@oxlint/binding-freebsd-x64": ["@oxlint/binding-freebsd-x64@1.67.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-e4dDKZuLu8TR9DEBssWSDahlPgZBwojTTHZUvnjBRJfJJbpxYCjfjKfi0Z1+CSLMiJBwI2yCDtRM1XJQaARjmg=="], + + "vite-plus/oxlint/@oxlint/binding-linux-arm-gnueabihf": ["@oxlint/binding-linux-arm-gnueabihf@1.67.0", "", { "os": "linux", "cpu": "arm" }, "sha512-BKytFdcQzbITV3xlnzDUDTEDtbUMCCiC4EaNTDZ4FyT8gdNvBC4gfiLucXp/sQl0XU3p7syTlorUWVVVBZab2g=="], + + "vite-plus/oxlint/@oxlint/binding-linux-arm-musleabihf": ["@oxlint/binding-linux-arm-musleabihf@1.67.0", "", { "os": "linux", "cpu": "arm" }, "sha512-XYAv0esBDX7BpTzRDjVX2Vdj+zndd8ll2dFQiaeQ6zTZr7A8GRDTN7fH3FP3jU+O0vCDx85oH/EtG7BzPgAXuw=="], + + "vite-plus/oxlint/@oxlint/binding-linux-arm64-gnu": ["@oxlint/binding-linux-arm64-gnu@1.67.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-zizRMjA0i6u/2B0evgda04iycu+MoNuf1pBy6Eh+1CjC5wMEG7qN5zdDKTCvFc0KSYSDM9QTG3gjZHirgtQuKg=="], + + "vite-plus/oxlint/@oxlint/binding-linux-arm64-musl": ["@oxlint/binding-linux-arm64-musl@1.67.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-zB/Tf6sUjmmvvbva9Gj3JTJ8rJ9t4I8/U0o6vSRtd0DRIsIuyegBwJAzhSUFQHdMijIRJkW0exs/yBhpw2S20w=="], + + "vite-plus/oxlint/@oxlint/binding-linux-ppc64-gnu": ["@oxlint/binding-linux-ppc64-gnu@1.67.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-kgU40Gt74CK0TCsF51KZymkIwN9U0BajKsMijB52zPqOeZU9NAHkA/NSQkZDHEaCakx42DxhXkODiAqf2b4Gug=="], + + "vite-plus/oxlint/@oxlint/binding-linux-riscv64-gnu": ["@oxlint/binding-linux-riscv64-gnu@1.67.0", "", { "os": "linux", "cpu": "none" }, "sha512-tOYhkk/iaG9aD3FvGpBFd1Lrw0x0RaVoJBxjUkfNzS50rC5NS5BteNCwgr8A2zCdADrIIoze6D7u6U5Ic++/iQ=="], + + "vite-plus/oxlint/@oxlint/binding-linux-riscv64-musl": ["@oxlint/binding-linux-riscv64-musl@1.67.0", "", { "os": "linux", "cpu": "none" }, "sha512-sEtywrPb+0b+tHYl1SDCrw903fiC4eyKoNqzP3v+f2JT3Xcv4NEYG+P8rj+eEnX7IWhqV/xj8/JmcmVj21CXaA=="], + + "vite-plus/oxlint/@oxlint/binding-linux-s390x-gnu": ["@oxlint/binding-linux-s390x-gnu@1.67.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-BvR8Moa0zCLxroOx4vZaZN9nUfwAUpSTwjZdxZyKy4bv3PrzrXrxKR/ZQ0L9wNSvlPhnMJeZfa3q5w6ZCTuN6Q=="], + + "vite-plus/oxlint/@oxlint/binding-linux-x64-gnu": ["@oxlint/binding-linux-x64-gnu@1.67.0", "", { "os": "linux", "cpu": "x64" }, "sha512-mm2cxM6fksOpq6l0uFws8BUGKAR4dNa/cZCn37Npq7PFbhD5HDJqWfnoIvTaeRKMy5XdS2tO0MA0qbHDrnXAAA=="], + + "vite-plus/oxlint/@oxlint/binding-linux-x64-musl": ["@oxlint/binding-linux-x64-musl@1.67.0", "", { "os": "linux", "cpu": "x64" }, "sha512-WmbMuLapKyDlobMkXAaAL0Y+Uczh4LETfIfQsUpbId4Ip8Ai82/jqeYTOoUCkuuhBFapgqP253+d83tLKOksJg=="], + + "vite-plus/oxlint/@oxlint/binding-openharmony-arm64": ["@oxlint/binding-openharmony-arm64@1.67.0", "", { "os": "none", "cpu": "arm64" }, "sha512-9g/PqxYJelzzTAOR5Y+RiRqdeydhEuXv2KxNeFcAKQ7UsvnWSY1OP4MsuPMbTO2Pf70tz7mFhl1j13H3fyh+8g=="], + + "vite-plus/oxlint/@oxlint/binding-win32-arm64-msvc": ["@oxlint/binding-win32-arm64-msvc@1.67.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-2VhwE6Gatb0vJGnN0TBuQMbKCOiZlSQ/zJvVWYLK4a9d4iDiJOen/yVQkGpmsJ90MuH66fzi0kEKI0jRQMDxGA=="], + + "vite-plus/oxlint/@oxlint/binding-win32-ia32-msvc": ["@oxlint/binding-win32-ia32-msvc@1.67.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-EQ3VExXfeM1InbE5+JjufhZZTWy+kHUwgt3yZR7gQ47Je/mE0WspQPan0OJznh493L5anM210YNJtH1PXjTSFg=="], + + "vite-plus/oxlint/@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.67.0", "", { "os": "win32", "cpu": "x64" }, "sha512-bw24y+/1MHS4QDkons3YyHkPT9uCMoLHHgQhb+mb8NOjTYwub1CZ+K9Ngr8aO5DMrDrkqHwTzlTwFP2vS8Y/ZQ=="], + "vite/rolldown/@oxc-project/types": ["@oxc-project/types@0.139.0", "", {}, "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw=="], "vite/rolldown/@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.1.5", "", { "os": "android", "cpu": "arm64" }, "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ=="], diff --git a/docs/contract-change-ledger.md b/docs/contract-change-ledger.md new file mode 100644 index 00000000..36010023 --- /dev/null +++ b/docs/contract-change-ledger.md @@ -0,0 +1,1104 @@ +# Contract-Change Ledger — Non-Critical Surfaces + +Campaign: audit-only inventory (P1) → cluster-at-a-time remediation (P2) → recurrence ratchet (P3). +Started 2026-08-16. Skill: `contract-change` (global, `~/.claude/skills/contract-change`), MSW-governed (one loop). + +## Scope declaration + +**In scope (candidate sources):** `packages/{cli,next-plugin,vite-plugin,unplugin,oracle,_assertions,_integration,_parity,test-ds,showcase}`, `packages/extract/{pipeline,session,tests}` (TS side only), `e2e/*`, `scripts/`, `tools/oxlint/anti-slop`, root `vite.config.ts`. + +**Excluded (critical / frozen):** `packages/system` (type-system fulcrum + DX), `packages/properties` (type-surface adjacent), `packages/extract/crates/**` (Rust core), `legacy/**` (archived), `openspec/**`, generated/vendored paths. + +Owner search is repository-global: excluded surfaces may be cited as existing owners; they are never edited and never produce candidates. + +**Baseline instrument:** `scan-abstraction-collisions.mjs --all` @ working tree of 391df7a (dirty, anti-slop campaign uncommitted). Reports: `.git/contract-change/reports/p1-baseline-all.json` (988 candidates; 671 in scope), `p1-inscope-digest.md`. + +**Proof baseline (pre-remediation, 2026-08-16):** `vp run verify:compile` exit 0; `vp run verify:unit:ts` exit 0 (147 files, 1721 tests, 14.5s). All P2 iteration proofs measure against this green state. `vp run verify:lint` exit 1 with exactly **408 anti-slop errors** — the known mid-flight residual of the separate anti-slop campaign (849→408, scripts lane deferred), NOT part of this campaign's baseline obligations. P2 lint gate: the error count must never increase; remediations that clear errors (e.g. the two `no-module-mocking` errors) record the delta. + +## Row schema + +`claim | lane | file:line | existing owner/analog | disposition | evidence | status` + +- **lane:** vocabulary / boundary / flow / state / policy / proof +- **disposition:** REUSE / INLINE / REMOVE / PROMOTE / DISTINCT (+ UNRESOLVED only while a named semantic question is open) +- **status:** U (unresolved) → A (audited, disposition assigned) → R (remediated) → V (verified). Phase close requires zero U rows. + +## Ledger + +Rows assembled programmatically from `.git/contract-change/reports/p1-lane-*.md` (full 8-column originals preserved there; semantic comparison and dependency legality folded into evidence here). + +### Vocabulary + +Audit-only. No repository file was edited. Worktree state audited: dirty tree at `main` (391df7a) with the uncommitted lint campaign applied (160 changed paths; `tools/` untracked/new). + +Instruments used throughout: `grep -rn` / `grep -rc` over `packages/ e2e/ scripts/ tools/` excluding `node_modules` and `dist`; `git diff HEAD` / `git show HEAD:` to separate campaign-introduced from pre-existing declarations; `md5` over `awk`-extracted body ranges for byte-equality claims; targeted `Read`/`sed` of declaration sites and their owners; `grep -rn openspec/` for spec-pinned surfaces. + +**Standing note on the campaign.** The anti-slop rules `no-unknown-parameters` / `no-unsafe-dictionary-type` / `no-unknown-type-aliases` (vendored at `tools/oxlint/anti-slop/rules/`) forbid `unknown`-typed parameters and broad dictionaries. The observed effect across the tree is that a single generic helper per module was replaced by a locally-named JSON value type plus a guard family. Net-new in the working tree: 25 `JsonValue`/`JsonObject` declarations and 28 `isJson*` guards (`git diff HEAD -U0 | grep -cE '^\+function isJson…'`). The rules do not require this shape — a shared exported `JsonValue` is not `unknown` and satisfies every rule — so the duplication is a choice the campaign made, not one the linter forced. That distinction is the axis most rows below turn on. + +--- + +### Cluster: JSON value vocabulary (campaign-introduced, largest single claim) + +The claim under audit: _the repository needs 11 places capable of defining what a JSON value is, and 10 places capable of deciding what a JSON object is._ + +| claim | lane | file:line | existing owner/analog | disposition | evidence | status | +| -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | +| W11 | CLI/watcher truthfulness | 9 production files + 5 test files (+13 net tests) - `TurbopackWatchOutcome` union kills the null-conflation (S9); `onCycleSettled(error\|null)` seam replaces the CLI monkey-patch + post-`close()` guard moved into the orchestrator (S8; the seam was ALREADY a declared spec debt at standalone-extraction-cli inc 06:209-212); Next binds the watch handle with a death report through its own `[animus-extract]` surface (S10); `??` rmSync fallbacks REMOVEd in cli build+watch (foreign-tree deletion proven pre-fix); `assertNoRetiredEngineSelection` at CLI ingress; lock read: `CliLockRead` none/held/indeterminate - non-ENOENT errno throws, present-but-undecodable lock claims the tree conservatively and `acquireLock` refuses to steal it (ENOTDIR joins ENOENT as no-lock) | 6 fail-first proofs VERBATIM (incl. EISDIR read as no-holder; foreign manifest deleted; both v1-selection vectors resolving instead of rejecting); `verify:compile` 0 (after build:ts - stale dists); 54 files/523->536 tests; `verify:unit:ts` 1782; `verify:integration` 165; **`vp run @animus-ui/rollup-app#verify` exit 0 - all 21 watch-contract assertions incl. the e2e-pinned failure line** (the AGENTS change-type route for cli, run in-iteration); `verify:lint` **393->392** | Re-scan (`w11-rescan.json`, 7 candidates): all >=5 previously adjudicated (W5 domain renames, reference-guard family, template-builder FP). FOLLOW-UP flagged: with the wrapper gone a failed CLI cycle also prints the Turbopack-branded orchestrator message alongside the contracted line - unpinned, driver-neutral rename is a clean follow-up; geological-reset failures remain swallowed inside `processWatchUpdate` (spec-recorded, unchanged). | done | +| W10 | Fail-loud parse policy | 15 files (1 new: package-internal `internal-wire.ts` owning "animus-produced JSON fails = throw", off the barrel — one policy owner, not four inline copies) — 4 sites converted (correlate-external-tokens, external-keyframes scan decode, resolve-asset incl. de-memoized failures, manifest-diagnostics selector-aliases); external-keyframes `:36-45` kept DISTINCT with verified delegation (Rust `parse_opt_value` refuses by name); oracle `render.ts` guards REMOVEd, `SemanticDiff` declared typed end-to-end in `core/probe.ts` | fail-first VERBATIM: 5 tests failed pre-fix across 4 files; post: 32/32; `verify:compile` 0; extract+oracle 51 files/605 (+5); `verify:integration` 165; **`verify:parity` PASS, baselines byte-untouched**; `verify:unit:ts` 1769; `verify:lint` **394→393** | Re-scan (`w10-rescan.json`): 1 candidate, none ≥5. TWO recorded contract reversals (D11a briefed + `manifest-diagnostics.test.ts:126` discovered pinning the same swallow — both marked RECORDED CONTRACT REVERSAL in-test). Strongest evidence: `analyze_css.rs:167-168` — the ENGINE's reader of the same wire went fail-loud in v2; resolve-asset was the last v1-policy holdout (divergence repair, not preference). Agent corrected crossed line-refs in the brief by re-verifying producers. FLAGGED+chipped: `parseFilesJson` message-decoration asymmetry (test-pinned; a third reversal out of mandate). Contraction closeout: 12 claims, 0 unresolved, 0 parallel paths. | done | +| W9 | Vite transform gate + fallback bookkeeping | 12 files + 2 spec spelling syncs — `isEngineTransformExtension` predicate exported from the owner (consumers share the TEST, not just the array); A1 (Vite regex), A2 (Next webpack regex) converted; A3 (Turbopack) pinned as already-correct derivation; unplugin's local `ENGINE_TRANSFORM_RE` deleted (its `.cjs` define-widening kept — different question, justification rewritten in place); live `.mjs` divergence FIXED (local `.mjs` now transforms under Vite + webpack, matching Turbopack + the engine's own acceptance: owned_ast.rs maps everything-else→mjs); S13: `recordFallbackState` sole writer, 4 raw-serve exits report through it, `:231` stays direct-delete with written justification (withhold release on OTHER files) | fail-first VERBATIM: 3 files/4 tests failed pre-fix (`.mjs` rule `[mjs,false]≠[mjs,true]`; two barrier tests resolved instead of rejecting — the exact fatal pair; `.mjs` local transform undefined); after: 54 files/**419** tests (+10); `verify:compile` 0; `verify:integration` 165; `verify:unit:ts` 149 files/1764; `verify:lint` **394** (agent removed its own +1 before landing) | Re-scan (`w9-rescan.json`): 3 candidates, none ≥5 — cleanest iteration. Spec spelling sync recorded (2 scenarios described the gate by its old literal; semantics untouched). FLAGGED residual: external-package `.cjs` reaches the engine under Vite only (gate bypassed for externals) while webpack/Turbopack reject it — needs an engine-level CJS-rewrite decision before a gate answer is meaningful. Probe now borrows `PluginContext.prototype.recordFallbackState` — one less mirrored fake (context-probe drift row partially addressed, main mirror remains W12+). | done | +| W8 | next-plugin constants/deadline/context | 10 files (incl. orchestrator's enumeration fix) — `ANIMUS_CSS_MODULE_ID` relocated to `session-paths.ts` (5 consumers enumerated; barrel re-point left externals byte-identical; NO re-export kept); Turbopack resolveAlias key now imports the constant + `loader-core.ts`'s 4 literal spellings converted (`CSS_IMPORT_RE` deliberately stays literal — reads foreign emitter output); deadline single authority: local 75ms ceiling deleted, `status.deadlineAt` re-read each poll is the sole bound, `DEFAULT_WATCH_DEBOUNCE_MS` one definition; `LoaderContextBase` in loader-core.ts, both loaders extend | fail-first: new gauntlet test FAILED pre-fix (gave up at 2152ms vs published deadline now+6000 — the exact D4 bug), 17/17 post-fix; next-plugin 29 files/204 (+2); vite+cli+extract 48 files/458; `verify:compile` 0; topology 0; `verify:integration` 165; `verify:lint` **394 exactly** (0 attributable); ORCHESTRATOR FIX: W7's `files-json-decode.test.ts` registered in `typescriptTestTargets` (fake-engine-injected, engine-free — verified) → `verify:unit:ts` back to exit 0; W8's chip for it dismissed | Re-scan (`w8-rescan.json`, 9 candidates): all ≥5 previously adjudicated (`sleep` W6; template-builder family P1 false-positive). Agent contraction closeout: 11 claims, 0 unresolved; 1 documented parallel path (no-deadline cap branch — protected contract: unvalidated on-disk status JSON; owner: Turbopack loader; removal condition: validating status decoder — recorded at declaration). MSW rejection recorded: status-decoder validation not done (contract met without it; second behavior change). | done | +| W7 | Manifest schema owner | 17 files (3 new) — `manifest-schema.ts` owns `ProjectManifest`, grounded in the Rust producer struct (engine.rs:36-61, no Options, 15 always-emitted fields) + a REAL generated manifest + sub-struct reads; `ProjectAnalysisResult.manifest: any` killed; `_parity`/`_integration`/`cli` readers converted to `Pick`; snake/camel dual acceptance removed as DEAD TOLERANCE (engine emits snake_case only per its own doc; baselines record derived surfaces, never raw manifest keys; `??` chain born as pre-emptive two-engine tolerance in 8088ffd, v1 retired); `parseFilesJson` unified decoder in `source-ingestion.ts` (3 policies → throw; turbopack site routes through its documented torn-read channel); `usageTags` folded | `verify:compile` 0; 5-dir sweep 75 files/618 tests; extract tests 258 (+13 new fail-first: pre-fix adapter accepted `[{"file":"a.tsx"}]` and seeded the drift map with undefined keys); **`verify:parity` PASS 65/65 ×2 modes, baselines byte-untouched**; `verify:integration` 166; `verify:lint` **400→394** | Re-scan (`w7-rescan.json`): 6 candidates, only the W6-adjudicated `sleep` pair ≥5. Agent contraction closeout: 20 claims, 0 unresolved, 0 parallel paths (self-caught+removed its own forwarding alias `CssSheets`; `FRAGMENT_LAYERS` tied to owner via `satisfies keyof`). One pre-edit test-count baseline not captured (tier gates cover). `manifest-diagnostics.test.ts` regex updated — its invariant (nothing between parse and surfacing) unchanged. DISTINCT survivors documented: oracle + rollup `.mjs` projections; `resolveClasses.ts SystemPropMap` (frozen browser-runtime boundary). | done | +| W6 | Test-harness dedup | 23 files (2 new, 1 deleted) — `makeTempRoot`/`disposeTempRoots` unified registry in `singleton-fixtures.ts` (8 local lifecycles deleted; `cleanupProjects` renamed into it — disjoint consumer sets proven); prerequisite probe owned by NEW `packages/extract/tests/engine-prerequisites.ts` (extract = authority for its own binary; outside `files`/`exports` so nothing shipped widens; relative-path imports match existing idiom; vite dev-lane `prerequisites.ts` deleted as pure pass-throughs); oracle `fixture-world.ts` extracted (12 members md5-verified identical ×4 or ×3; ~1,100 preamble lines → 366) | `verify:compile` 0; targeted suites **before 81 files/719 tests = after 81/719**; extract+scripts sweep 26 files/291; `verify:lint` exactly **400**, topology exit 0 | Re-scan (`w6-rescan.json`, 13 candidates): `host` triple = the documented owner + 2 DISTINCT locals (cascade omits obligations — runtime.ts:109 reads `host.obligations?.()`; prove-refine inserts mid-array cut affecting source-order tiebreaks — reasons written in-file); source-builder score-5s = pre-existing cleared template-literal family. DISTINCT verdicts: `runLoader` ×4 per-suite wiring (not forced); extract's 4 temp-root lifecycles (V13 re-verified); `sleep` src copy is poll backoff, not the test helper. `index-v2.js` literal mirror documented in the probe header (it must not import a module that throws on absence). | done | +| W5 | Artifact wire contracts | 15 files — `commit.json` wire type + `decodeCommitRecord` owned by `published-set.ts` (producer CLI imports; 3 declarations → 1); `.animus` lock shape + `isLockHolderAlive` liveness owned beside it (writer emits via `lockBytes()` `satisfies` pin); `ANIMUS_ARTIFACT_DIR` owner established in `session-paths.ts` (4 consumers incl. CLI default + watcher ignores); oracle tether test `artifact-name-parity.test.ts` pins MANIFEST/STYLESHEET/COMMIT filenames + artifact dir (drift-calibrated both directions); `MANIFEST_ORIGIN` deleted | `verify:compile` 0; targeted 30 files/388 tests (8 new, fail-first proven: forged empty commit record vacuously certified trees; `JSON.parse('null')` threw past the decode contract; EPERM lock read as dead on BOTH sides → steal + hygiene-delete races — unified EPERM=alive); broad sweep 119 files/1154 pass; `verify:lint` **403→400** | Re-scan (`w5-rescan.json`, 13 candidates): CAUGHT one W5-introduced collision — generic-named module-private JSON guards in `published-set.ts` colliding with the W1 `_assertions` family; renamed to `isPublishedSet*` (compile 0 + 46 covering tests re-proven). Others pre-existing/cleared (reference-guard family, namespace-key false positives, `expectedEpoch`→W6). DECISIONS RECORDED: `payloads` gate tightened (arrays/null rejected — deliberate acceptance-gate change, no test pinned the hole); prune path now yields empty prunable set on undecodable record; test-file literals kept as independent witnesses (vacuity argument); oracle bin keeps local literals (tether covers drift; no runtime dist dependency added). NOTE: oracle test lane now needs `build:ts` (session dist import) — same dependency as existing `host-layer-parity.test.ts`. | done | +| W4 | `_assertions` promotion wave | 16 files — `writeLaneReceipt(path, claim)` promoted with STRUCTURAL inseparability (claim type cannot spell the engine triple; module-private guard runs first; lanes log the returned values); 4 local guard blocks deleted + a FIFTH copy discovered in `scripts/verify/packed.sh` inline `node -e` (narrower regex) moved into `e2e/packed-app/scripts/assert-build.ts`; receipt emitters added to svelte/vinext/react-router (+packed via the move, `packageForm:'packed'` finally used); `LaneHost` widened honestly (svelte/vinext/react-router); `v1-feature-drift` retired + `REGISTER_CATEGORIES` derived via `satisfies` (kills the runtime/type dual authority); `layerBlockBody` + `compact` + `installedHostVersion` promoted; svelte `ruleBody` SKIPPED with proven semantic difference (flat regex vs depth-counting) | `verify:compile` 0; `_parity`+`_assertions` 12 files/143 tests (+6 new); per-lane tsc 0×8 (vinext/react-router/packed scripts type-checked via scratch configs — those lanes' tsconfigs don't cover scripts/, non-vacuity proven via `import.meta.dirname` failure + `--listFiles`); `verify:lint` **404→403**; guard predicate proven over all 9 real lane configs + vacuity guard (fires on `engine:'v1'`); `bash -n packed.sh` clean | Re-scan (`w4-rescan.json`, 21 candidates): `emitLaneReceipt` ×6 verified thin lane-data wrappers over the promoted authority (DISTINCT convention, G3 documented in-file); `compact`/`escapeForRegExp` production-side equivalents (assemble-stylesheet.ts, asset-placeholders.ts) DISTINCT — consolidation would create a production→assertions edge, forbidden by the V1 ingress ruling. Agent contraction closeout: 9 claims, 0 unresolved, 5 new exported surfaces, 0 parallel paths. FLAGGED: `_assertions` internally holds 4 depth-counters across 2 unbalanced-input conventions (−1 vs truncate) — unifying changes gate behavior on malformed CSS, reported not forced; packed-app's undeclared root-hoist import edge documented in-file (workspace:\* would break the staged npm install). | done | +| W3 | Module-mocking migration (incl. C-016 seam) | 3 files — W3a STOPPED with instrument evidence (`setEngineApiOverride` cannot reach the G1-guarded loader-local engine; injected double 0-calls where tests expect 1; naive migration = 75%-vacuous suite). W3b: added `__setTurbopackLoaderEngineApiForTests` seam to `turbopack-loader.ts` mirroring the sibling fs seam (module-local ⇒ G1 discharged at the seam comment, cross-ref anti-slop C-016); both suites migrated; gauntlet keeps dual injection (session override + loader seam) per its own documented split | suites 23/23; next-plugin 29 files/200 tests; `verify:compile` 0; `verify:lint` **406→404**, `no-module-mocking` 2→0 repo-wide; live vacuity guard: uninjected scratch run reproduces the exact 2-failure 0-calls signature, injected run green | Re-scan (`w3-rescan.json`): 9 candidates, all pre-existing/queued (LoaderContext→W8; makeRoot/runLoader/sleep→W6). Agent's own contraction closeout: 7 claims, 0 unresolved, 1 new exported surface (the seam), 0 parallel runtime paths (old `engineApi` const has zero remaining production readers besides the seam default). NOTE for anti-slop campaign: C-016 gate discharged + both pending no-module-mocking rows cleared — reconcile that ledger's disposition. | done | +| W2 | Verification engine-driving | 8 source files + `_integration/CLAUDE.md` — both harnesses now drive `createV2EngineApi` + `buildAnalyzeProjectArgs` (18-slot passthrough by construction); local 14-slot type, hand-rolled mappings, `lastEngine`, parity-local `canonicalJson`/`EngineApi`/`parseTransformResult` deleted | `verify:compile` 0; `_parity` 6 files/50 tests; **`verify:parity` 0 — 65/65 units, 0 divergences, baselines + register.json untouched**; `verify:integration` 0 (165 pass + 1 todo; −1 test, see note); `verify:lint` **408→406** | Re-scan (`w2-rescan.json`): 4 candidates, all pre-existing/queued (ReverseProvenance→W7; FileEntry dup noted for W7; main/run = closed convention). canonicalJson collapse proven byte-identical on 520 baseline observables + 15 structured samples before deletion. Null-slot equivalence proven for all previously-dropped slots (emitterConfigJson literal null → `{}` → NAPI None). Deleted test `runPipeline retains a null selector-order NAPI slot` — its subject WAS the deleted shim; the 18-slot contract is owned by `buildAnalyzeProjectArgs`, pinned by vite-plugin + next-plugin `analyze-project-args.test.ts` (verified present). FLAGGED for owner: integration fixtures export 9 conditionAliases + 4 transformSources that the harness deliberately does not feed (under-testing, feeding = behavior change); parity's malformed-result TypeError proof moved to adapter ownership. | done | --- | +| `type JsonValue` (11 in-scope copies, 13 with `packages/system`) | vocabulary | `packages/_parity/src/engine-run.ts:31`; `packages/_integration/__tests__/manifest-shape.test.ts:16`; `packages/next-plugin/tests/{plugin-pipeline.test.ts:250,session-artifacts.test.ts:82,turbopack-orchestrator.test.ts:84}`; `packages/next-plugin/tests/webpack-gauntlet/{cache-epoch.test.ts:130,external-ingestion.test.ts:135,harness.ts:285}`; `packages/vite-plugin/tests/{appearance-bootstrap-injection.test.ts:116,svelte-source-lifecycle.test.ts:23}`; `e2e/svelte-app/scripts/assert-build.ts:26` | none — no exported `JsonValue` exists anywhere in the repo | **PROMOTE** (test/verification half) to `packages/_assertions`; production half resolved **DISTINCT** — six production JSON types each guard a different ingress and extract/cli declare zero (see `p1-uresolve-verification.md` V1) | Identical union in every copy (`null \| boolean \| number \| string \| JsonValue[] \| {…}`), in two spellings only (multi-line vs one-line). No copy narrows or extends the domain. Semantics are _identical_, not merely similar. · **dep:** `packages/_assertions` is the designated cross-boundary owner for shared verification vocabulary (AGENTS.md §One-Way Dependency Rule, line 21) and is already a declared dep of every `e2e/*` fixture. `_integration`, `_parity`, `next-plugin`, `vite-plugin` would need `@animus-ui/assertions` added as a devDep — legal (`packages/*` → `packages/*`). · **instrument:** `grep -rn '^(export )?(type\|interface) Json(Value\|Object\|Array\|Primitive)\b'` → 25 hits/13 files; `git diff HEAD -U0 \| grep '^+type JsonValue'` → 13 net-new | V | +| `type/interface JsonObject` (10 in-scope copies) | vocabulary | same files as above (`:24`, `:39`, `:252`, `:90`, `:85`, `:138`, `:143`, `:293`, `:118`, `:25`) | none | **PROMOTE** with `JsonValue` | Same index signature everywhere. Three copies are declared as `interface JsonObject` and seven as `type … = { [key: string]: JsonValue }` — a spelling split with no semantic cause. · **dep:** as above · **instrument:** same grep | V | +| `isJsonObject` (10 in-scope copies) | vocabulary | `packages/_integration/__tests__/manifest-shape.test.ts:64`; `packages/_parity/src/engine-run.ts:68`; `packages/next-plugin/tests/{plugin-pipeline.test.ts:262,session-artifacts.test.ts:124,turbopack-orchestrator.test.ts:87}`; `.../webpack-gauntlet/{cache-epoch.test.ts:140,external-ingestion.test.ts:151,harness.ts:316}`; `packages/vite-plugin/tests/{appearance-bootstrap-injection.test.ts:122,svelte-source-lifecycle.test.ts:29}` | `isRecord` at `packages/oracle/src/host/animus/manifest-types.ts:194` carries the _documented_ rationale for this exact test | **PROMOTE** alongside `JsonValue`; carry the `manifest-types.ts` doc comment to the promoted home | Every copy is `Object.prototype.toString.call(value) === '[object Object]'`. The only place the reasoning is written down (why the tag test rather than `typeof`, what it excludes: callables, boxed primitives, `Date`/`Map`) is `manifest-types.ts:184-193`. Ten copies inherit the behaviour without the reasoning. · **dep:** as above · **instrument:** `grep -rn 'function isJson(Object\|String\|Number\|Boolean\|Array\|Value)\b'` → 28 hits/14 files | V | +| `isJsonString` (7), `isJsonBoolean` (4), `isJsonNumber` (2) | vocabulary | `…manifest-shape.test.ts:68,72`; `…engine-run.ts:72,76,80`; `…session-artifacts.test.ts:128,132,136`; `…turbopack-orchestrator.test.ts:91,95`; `…cache-epoch.test.ts:144`; `…external-ingestion.test.ts:155`; `…harness.ts:320` | none | **PROMOTE** with the family | Identical `[object String]` / `[object Boolean]` / `[object Number]` tag tests. · **dep:** as above · **instrument:** same grep | V | +| `parseJsonObject` (3 copies) | vocabulary | `packages/_parity/src/engine-run.ts:84`; `packages/next-plugin/tests/plugin-pipeline.test.ts:266`; `packages/next-plugin/tests/session-artifacts.test.ts:150` | none | **PROMOTE**; the drift is the argument | Same body (`JSON.parse` → guard → throw). **Already drifted on failure behaviour**: two throw `TypeError`, `plugin-pipeline.test.ts:268` throws `Error`. Parameter naming differs (`boundary`/`label`/`artifactName`). · **dep:** as above · **instrument:** `grep -n -A7 'function parseJsonObject'` across the three files | V | +| `packages/next-plugin/tests/webpack-gauntlet/harness.ts:285-320` declares the JSON vocabulary while being the _shared harness_ for the gauntlet suite | vocabulary | itself (it is the sibling-file owner) | `cache-epoch.test.ts` and `external-ingestion.test.ts` sit in the same directory, import from `harness.ts`, and still redeclare all of `JsonValue`/`JsonObject`/`isJsonObject`/`isJsonString`. | intra-directory import already exists | **REUSE** — export the vocabulary from `harness.ts` even if the repo-wide promotion is declined; this one is a same-directory copy with an existing import edge | `grep -rn 'webpack-gauntlet/harness'`; harness import lines in both test files | +| `e2e/svelte-app/scripts/assert-build.ts:26` declares `JsonValue` | vocabulary | `packages/_assertions` (already imported by this file at line 6) | Identical union. This file crosses the `e2e/ → packages/` boundary in the sanctioned direction already, so the reuse path is open and in use for other symbols. | legal today; no new edge needed | **REUSE** once `_assertions` exports the vocabulary | `grep -rn 'animus-ui/assertions' e2e/` → this file at line 6 | +| Production-code half of the JSON vocabulary: `packages/extract`, `packages/oracle`, `packages/cli`, plugin `src/` cannot depend on the test-only `_assertions` | vocabulary | — | The domain fact ("what `JSON.parse` yields") is one fact; the two consumer populations (shipped libraries vs verification code) have different dependency budgets. `_assertions` is private-but-`packages/`-resident, so a runtime dep from `extract` is topologically legal but semantically wrong (assertion utilities in a published library's graph). | needs a decision | **UNRESOLVED** — exact question: _does the shipped JSON-value vocabulary get its own tiny owner (e.g. `packages/extract/pipeline/json-value.ts`, already the lowest package in the graph), or does each shipped package keep a local domain-named copy on purpose?_ Answer before executing the PROMOTE above, so the two halves do not end up as two authorities. | `packages/_assertions/package.json` (`"private": true`, no deps); AGENTS.md:9,21 | + +--- + +### Cluster: oracle-local JSON/reference guard families (campaign-introduced) + +The claim: _`packages/oracle/src/host/animus/` needs three separate definitions of the JSON value domain, one per file._ + +| claim | lane | file:line | existing owner/analog | disposition | evidence | status | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `ArtifactJsonValue` / `ArtifactJsonObject` | vocabulary | `packages/oracle/src/host/animus/loader.ts:20,28` | `ManifestJsonValue` / `ManifestJsonObject`, **exported**, in the _same directory_, at `packages/oracle/src/host/animus/manifest-types.ts:29,38` | **REUSE** — delete the `Artifact*` pair, import `ManifestJson*`; decide the `undefined` question once | Structurally the same union. Only difference: `ManifestJsonObject`'s value type admits `\| undefined` (documented at manifest-types.ts:24-27 as "an omitted key"). `loader.ts`'s copy drops that, so the two disagree about how an absent key is typed — in files that read the _same_ `manifest.json`. · **dep:** same directory, same package; `loader.ts` already imports `./errors` and `./host` · **instrument:** `sed -n '14,32p' loader.ts` vs `sed -n '19,40p' manifest-types.ts`; `git diff HEAD` shows the `Artifact*` pair is net-new | V | +| `ReplacementJsonValue` / `ReplacementJsonObject` | vocabulary | `packages/oracle/src/host/animus/replacement.ts:45,53` | same `ManifestJson*` owner | **REUSE** | Byte-identical to the `Artifact*` pair modulo the name prefix. Three copies of one union inside one directory. · **dep:** as above · **instrument:** `sed -n '40,56p' replacement.ts`; `git diff HEAD` shows net-new | V | +| `isArtifactObject` / `isArtifactString` (`loader.ts:34,40`), `isReplacementObject` / `isReplacementString` (`replacement.ts:57,63`), `isSessionObject` / `isSessionString` / `isSessionNumber` / `isSessionBoolean` (`cli/session.ts:129,133,137,141`) | vocabulary | `isRecord` at `manifest-types.ts:194` — which the campaign **deleted from `cli/session.ts` and replaced with four locals** | Every one is `Object(value) === value && Object.prototype.toString.call(value) === '[object …]'`. Prefix differs; predicate target type differs; runtime behaviour is identical. The campaign's own diff shows `-const isRecord = (value: unknown)…` replaced by `+const isSessionObject/String/Number/Boolean` — one authority became four. | intra-package | **REUSE** — one generic-parameter guard family (`(value: Value): value is Value & …`) already type-checks under the anti-slop rules, as `isCanonicalReference` at `core/identity.ts:86` and `isConfigReference` at `cli/config.ts:90` demonstrate | `git diff HEAD -- packages/oracle/src/cli/session.ts \| grep -E '^[+-]const is'`; `grep -rc 'Object.prototype.toString.call'` → 4 in `cli/session.ts`, 2 each in `loader.ts`/`replacement.ts` | +| `Object(value) === value` reference-guard family, repo-wide | vocabulary | 21 files; named instances include `isConfigReference` (`packages/cli/src/config.ts:90`), `isCompilerReference` (`packages/extract/pipeline/svelte-source-adapter.ts:199`), `isCanonicalReference` (`packages/oracle/src/core/identity.ts:86`), `isComparatorPayload` (`packages/oracle/src/cli/render.ts:105`), `isPointLiteral` (`packages/oracle/src/engines/runtime.ts:58`), `isProofString` (`packages/oracle/src/engines/prove.ts:104`), `isEmitterJsonObject` (`packages/vite-plugin/tests/plugin-context.test.ts:26`), `isForwardedString` (`packages/next-plugin/tests/turbopack-orchestrator.test.ts:160`), `isLoaderEntry` (`…turbopack-orchestrator.test.ts:154`) | none repo-wide | **DISTINCT** — domain-named guards at their own ingress are the intended shape per the V1 ingress ruling; name the policy once in the P3 owners ratchet, no code edit | One runtime fact ("is this a keyed reference") expressed 21 times under 21 domain names. Domain naming is defensible where the predicate's _target type_ is domain-specific (`isPointLiteral`, `isProofString`) — the guard is doing real narrowing work there. It is not defensible where the target is a generic JSON object (`isEmitterJsonObject`, `isLoaderEntry`, `isForwardedString`, `isArtifactObject`). · **dep:** crosses four packages · **instrument:** `grep -rn 'Object(value) === value\|Object(value) !== value'` → 30 hits / 21 files (list in evidence appendix) | A | +| `parseArtifactJson` (`loader.ts:32`), `parseReplacementJson` (`replacement.ts:69`) | vocabulary | each other; both are `(text: string) => JSON.parse(text)` typed to the local union | Pure pass-through with one call site each (digest: "pass-through wrapper"). The only content is the return type annotation, which the promoted `ManifestJsonValue` would supply at the call site. | intra-file | **INLINE** | digest rows score=7; `sed -n '25,50p' loader.ts`, `sed -n '65,72p' replacement.ts` | +| `isCanonicalArray` (`packages/oracle/src/core/identity.ts:134`) vs `isDiffEntryList` (`packages/oracle/src/cli/render.ts:114`) | vocabulary | each other | Both are `Array.isArray(value)` with a `` predicate. Target types genuinely differ (`readonly CanonicalValue[]` vs `readonly SemanticDiffEntry[]`), and `render.ts:108-113` documents _why_ it deliberately does not re-derive entry structure. Highest-scoring digest rows (9) but a scanner false positive on body shape. | intra-package | **DISTINCT** | `sed -n '95,130p' render.ts`; `sed -n '130,140p' core/identity.ts` | +| `isIdentStart`/`isIdentChar` (`packages/oracle/src/host/animus/selector.ts:35,37`) vs `isJsxAttributeName` (`packages/extract/pipeline/svelte-source-adapter.ts:727`) | vocabulary | none | Scanner matched on "one-line regex `.test()`". The grammars are different languages: CSS ident (`[A-Za-z_]`, `[A-Za-z0-9_-]`) vs JSX attribute name (`^[A-Za-z_$][A-Za-z0-9_$-]*$`, admits `$`). Different packages, different specs. | n/a | **DISTINCT** — scanner false positive; the digest's top-scored rows (9) are all this shape | `sed -n '33,40p' selector.ts` vs `sed -n '727,730p' svelte-source-adapter.ts` | + +--- + +### Cluster: artifact-filename and exit-code contract constants + +The claim: _the on-disk artifact filenames may be spelled independently by the producer and the consumer._ **This is the highest-consequence row in the lane.** + +| claim | lane | file:line | existing owner/analog | disposition | evidence | status | +| ------------------------------------------------------------------------------------- | ---------- | ---------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | +| `MANIFEST_FILE = 'manifest.json'`, `COMMIT_FILE = 'commit.json'` (hardcoded literals) | vocabulary | `packages/oracle/src/host/animus/loader.ts:8,12` | `packages/cli/src/writer.ts:41,42`, which **re-exports the session constants** (`MANIFEST_ARTIFACT`, `CLI_COMMIT_ARTIFACT` from `@animus-ui/extract/session`) rather than spelling them, under the comment: _"a drifted local literal on either side breaks the contract silently"_ (writer.ts:35-38) | **DISTINCT** + tether test — oracle is private with extract as devDep whose ./session entry is built dist (runtime import couples oracle to build:ts; repo precedent `host-layer-parity.test.ts`); pin spellings with a tether test; nested REUSE: collapse `obligations.ts:19 MANIFEST_ORIGIN` onto `loader.ts:8` (see `p1-uresolve-verification.md` V2) | The CLI **writes** these files; the oracle **reads** them (`loadAnimusArtifacts`, `places/snapshot.ts:152 ARTIFACT_FILES`). Same bytes, same contract, two authorities — and the producer's source explicitly warns about exactly the failure mode the consumer instantiates. A third authority exists in `packages/oracle/__tests__/{places-session,places-warm,places-compare}.test.ts`, which hardcode `'manifest.json'` again rather than importing the oracle's own constant. · **dep:** `@animus-ui/extract` is already a devDependency of `packages/oracle` (`packages/oracle/package.json:15`), so importing the session constants is dependency-legal today. It would become a runtime dep for the `animus-oracle` bin. · **instrument:** `sed -n '35,45p' packages/cli/src/writer.ts`; `sed -n '1,14p' packages/oracle/src/host/animus/loader.ts`; `grep -rn "manifest.json'" packages/oracle/__tests__` → 5 hardcoded literals; `cat packages/oracle/package.json` | V | +| `EXIT_OK=0`, `EXIT_USAGE=2`, `EXIT_ENVIRONMENT=3` | vocabulary | `packages/cli/src/index.ts:24,26,27` and `packages/oracle/src/cli/run.ts:64,66,67` | each other | **DISTINCT** — but note the agreement is undocumented and unpinned; if it is meant to be a repository convention, that belongs in AGENTS.md rather than in duplicate constants | Two separate binaries (`animus`, `animus-oracle`) with independent exit-code policies. Values agree on the three shared codes and diverge where the domain diverges (`EXIT_EXTRACTION=1` vs `EXIT_DISPROVED=1`; oracle adds `EXIT_UNSETTLED=4`). Two CLIs are entitled to their own contracts; the agreement on 0/2/3 is convention, not a shared fact. · **dep:** none needed · **instrument:** `sed -n '18,32p' packages/cli/src/index.ts`; `sed -n '58,75p' packages/oracle/src/cli/run.ts` | A | + +--- + +### Cluster: canonical form / comparison normal form + +The claim: _"the canonical byte form of a value" may be defined more than once inside one package._ Repo memory records canonicalization drift as a prior live defect ("oracle comparators share the writer's canonical form"). + +| claim | lane | file:line | existing owner/analog | disposition | evidence | status | +| -------------------------------------------------------- | ---------- | -------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------ | +| `canonicalJson` (local) | vocabulary | `packages/_parity/src/engine-run.ts:354` | `canonicalJson`, **exported from the same package**, at `packages/_parity/src/content-hash.ts:18` | **REUSE** — import `canonicalJson` from `./content-hash`; if the throw-on-unserializable behaviour is wanted, it belongs in the owner | Both produce key-sorted JSON. Implementations differ: `content-hash.ts` pre-canonicalizes recursively (`canonicalize` at :5, arrays mapped) then stringifies; `engine-run.ts` sorts inside a `JSON.stringify` replacer and additionally throws `TypeError` when the result is `undefined`. Both feed the _same_ parity pipeline — `engine-run.ts` produces `systemPropMapJson`/`sheetsJson`/`dynamicPropsJson`/`componentFragmentsJson`, which `content-hash.ts:hashArtifact` then hashes. Two canonical forms, one hash chain. `engine-run.ts` does not import `./content-hash` at all. · **dep:** same package, sibling module; four other `_parity` modules already import `./content-hash` · **instrument:** `grep -rn 'canonicalJson' packages/_parity/`; `grep -n "^import" packages/_parity/src/engine-run.ts` (no `content-hash`); `git show HEAD:…engine-run.ts` confirms pre-existing, not campaign-introduced | V | +| `canonicalJson` (oracle) | vocabulary | `packages/oracle/src/core/identity.ts:232` | the two `_parity` copies | **DISTINCT** — but the name collision across three sites is a live hazard: importing the wrong `canonicalJson` silently swaps a strict encoder for a lenient one | Genuinely different contract: refuses NaN/Infinity/bigint/functions/symbols/non-plain objects with a loud `TypeError` per DESIGN §8, because it backs content-addressed world ids. `_parity`'s is a comparison normal form for parity artifacts. Same name, different guarantees, different packages, no shared consumer. · **dep:** separate packages, no edge · **instrument:** `sed -n '218,246p' packages/oracle/src/core/identity.ts` vs `sed -n '1,32p' packages/_parity/src/content-hash.ts` | A | +| `compact(value) => value.replace(/\s+/g,'')` | vocabulary | `e2e/svelte-app/scripts/assert-build.ts:120` | `packages/_assertions/src/assert-appearance.ts:27` (module-private), documented as _"Collapse whitespace so a minified and a pretty-printed form compare equal"_ | **REUSE** — export `compact` from `_assertions`; the reuse edge exists and is in use in this exact file | Byte-identical. This is the comparison normal form for CSS assertions — i.e. the thing that decides whether two stylesheets are "the same". Two copies, 15 call sites each. · **dep:** `e2e/svelte-app/scripts/assert-build.ts:6` **already imports `@animus-ui/assertions`** · **instrument:** `grep -rnE 'function compact\b'`; `grep -rn 'animus-ui/assertions' e2e/` | A | +| `canonicalizeCss(css) => css.replace(/\s+/g,' ').trim()` | vocabulary | `packages/vite-plugin/tests/dev-lane/scenario.ts:210` | `compact` above | **DISTINCT** — dev-lane form is _stricter_ not laxer (whitespace-only vs lightningcss-minify), correct for cold-vs-incremental same-engine comparison; reuse also illegal (module-private + vite-plugin has no devDeps) (V5) | Same _purpose_ ("whitespace-insensitive comparison key for two served stylesheets") but a **different normal form**: collapse-to-single-space vs delete-all-whitespace. Two stylesheets can compare equal under one and unequal under the other. · **dep:** `vite-plugin` does not declare `@animus-ui/assertions` · **instrument:** `sed -n '205,215p' packages/vite-plugin/tests/dev-lane/scenario.ts` vs `assert-appearance.ts:26-29` | A | +| `escapeForRegExp` / `escapeRegExp` | vocabulary | `packages/_assertions/src/assert-css.ts:369`; `packages/extract/pipeline/asset-placeholders.ts:15` | each other | **DISTINCT** — a 1-line escaping primitive with no domain meaning; duplication is cheaper than a new shared package. Recorded so the pair is not mistaken for an authority collision. | Byte-identical bodies (`/[.*+?^${}()\|[\]\\]/g` → `'\\$&'`). Both module-private. Different packages with no legal shared owner below both (`_assertions` is test-only; `extract` is a shipped library and must not depend on it). · **dep:** no legal shared owner · **instrument:** `sed -n '365,375p' assert-css.ts` vs `sed -n '13,18p' asset-placeholders.ts` | A | +| `stripLeadingLayerDeclaration` | vocabulary | `packages/extract/pipeline/assemble-stylesheet.ts:70` | flagged by the scanner against `compact` | **DISTINCT** | Unrelated: strips a leading `@layer …;` statement. Scanner false positive on "one-line `.replace()`". · **dep:** n/a · **instrument:** `sed -n '64,73p' assemble-stylesheet.ts` | A | + +--- + +### Cluster: engine / NAPI contract types + +The claim: _the shape of the extraction engine and its NAPI argument order may be modelled independently by each verification package._ + +| claim | lane | file:line | existing owner/analog | disposition | evidence | status | +| ------------------------------------------------------------ | ---------- | -------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------ | +| `interface V2ExtractEngine` | vocabulary | `packages/_integration/__tests__/run-pipeline.ts:24` | `packages/extract/pipeline/engine-adapter.ts:26`, **exported** | **REUSE** | Byte-identical member list **and byte-identical doc comment** (`/** The stateful v2 engine handle produced by \`new native.ExtractEngine(...)\`. \*/`) — the copy was made by transcription. · **dep:** `\_integration`already depends on`@animus-ui/extract`; `engine-adapter.ts`explicitly documents that it "never imports from either plugin, webpack, or vite", so the dependency direction is`\_integration → extract`, which is legal · **instrument:** `sed -n '24,32p' engine-adapter.ts`vs`sed -n '22,30p' run-pipeline.ts` | A | +| `type AnalyzeProject` (hand-written 14-arg positional tuple) | vocabulary | `packages/_integration/__tests__/run-pipeline.ts:43` | `AnalyzeProjectArgs`, exported from `packages/extract/pipeline/analyze-project-args.ts:11` via `pipeline/index.ts:12` — and this is the module `engine-adapter.ts` names as "the NAPI contract" | **REUSE** — adopt the `_parity` shape (`(...args: AnalyzeProjectArgs) => string`) | `packages/_parity/src/engine-run.ts:103` models the same thing correctly: `type AnalyzeProject = (...args: AnalyzeProjectArgs) => string`. `_integration`'s copy retypes all 14 positional parameters by hand. Argument _order_ is the fact; a hand-written copy can drift from the NAPI contract without any compile error. · **dep:** `_integration → extract` legal and already established · **instrument:** `grep -n 'AnalyzeProjectArgs' packages/extract/pipeline/*.ts`; `sed -n '43,60p' run-pipeline.ts` vs `sed -n '103,104p' engine-run.ts` | A | +| `interface EngineApi` | vocabulary | `packages/_parity/src/engine-run.ts:105` | `packages/extract/pipeline/engine-adapter.ts:44`, exported | **DISTINCT** — rename the local one (e.g. `ParityEngineFacade`); the collision invites an import of the wrong `EngineApi` in a package that already imports from `@animus-ui/extract/pipeline` | Same name, genuinely different surfaces: extract's is the engine-agnostic API (`loadSystemModule`, `scanKeyframesExports`, `extractFacts?`, `analyzeProject`); `_parity`'s is a three-member harness façade (`analyzeProject`, `transformFile`, `clearAnalysisCache`). · **dep:** legal either way · **instrument:** `sed -n '38,60p' engine-adapter.ts` vs `sed -n '105,115p' engine-run.ts` | A | +| `type FileEntry = { path; source; hash }` | vocabulary | `packages/extract/session/extraction-session.ts:158` | `OriginalSourceEntry` at `packages/extract/pipeline/source-ingestion.ts:20`, **exported**, structurally identical, and carrying the load-bearing doc _"Hash of the raw original source, never a generated projection"_ | **REUSE** — the whole point of the exported trio is that "which hash" is the fact | Same package, sibling directory. The exported vocabulary deliberately distinguishes `RawSourceEntry` (optional hash) / `OriginalSourceEntry` (raw hash) / `AnalysisSourceEntry` (parser-ready hash) — a three-way distinction the local `FileEntry` erases, so a caller cannot tell which hash a `FileEntry` carries. · **dep:** `session/` already imports from `pipeline/` · **instrument:** `sed -n '13,32p' source-ingestion.ts` vs `sed -n '155,160p' extraction-session.ts` | A | +| `interface FileEntry { path; source }` (×2) | vocabulary | `packages/_integration/fixtures/read-fixtures.ts:4`; `packages/_integration/__tests__/keyframes-binding-substitution.test.ts:24` | `RawSourceEntry` at `packages/extract/pipeline/source-ingestion.ts:13` | **REUSE** | Identical two-member shape describing "what `analyzeProject` expects" — which is exactly `RawSourceEntry` minus the optional hash. Two local copies inside one package, neither importing the owner. · **dep:** `_integration → extract` legal · **instrument:** `grep -rnE '(interface\|type) (FileEntry\|SourceEntry\|RawSourceEntry)\b' packages/extract/` | A | +| `type ReverseProvenance` (×2), `type SystemPropMap` | vocabulary | `packages/_integration/__tests__/manifest-shape.test.ts:41,53`; `packages/_parity/src/engine-run.ts:53` | none exported | **PROMOTE** → `packages/extract/pipeline/manifest-schema.ts` (producer-owned); readers disagree today (`components` required vs optional; `_parity` accepts snake_case AND camelCase); `_parity`/`_integration`/`cli` become REUSE, oracle + rollup `.mjs` stay DISTINCT projections (V3) | `ReverseProvenance = { [parentId: string]: string[] }` in both; two verification packages independently model the same manifest channel. `SystemPropMap` is single-site. Neither package can see the other (no edge), and the true owner is the manifest schema itself, which is untyped JSON by design (`manifest-types.ts:16-19`). · **dep:** no edge between `_integration` and `_parity` · **instrument:** digest rows; `sed -n '16,26p' packages/oracle/src/host/animus/manifest-types.ts` | V | + +--- + +### Cluster: assertion vocabulary and the `_assertions` owner + +| claim | lane | file:line | existing owner/analog | disposition | evidence | status | +| ---------------------------------------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | +| `assertNoUnresolvedTokens` (second definition) | vocabulary | `packages/_integration/__tests__/assert-no-unresolved-tokens.ts:46` (exported) | `packages/_assertions/src/assert-css.ts:117` (exported; `_assertions` is the AGENTS.md-designated owner of shared assertion vocabulary) | **DISTINCT, rename required** — the two invariants are both wanted; the name belongs to the designated owner. Rename the `_integration` one to what it actually asserts (e.g. `assertNoBareTokenValues`). | **Same name, different invariants.** `_assertions` detects unsubstituted `{colors.x}` template placeholders via a fixed pattern list. `_integration` detects a _bare_ token name emitted as a CSS value (`background-color: primary;`), derived from the live theme fixture. Failure behaviour also differs: `throw new AssertionError` vs vitest `expect().not.toMatch`. A reader importing the wrong one gets a silently weaker check under a name that reads like the strong one. · **dep:** both are `packages/*`; `_integration` does not declare `@animus-ui/assertions` · **instrument:** `sed -n '107,135p' assert-css.ts` vs `sed -n '1,60p' assert-no-unresolved-tokens.ts`; AGENTS.md:21 | A | +| `assertDir(path, label)` (×3) | vocabulary | `e2e/next-app/scripts/assert-build.ts:35`; `e2e/next16-app/…:24`; `e2e/packed-app/…:28` | `packages/_assertions/src/find-build-assets.ts` already owns the filesystem-touching helpers (`findCssFiles`, `readAsset`, `readAllConcat`), and all three files already import `@animus-ui/assertions` | **REUSE** — export from `_assertions/find-build-assets.ts` | Same body: `stat` → `isDirectory()` → throw `AssertionError`. Already throws the owner's error type, so the coupling exists; only the function is copied. · **dep:** edge exists and is in use · **instrument:** digest rows; `sed -n '33,46p' e2e/next-app/scripts/assert-build.ts`; `grep -rn 'animus-ui/assertions' e2e/` | A | +| `emitLaneReceipt` (×4) | vocabulary | `e2e/next-app/scripts/assert-build.ts:46`; `e2e/next16-app/…:35`; `e2e/vite-app/…:105`; `scripts/assert-showcase-build.ts:25` | `writeLaneReceipt` + `LaneReceipt` at `packages/_assertions/src/receipt.ts:15,4` | **DISTINCT** — recorded as the counter-example: this is what correct reuse looks like in this repo | The shared _fact_ (receipt shape, write mechanics) is already owned and reused by all four. Each local function only gathers lane-specific evidence — a different config file to inspect, a different host version source, a different lane name. Genuinely per-lane. · **dep:** already correct · **instrument:** `cat packages/_assertions/src/receipt.ts`; `sed -n '46,80p' e2e/next-app/scripts/assert-build.ts` | A | +| `walk` (×6) | vocabulary | `packages/_assertions/src/find-build-assets.ts:19`; `packages/extract/pipeline/svelte-source-adapter.ts:379`; `packages/oracle/src/core/predicate.ts:213`; `packages/oracle/src/engines/speculate.ts:100`; `scripts/hygiene/reconcile-after-knip.ts:77`; `scripts/verify/topology.ts:411` | none | **DISTINCT** — name-only recurrence | Six different trees: build-output directories, Svelte AST, predicate trees, speculation frontier, TS AST, import graph. Nothing shared beyond the verb. · **dep:** n/a · **instrument:** `grep -rn 'function walk\b'` across the six sites | A | + +--- + +### Cluster: `scripts/hygiene` oxlint diagnostic vocabulary (pre-existing, not campaign) + +`scripts/` is clean in this worktree (`git status --porcelain -- scripts/` → empty), so everything here predates the campaign. `scripts/hygiene/_receipts.ts` is the existing shared module in the same directory, and `delete-unused.test.ts` already imports from `./delete-unused.ts` — the reuse edges exist. + +| claim | lane | file:line | existing owner/analog | disposition | evidence | status | +| -------------------------------------------------------------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | +| `OxlintSpan` / `OxlintLabel` / `OxlintDiagnostic` (×3 each), `OxlintReport` (×2) | vocabulary | `scripts/hygiene/_emit-oxlint-receipts.ts:33,39,40,46`; `scripts/hygiene/delete-unused.ts:135,141,142,149`; `scripts/hygiene/delete-unused.test.ts:31,37,38` | none; `_receipts.ts` is the sibling shared module | **PROMOTE** to a single `scripts/hygiene/_oxlint-report.ts` (or into `_receipts.ts`) | Three independent models of one _external_ contract (oxlint's JSON output). **Already drifted**: `OxlintReport.diagnostics` is optional in `_emit-oxlint-receipts.ts:46` and required in `delete-unused.ts:149`; `delete-unused.test.ts:38` adds `severity`/`causes`/`related`/`url` that neither of the others carries. An oxlint format change needs three synchronized edits, and nothing enforces that. · **dep:** same directory; import edge already exists · **instrument:** `sed -n '25,50p' _emit-oxlint-receipts.ts`; `sed -n '125,155p' delete-unused.ts`; `sed -n '25,50p' delete-unused.test.ts` | V | +| `classifyUnusedVar` (×2) | vocabulary | `scripts/hygiene/_emit-oxlint-receipts.ts:59`; `scripts/hygiene/delete-unused.ts:396` | each other | **PROMOTE** — highest-risk row in this cluster; the emitter and the deleter must not disagree about what a diagnostic means | **Already drifted** (bodies hash differently: `5f825ca9…` vs `2e65ae81…`). This is the function that decides _which oxlint message means "unused variable"_ — i.e. what the automated deleter is allowed to delete and what the receipt claims was deleted. Two authorities on the same classification, in a tool that mutates source. · **dep:** same directory · **instrument:** `md5` over `awk NR>=59&&NR<=90` vs `NR>=396&&NR<=427` | V | +| `readStdin` (×3) | vocabulary | `scripts/hygiene/_emit-knip-receipts.ts:67`; `_emit-oxlint-receipts.ts:48`; `delete-unused.ts:170` | none | **PROMOTE** to `_receipts.ts` | Byte-identical (`md5 0ac369d0…` confirmed for the `unwrapCode` pair; `readStdin` bodies visually identical across all three). · **dep:** same directory · **instrument:** `sed -n` over all three sites | V | +| `unwrapCode` (×2), `langFor` (×2) | vocabulary | `_emit-oxlint-receipts.ts:54` / `delete-unused.ts:386`; `delete-unused.ts:48` / `reconcile-after-knip.ts:43` | each other | **PROMOTE** | Byte-identical (`unwrapCode` md5 `0ac369d0e328f23e76f2f2e33ea037dc` on both; `langFor` identical line-for-line). `langFor` encodes the extension→parser-language mapping, a real repository fact used to drive AST parsing in two tools. · **dep:** same directory · **instrument:** `md5` comparison; `sed -n '48,58p'` on both | V | +| `type Node` (×2), `function isNode` | vocabulary | `scripts/hygiene/delete-unused.ts:32,64`; `scripts/hygiene/reconcile-after-knip.ts:31` | each other | **REUSE** — travels with the `langFor`/`Oxlint*` promotion | Two local models of the parser's node type, used by two tools that walk the same ASTs. · **dep:** same directory · **instrument:** digest rows; `grep -rn 'function isNode'` | A | + +--- + +### Cluster: `scripts/verify` package-manifest and graph vocabulary (pre-existing) + +| claim | lane | file:line | existing owner/analog | disposition | evidence | status | +| ------------------------------------------------------------------------ | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | +| `PackageManifest` (×2) + `Manifest` (×2) — four models of `package.json` | vocabulary | `scripts/verify/workspace-graph.ts:6`; `scripts/verify/packed-graph.ts:47` (exported); `scripts/verify/owner-graph.test.ts:22`; `scripts/verify/workers-config.test.ts:16` | `workspace-graph.ts` is the de-facto owner (it exports `discoverWorkspaceManifests` / `WorkspaceEntry` and is imported by `owner-graph.test.ts:19`) | **PROMOTE** — one `PackageManifest` in `workspace-graph.ts` with the union of read fields, all four importing it | Four disjoint field sets over one file format: `workspace-graph` has `name/main/module/types/exports/dependencies/optionalDependencies`; `packed-graph` requires `name/version` and adds `peerDependencies`; `owner-graph.test` has `name/scripts`; `workers-config.test` has `scripts` only. No copy is wrong; collectively they mean nobody owns "what fields of `package.json` this repo's verification gates read". · **dep:** all in one directory; `owner-graph.test.ts` already imports `./workspace-graph` · **instrument:** `sed -n '1,30p' workspace-graph.ts`; `sed -n '40,55p' packed-graph.ts`; `sed -n '15,25p' owner-graph.test.ts`; `sed -n '14,20p' workers-config.test.ts` | V | +| `readManifest(path)` (×2) | vocabulary | `scripts/verify/owner-graph.test.ts:131`; `scripts/verify/workspace-graph.ts:27` | `workspace-graph.ts` (not exported today) | **REUSE** — export it | Identical body (`JSON.parse(readFileSync(path,'utf8')) as …`). The consumer already imports two other symbols from the owner module. · **dep:** edge exists · **instrument:** `grep -rnE 'function readManifest'`; `sed -n '15,22p' owner-graph.test.ts` | A | +| `stripComments(source)` (×2, both exported) | vocabulary | `scripts/verify/rust-policy.ts:131`; `scripts/verify/topology.ts:133` | each other | **DISTINCT** + rename to disambiguate — the literal-aware version is _worse_ for Rust (lifetimes `&'a str` read as string openers; raw strings mishandled); both miss nested block comments (V4) | Same name, same purpose ("source text with comments removed"), **different implementations**: `topology.ts` tracks string/template literals (`"`/`'`/`` ` ``) before stripping; `rust-policy.ts` does not. Both feed verification gates that grep the stripped text, so a `//` inside a string literal is invisible to one gate and visible to the other. Two authorities on what counts as a comment, driving two policy gates. · **dep:** same directory · **instrument:** `sed -n '131,142p' rust-policy.ts` vs `sed -n '133,145p' topology.ts` | V | +| `visit` (×4), `RootTask` (×2), `source` (×3), `Node`-walkers | vocabulary | `scripts/hygiene/reconcile-after-knip.ts:190`; `scripts/verify/{owner-graph.test.ts:84, packed-graph.ts:144, workspace-graph.ts:137}`; `RootTask` at `extract-test-enumeration.test.ts:33` / `owner-graph.test.ts:33`; `source` at `e2e/react-router-app/scripts/config.test.ts:7`, `e2e/vinext-app/scripts/config.test.ts:7`, `scripts/verify/workers-config.test.ts:26` | none | **DISTINCT** for `visit` and `source` (local closures / cross-boundary). **REUSE** for `RootTask` (both sites are in `scripts/verify/`, both model the same `package.json` task schema). | `visit` is a local DFS closure over four different graphs (dist closure, packed closure, owner graph, knip graph) — the traversal is inlined into each graph's own recursion, not a shared algorithm. `RootTask` (`{command?, dependsOn?}`) is a genuine two-site duplicate of the `vp` task-graph schema. `source(path)` is `readFileSync` + an existence assertion, ×3. · **dep:** `e2e/*` files cannot import `scripts/` · **instrument:** digest rows; `sed -n '26,35p' workers-config.test.ts` | A | + +--- + +### Cluster: next-plugin loader and webpack model types + +| claim | lane | file:line | existing owner/analog | disposition | evidence | status | +| ------------------------------------------------------------------------------------------------------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | +| `type LoaderContext` (×2, production code) | vocabulary | `packages/next-plugin/src/loader.ts:25`; `packages/next-plugin/src/turbopack-loader.ts:39` | `packages/next-plugin/src/loader-core.ts` is the shared owner and already exports `LoaderPolicyOptions` (:8) | **REUSE** — one contract: Turbopack follows the webpack loader contract (`turbopack-config.ts:128-133`); put generic `LoaderContextBase` in `loader-core.ts`, which already owns the symmetric options half (D5) | Shared members: `resourcePath`, `rootContext`, `getOptions`, `addDependency?`. Divergent members are real: `loader.ts` has `mode?: 'development'\|'production'\|'none'`; `turbopack-loader.ts` has `async?: () => LoaderCallback`. Options type differs (`LoaderPolicyOptions` vs `TurbopackLoaderOptions extends LoaderPolicyOptions`). Runtime contexts differ (webpack process vs isolated Turbopack worker, per the G1 comment at turbopack-loader.ts:52-57). · **dep:** same package, `loader-core.ts` is imported by both · **instrument:** `sed -n '25,35p' loader.ts` vs `sed -n '39,50p' turbopack-loader.ts`; `grep -n 'export' loader-core.ts` | V | +| `type WebpackSource` (×2) | vocabulary | `packages/next-plugin/src/plugin.ts:27`; `packages/next-plugin/tests/plugin-pipeline.test.ts:142` | each other | **DISTINCT** — the asymmetry is correct and load-bearing | The test derives it (`Parameters[1]`) from the plugin's own hook types; the production file hand-writes `{ source(): string\|Buffer; size(): number }`. The derived one is anchored to reality; the hand-written one is the model under test. Deriving both would make the test vacuous. · **dep:** same package · **instrument:** `sed -n '25,32p' plugin.ts` vs `sed -n '138,145p' plugin-pipeline.test.ts` | A | +| `AsyncHandler` / `CompilationHandler` (×2 each) | vocabulary | `packages/next-plugin/tests/plugin-needbuild.test.ts:121,122`; `packages/next-plugin/tests/plugin-pipeline.test.ts:131,132` | each other | **DISTINCT**, low risk — but note the shared names invite the assumption that both are derived | `plugin-pipeline` derives them from the real plugin's tap signatures (`Parameters<…tapPromise>[1]`); `plugin-needbuild` hand-writes structural equivalents for its fake compiler. Same names, different derivation strategies. The hand-written pair can drift from webpack's actual signature without a compile error. · **dep:** same directory · **instrument:** `sed -n '118,133p' plugin-needbuild.test.ts` vs `sed -n '129,143p' plugin-pipeline.test.ts` | A | +| `WatchIgnoreMatcher` (×2), `FixtureWebpack` (×2), `LoaderSingletonSlots` (×2), `Workspace` (×2), `class FakeWatcher` (×2) | vocabulary | `plugin-needbuild.test.ts:118` / `webpack-gauntlet/harness.ts:62`; `webpack-gauntlet/{cache-epoch.test.ts:88,harness.ts:102}`; `loader-css-import.test.ts:29` / `loader-epoch-guard.test.ts:49`; `svelte-source-lifecycle.test.ts:131` / `watch-external-ingestion.test.ts:79`; `turbopack-external-watchers.test.ts:28` / `turbopack-watcher-registration.test.ts:26` | `packages/next-plugin/tests/singleton-fixtures.ts` and `webpack-gauntlet/harness.ts` are the two existing shared test modules | **REUSE** for `FixtureWebpack` and `WatchIgnoreMatcher` (harness is the owner and is already imported); **DISTINCT** for `FakeWatcher`/`Workspace`/`LoaderSingletonSlots` pending a check of whether the two fakes model the same watcher lifecycle | Pairwise duplicates of fake-bundler surface types. `FixtureWebpack` is the sharpest case: `cache-epoch.test.ts` already imports from `harness.ts` (same directory) and still redeclares the type the harness exports the compiler for. · **dep:** intra-package, edges exist · **instrument:** digest rows; `grep -rn 'webpack-gauntlet/harness\|singleton-fixtures' packages/next-plugin/tests/` | A | +| `sleep(ms)` (×2) | vocabulary | `packages/next-plugin/src/turbopack-loader.ts:267`; `packages/next-plugin/tests/webpack-gauntlet/cache-epoch.test.ts:83` | each other | **DISTINCT** — a timing primitive with no domain meaning | Identical `new Promise(r => setTimeout(r, ms))`. One is production (the loader's catch-up wait), one is test scheduling. A test cannot import a module-private production helper, and exporting it would widen the loader's surface for no runtime reason. · **dep:** n/a · **instrument:** `grep -rnE 'function sleep\b'` | A | +| `runLoader` (×5) | vocabulary | `packages/next-plugin/src/turbopack-loader.ts:304`; `…tests/{loader-css-import.test.ts:78, loader-epoch-guard.test.ts:129, turbopack-loader.test.ts:109, turbopack-protocol-gauntlet.test.ts:212}` | the production `runLoader` is the subject under test | **DISTINCT** — but see the `LoaderContext` row: four test-local context stand-ins plus two production context types is six models of one surface | Four test drivers each construct their own loader-context stand-in and invoke the production entry. Each models a different invocation shape (css-import policy, epoch guard, protocol gauntlet, plain transform). · **dep:** intra-package · **instrument:** digest rows | A | + +--- + +### Cluster: oracle engine-test fixture world (duplicated and drifted) + +| claim | lane | file:line | existing owner/analog | disposition | evidence | status | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | +| The engine fixture world — `TOKENS`, `tokens()`, `BASE_DIMENSIONS`, `FixtureOptions`, `config()`, `host()`, `obligations()`, `classesFor()` — copied into four test files | vocabulary | `packages/oracle/__tests__/engine-cascade.test.ts:46,88,110,125`; `engine-inspect-explain.test.ts:39,81,103,118,295,345`; `engine-prove-refine.test.ts:40,82,104,318,368`; `engine-simulate-diff.test.ts:41,83,105,120,297,347` | none — `packages/oracle/__tests__/` contains no shared fixture module at all (27 test files, zero helpers) | **PROMOTE** to `packages/oracle/__tests__/fixture-world.ts` — highest-value promotion in the lane by volume; per-suite variation stays as options on the shared builder | Preambles are 351 / 358 / 397 / 385 lines, ~70–90% identical. `tokens()` is **byte-identical across all four** (md5 `57c6ddef04b0b15ff075abe35ab4b65e`); `host()` is byte-identical across the three that have it (md5 `76f0fdb0f9b473fe127d21798f386a1f`); `classesFor()` byte-identical across all four (md5 `b4258a335a9c280e755610f75f849181`). Pairwise preamble diffs: 29–134 differing lines. This is the fixture _world_ the engine assertions are written against — the shared semantic ground. Four copies means an engine behaviour change can be reflected in three worlds and missed in the fourth, and the differing line counts show that has already started. · **dep:** same directory, no new edge needed · **instrument:** `md5` over `awk`-extracted `tokens()`/`host()`/`classesFor()` ranges in all four files; `diff` over `head -$((describe_line-1))` preambles (pairwise counts recorded above) | V | + +--- + +### Cluster: test fixture composers that already reuse their owner (scanner recurrences) + +| claim | lane | file:line | existing owner/analog | disposition | evidence | status | +| ---------------------------------------------------------------------------------------------------------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `createProject` (9 sites) and `buildManifest` (4 sites) in next-plugin tests | vocabulary | `packages/next-plugin/tests/singleton-fixtures.ts:103,85` (both exported) plus 8 and 3 local declarations | The local declarations are **argument-binding aliases**, not copies: `function createProject() { return createFixtureProject('animus-txn-'); }`. The shared owner already holds the fixture bytes, the temp-root registry and `cleanupProjects`. Every consumer file already imports from `./singleton-fixtures`. | intra-directory, edges exist | **DISTINCT** (the reuse is correct) — with an **INLINE** note for the zero-value cases where the alias is called once or twice and only supplies a literal prefix | `sed -n '75,125p' singleton-fixtures.ts`; `sed -n '105,115p' plugin-pipeline.test.ts`; `sed -n '50,60p' watch-transaction.test.ts`; `grep -rn 'singleton-fixtures' packages/next-plugin/tests/` → 14 import sites | +| `makeContext` (6 sites), `makeProbe` (3 sites) in vite-plugin tests | vocabulary | `packages/vite-plugin/tests/context-probe.ts` (`ContextProbe`, `makeContextProbe`) | Same shape: each local `makeContext` composes `makeContextProbe(...)` with suite-specific extras (the probe's documented `extras` seam). Not duplication. | intra-directory | **DISTINCT** | `sed -n '1,45p' context-probe.ts`; `sed -n '40,62p' hot-update.test.ts`; `sed -n '10,32p' external-token-contracts.test.ts` | +| `makeRoot` (5), `write` (4), `makeSession` (4), `startSession` (4), `expectedEpoch` (2), `usageTags` (2), `extractFacts` (2) | vocabulary | `makeRoot`: `packages/cli/tests/cli-unit.test.ts:36`, `packages/extract/tests/{collect-external-packages.test.ts:22,tsconfig-paths.test.ts:11}`, `packages/next-plugin/tests/{turbopack-loader.test.ts:49,turbopack-protocol-gauntlet.test.ts:107}`; `expectedEpoch`: `replacement-epoch.test.ts:106` / `session-artifacts.test.ts:339`; `usageTags`: `_integration/__tests__/{svelte-source-lifecycle.test.ts:61,svelte-usage-extraction.test.ts:48}` | none | **DISTINCT** for `makeRoot`/`write`/`makeSession`/`startSession`; **REUSE** for `expectedEpoch` (→ `singleton-fixtures.ts`) and `usageTags` (→ a `_integration/__tests__` helper) | `makeRoot`/`write` are `mkdtempSync`/`writeFileSync` wrappers spread across four packages with no legal shared test-utility owner — cross-package test scaffolding, not vocabulary. `expectedEpoch` and `usageTags` are same-package, same-directory, byte-equivalent pairs (digest: "1 equivalent short body") where a shared module already exists (`singleton-fixtures.ts`) or could trivially exist. · **dep:** `makeRoot`/`write`: no owner; the others: intra-directory · **instrument:** digest rows (score 8, "1 equivalent short body"); `grep -rnE 'function (makeRoot\|write)\b'` | V | + +--- + +### Cluster: showcase documentation-component vocabulary + +| claim | lane | file:line | existing owner/analog | disposition | evidence | status | +| -------------------------------- | ---------- | --------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | +| `type Language` (5-member union) | vocabulary | `packages/showcase/src/components/docs/BeforeAfter.tsx:84` | `packages/showcase/src/components/surfaces/SyntaxBlock.tsx:289` (7-member union; not exported) | **REUSE** — export `Language` from `SyntaxBlock.tsx`; the highlighter owns its language vocabulary | `BeforeAfter` **imports `SyntaxBlock`** (line 2) and passes `language` straight through (lines 100, 109), while declaring its own narrower union. Two definitions of "which languages the highlighter accepts": adding `svelte` to the highlighter does not reach `BeforeAfter`, and `BeforeAfter` could in principle name a language the highlighter rejects. · **dep:** same package, import edge already exists · **instrument:** `grep -n 'import\|SyntaxBlock\|language' BeforeAfter.tsx`; `sed -n '283,300p' SyntaxBlock.tsx` | V | +| `interface Param` (×2) | vocabulary | `packages/showcase/src/components/docs/ParamTable.tsx:47`; `TypeSignature.tsx:37` | each other | **DISTINCT** — name collision only; rename if the two components are ever composed | Same name, different meanings: `{name,type,default,desc}` (a documented parameter row) vs `{name,type}` (a signature fragment). Both are local component props. · **dep:** same directory · **instrument:** `sed -n '43,58p' ParamTable.tsx` vs `sed -n '33,48p' TypeSignature.tsx` | A | + +--- + +### Cluster: vendored anti-slop plugin (`tools/`, untracked/new) + +| claim | lane | file:line | existing owner/analog | disposition | evidence | status | +| ----------------------------------------------------------------------------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `tools/oxlint/anti-slop/**` as a new repository-resident module (19 files) | vocabulary | `~/.claude/skills/install-anti-slop/assets/anti-slop/` | `diff -rq` reports **zero differences** — the repo copy is a byte-exact vendored install of the skill's assets. It is wired in at `vite.config.ts:125` (`specifier: './tools/oxlint/anti-slop/index.ts'`) and excluded from two config globs (`:186`, `:355`). It is not gitignored (`git check-ignore` → no match), so it will be committed. | vendored external artifact | **DISTINCT** — a vendored upstream copy is a legitimate authority claim, but it should be labelled as vendored (provenance comment or a `tools/README`) so future contributors do not edit it locally and silently fork it | `diff -rq tools/oxlint/anti-slop ~/.claude/skills/install-anti-slop/assets/anti-slop` → exit 0, no output; `git check-ignore -v` → no match; `grep -rn 'tools/oxlint' vite.config.ts` | +| `typeReferenceName` (×3), `resolveVariable` (×3), `isNode`, `Parameter` / `ParameterOwner` (×2) | vocabulary | `tools/oxlint/anti-slop/rules/{no-unsafe-dictionary-type.ts:56, no-widen-then-assert.ts:30}` + `shared/dictionary-types.ts:98`; `shared/reflect-method.ts:3` + `rules/{no-known-value-widening.ts:29, no-module-mocking.ts:7}`; `shared/lexical-type-parameters.ts:5`; `rules/{no-object-parameters.ts:7,8, no-unknown-parameters.ts:4,5}` | `tools/oxlint/anti-slop/shared/` is the plugin's own designated shared directory and already holds one copy of each | **DISTINCT (do not fix in-repo)** — de-duplicating here forks the vendored copy and breaks the byte-equality that makes re-installation safe. Route the finding upstream to the `install-anti-slop` skill instead. | Byte-identical duplicates inside the vendored plugin — `typeReferenceName` is the same one-liner in three files, two of which are rules that could import `shared/dictionary-types.ts`. This is genuine internal duplication, but it is _upstream's_ duplication. · **dep:** vendored · **instrument:** `grep -n 'function typeReferenceName' -A12` across the three files; `grep -rn 'resolveVariable' tools/` | A | + +--- + +### Cluster: deliberate recurrences (design-system augmentation and consumer fixtures) + +| claim | lane | file:line | existing owner/analog | disposition | evidence | status | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------- | +| `interface Theme` (14 declarations) | vocabulary | `packages/{test-ds/src/dev-types.ts:23, extract/tests/test-system.ts:125, showcase/src/ds.ts:699, system/__tests__/test-system.ts:39}`; `e2e/{next-app,next16-app,packed-app,react-router-app,rollup-app,vinext-app,vite-app}/src/ds.ts` | `packages/system/src/types/theme.ts:50` (`export interface Theme extends BaseTheme {}`), which documents the augmentation convention at :37 | **DISTINCT** — the scanner's largest type recurrence is a false positive on an intentional augmentation protocol | Every one of the 13 non-owner declarations sits **inside `declare module '@animus-ui/system' { … }`**. They are TypeScript declaration-merging augmentations _into the single owner_, not competing declarations. This is the opposite of parallel authority: it is many consumers pointing at one type. · **dep:** by construction · **instrument:** `grep -rn 'interface Theme\b'` → 14 hits, 13 inside `declare module`; `sed -n '30,52p' packages/system/src/types/theme.ts`; `sed -n '1,24p' packages/test-ds/src/dev-types.ts` | A | +| `type TestTheme` (4 declarations) | vocabulary | `e2e/next-app/src/ds.ts:188`; `e2e/next16-app/src/ds.ts:158`; `packages/extract/tests/test-system.ts:122`; `packages/system/__tests__/test-system.ts` | the per-app convention used everywhere else (`PackedAppTheme`, `ViteAppTheme`, `RollupAppTheme`, `VinextTheme`, `ReactRouterTheme`, `ShowcaseTheme`) | **DISTINCT, rename to match the sibling convention** (`NextAppTheme`, `Next16AppTheme`, `ExtractTestTheme`, `SystemTestTheme`) | Each is `export type TestTheme = typeof theme` over a _different_ theme. Not shared authority (each is local to its own compilation), but four unrelated themes share one generic name while six siblings follow a per-owner naming convention. · **dep:** n/a · **instrument:** `grep -rn 'interface Theme\b'` extends-clauses; the six per-app names visible in the same grep | A | +| e2e fixture-app entry points: `App` (48), `Home` (6), `RootLayout` (4), plus `Button`/`Card`/`Box`/`Stack`/`Badge`/`Panel`/`Fade`/`Pulse` component families | vocabulary | none (deliberately) | Framework-mandated per-app entry points (`app/layout.tsx` default export, `pages/_app.tsx`) and per-fixture design-system components. `RootLayout` bodies are near-identical across `next-app`/`next16-app`/`packed-app` and differ in `vinext-app` (which imports `virtual:animus/styles.css`); each app's `metadata.title` differs. Each fixture must be a **standalone consumer** — that independence _is_ the verification value, and importing a shared `RootLayout` would make the fixtures test each other instead of the published packages. Sharing them would also require `packages/* → e2e/*` or `e2e/* → e2e/*` edges, both outside the AGENTS.md dependency law. | sharing would violate the one-way rule | **DISTINCT by design** — one summary row for the whole family, per instruction | `cat` of all four `app/layout.tsx`; AGENTS.md:17-21; digest same-name counts | + +--- + +### Cluster: namespace-key constructors and branded-id wrappers (scanner false-positive family) + +These carry the digest's "16 equivalent short bodies" / "4 equivalent short bodies" reasons. The equivalence is the _template-literal shape_, not the meaning. + +| claim | lane | file:line | existing owner/analog | disposition | evidence | status | +| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ---------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `fileDependency` / `componentDependency` / `ruleDependency` / `tokenDependency` / `manifestDependency` | vocabulary | `packages/oracle/src/host/animus/dependency.ts:25,28,31,34,37` | each other | **DISTINCT** — the enumeration is the vocabulary | Five constructors over one branded id type, each pinning a **different namespace prefix** (`file:`, `component:`, `rule:`, `token:`, `manifest:`), documented as "the pinned dependency vocabulary … five namespaces, each a total function of one artifact input" with a DESIGN §9.6 citation. Collapsing them into one parameterized constructor would delete the namespace enumeration, which is the actual content. · **dep:** intra-file · **instrument:** `sed -n '1,45p' dependency.ts` | A | +| `mediaDimension` / `supportsDimension` / `containerDimension` (`host/animus/conditions.ts:33,35,37`), `variantDimension` / `stateDimension` (`host/animus/scenario.ts:16,19`) | vocabulary | same file | Same pattern: each pins one scenario-axis namespace. `dimensionOf` (conditions.ts:40) is the dispatcher that makes the enumeration load-bearing. | intra-file | **DISTINCT** | `sed -n '25,50p' conditions.ts`; `sed -n '14,22p' scenario.ts` | +| `eventKey` (`packages/vite-plugin/src/hot-update-events.ts:88`), `externalDiagnosticKey` (`packages/extract/session/turbopack-orchestrator.ts:85`), `githubSecret` (`scripts/verify/ci-graph.test.ts:87`), `themeSource`/`paletteSource`/`systemSource`/`usageSource` (`packages/vite-plugin/tests/dev-lane/fixture.ts:36,54,94,143`), `chain` (`packages/_parity/tools/seam-battery.ts:45`) | vocabulary | none | Single-purpose key builders and fixture-source templates. `eventKey` even documents its choice (`` separator, "a path can hold any character but NUL"). Each has one meaning and one call region. | n/a | **DISTINCT** — the "16 equivalent short bodies" cohort is entirely template-literal shape matching | `sed -n '86,92p' hot-update-events.ts`; `sed -n '82,90p' turbopack-orchestrator.ts`; `sed -n '30,60p' dev-lane/fixture.ts` | +| `brandId` + the eight `as*Id` wrappers | vocabulary | `packages/oracle/src/core/identity.ts:36` (private) and `:44-56` | itself | **DISTINCT** — not REMOVE | One private brand helper with eight typed entry points — the branded-id pattern, where each wrapper _is_ the type-level distinction. `asProbeStateId` (:53) is digest-flagged as a one-call pass-through, but its single caller (`core/probe.ts:167`) plus its re-export from `src/index.ts:16` make it part of the public identity surface. No openspec reference (`grep -rn 'asProbeStateId' openspec/` → no match), so it is not spec-pinned, but it is not dead either. · **dep:** intra-package · **instrument:** `sed -n '25,60p' core/identity.ts`; `grep -rn 'asProbeStateId'` → 3 sites; `grep -rn 'asProbeStateId' openspec/` → empty | A | +| `getOwningWatchSession` (`packages/extract/session/singleton.ts:172`), `envelopeCssArtifact` (`packages/extract/session/session-paths.ts:186`) | vocabulary | — | Both digest-flagged as exported one-call pass-throughs. `getOwningWatchSession` is the read half of a getter/setter pair over a module-private global slot — the accessor _is_ the encapsulation. `envelopeCssArtifact` has two real consumers (`extraction-session.ts:1757` and `next-plugin/tests/turbopack-protocol-gauntlet.test.ts:161`) and is referenced by name in three openspec documents. | n/a | **DISTINCT** — explicitly **not REMOVE**; `envelopeCssArtifact` is openspec-referenced per the repo prior | `grep -rn 'getOwningWatchSession\|envelopeCssArtifact' packages/`; `grep -rn 'envelopeCssArtifact' openspec/` → 3 hits in `openspec/changes/standalone-extraction-cli/` | +| `isJsxAttributeName` (`packages/extract/pipeline/svelte-source-adapter.ts:727`) | vocabulary | — | Digest's joint-highest score (9): "pass-through wrapper; tiny generic helper with at most one direct call". It is a single `RegExp.test` with one call site inside the Svelte→TSX projection. Genuinely a one-use indirection, but the named regex documents the JSX attribute-name grammar at the point where the adapter decides whether a Svelte attribute survives projection. | intra-file | **INLINE** (marginal) — inlining costs the grammar's name; acceptable either way, listed for completeness as a top-scored row that is not an authority collision | `sed -n '725,732p' svelte-source-adapter.ts` | + +--- + +### Coverage + +**Covered fully** (every digest row inspected, and each cluster's owner candidate opened and read): + +- `packages/cli/src` (8 digest rows) — config guards, exit codes, `writer.ts` artifact constants. +- `packages/_assertions` (4 rows) — full source listing read (`index.ts`, `receipt.ts`, `assert-css.ts`, `assert-appearance.ts`, `find-build-assets.ts`), plus `CLAUDE.md` and `package.json`. +- `packages/_parity/src` and `tools` (20 non-corpus rows) — `engine-run.ts` and `content-hash.ts` read in the relevant ranges; corpus fixtures treated as the fixture family. +- `packages/_integration` (19 rows) — `run-pipeline.ts`, `read-fixtures.ts`, `assert-no-unresolved-tokens.ts`, `manifest-shape.test.ts` declaration blocks. +- `packages/test-ds` (5 rows) — `dev-types.ts` read; the four component rows are the fixture-component family. +- `scripts/` (60 rows) — `hygiene/` and `verify/` file lists enumerated; every duplicated symbol pair opened and compared (with `md5` where byte-equality is claimed). +- `tools/oxlint/anti-slop` (35 rows) — full file listing, `diff -rq` against the upstream skill assets, duplicated helpers located. +- `packages/showcase` (18 rows) — the two type-duplication rows (`Language`, `Param`) traced to their owners; the remaining rows are component-name recurrences inside one app (`Panel`, `Pulse`, `Fade`, `Badge`, `Header`, `ClientPage`) and are the fixture-component family. +- `e2e/*` (~100 rows) — assert-build script duplication traced (`compact`, `assertDir`, `emitLaneReceipt`, `main`, `JsonValue`); fixture apps and their component families summarized as instructed. +- Cross-cutting JSON vocabulary — enumerated exhaustively by grep across the whole tree, with in-scope/out-of-scope counts separated. +- Root `vite.config.ts` — inspected for the anti-slop wiring; contributes **no** candidate rows to the digest (confirmed by filtering the digest on `^vite.config`). + +**Covered partially:** + +- `packages/oracle` (100 rows). Every guard/type/constant row and the four engine test files were opened. Not individually opened: the ~30 rows in `src/engines/{cascade,diff,explain,format,inspect,refine,simulate,speculate}.ts` and `src/places/*` whose digest reason is only "1 same-name … declaration" against a sibling in the same package. The newly-added untracked `src/providers/render-tree.ts` (which replaces the deleted `render-shape.ts`) was **not** audited for vocabulary claims — it is net-new code and warrants its own pass. +- `packages/next-plugin` (103 rows). All type-duplication and JSON-vocabulary rows opened; the loader/plugin production types read. The test-fixture composer rows (`makeSession`, `startSession`, `runLoader`, `write`) were resolved from the digest's `calls=` and same-name counts plus one or two sampled bodies each, not by reading all 5 sites of each. +- `packages/vite-plugin` (36 rows). `context-probe.ts`, `hot-update-events.ts`, `dev-lane/{fixture,scenario}.ts` and the three JSON-vocabulary test files read. The `dispatch`/`record` recurrences inside `hot-update.test.ts` (three declarations in one file) were not opened. +- `packages/extract` (59 rows). `pipeline/{engine-adapter,source-ingestion,asset-placeholders,assemble-stylesheet,svelte-source-adapter,analyze-project-args}.ts` and `session/{session-paths,singleton,turbopack-orchestrator,extraction-session}.ts` read at the flagged ranges. The ~25 `tests/fixtures/*.tsx` rows are the fixture-component family and were not opened individually. + +**Not covered:** + +- `packages/system`, `packages/properties`, `packages/extract/crates`, `legacy/` — excluded as candidate sources per the lane brief. `packages/system/__tests__` was cited only as evidence of the JSON vocabulary's true spread (2 more `JsonValue` copies, 3 more `isJson*` guards) and is otherwise untouched. +- Runtime-behaviour verification of any kind. No build, no test run, no `verify` tier — per the audit-only constraint. Every "identical"/"drifted" claim rests on byte comparison (`md5`, `diff`) or on reading both sites, and is labelled as such in the evidence column. +- `openspec/specs/` was grepped for the specific symbols where a REMOVE was in play (`asProbeStateId`, `envelopeCssArtifact`, `getOwningWatchSession`). It was **not** swept exhaustively against every symbol in the report, because no other row recommends REMOVE — the repo prior only binds that disposition. + +### Boundary + +Audit-only pass over the current (dirty) worktree. No repository file was modified. +Instruments: `rg`, `Read`, `git status`/`git check-ignore`, `.git/contract-change/reports/p1-inscope-digest.md`. +No build, no test, no verify command was run — every semantic claim below is read from source, not observed at runtime. + +**Repo-specific frame that governs most dispositions.** `vite.config.ts` (`lint.jsPlugins` → `./tools/oxlint/anti-slop/index.ts`) turns this lane's doctrine into repo-wide `error`-level rules: `no-runtime-typeof` (typeof only inside a type predicate), `no-unknown-parameters`, `no-unknown-returns`, `no-unknown-type-aliases`. Only `packages/system`'s 33-file frozen list and `tools/oxlint/anti-slop/**` are exempt. Consequence: **a local `JsonValue`-domain type plus locally-typed narrowing predicates is the shape the repo's own linter demands at every ingress**, and a shared generic `isRecord(value: unknown)` is the shape it forbids. Several duplications below are therefore the _intended_ output of the doctrine, not slop — and a naive "consolidate the guards" pass would regress it. The genuine findings are elsewhere: duplicated _wire-shape declarations_, duplicated _policy_, and `unknown` that never crossed a process boundary at all. + +`tools/` is untracked (`git status` → `?? tools/`), so the anti-slop plugin itself is part of the uncommitted campaign. + +--- + +### Cluster A — The JSON value-domain guard family (`JsonValue` / `isJsonObject` / `isJsonString`) + +| claim | lane | file:line | existing owner/analog | disposition | evidence | status | +| ----------------------------------------------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | +| 12 `type JsonValue` declarations across 5 packages + 1 e2e lane are one concept spelled 12 times | boundary | `packages/_parity/src/engine-run.ts:31`, `packages/_integration/__tests__/manifest-shape.test.ts:16`, `packages/next-plugin/tests/{plugin-pipeline.test.ts:250,session-artifacts.test.ts:82,turbopack-orchestrator.test.ts:84,webpack-gauntlet/cache-epoch.test.ts:130,webpack-gauntlet/external-ingestion.test.ts:135,webpack-gauntlet/harness.ts:285}`, `packages/vite-plugin/tests/{appearance-bootstrap-injection.test.ts:116,svelte-source-lifecycle.test.ts:23}`, `packages/system/__tests__/{bootstrap-packaging.test.ts:28,serialized-config.test.ts:36}`, `e2e/svelte-app/scripts/assert-build.ts:26` | none — no package exports a JSON value domain | **DISTINCT (accept the duplication) at package granularity; REUSE within a package** — see next two rows. Do not promote to `_assertions`: the build-order cost is real and the doctrine wants per-ingress domains | Bodies are structurally identical (`null\|boolean\|number\|string\|JsonValue[]\|{[k:string]:JsonValue}`). The oracle's `ManifestJsonValue` (`manifest-types.ts:29`) differs deliberately: `readonly` arrays and `\| undefined` on the index signature · **dep:** A shared home would have to be `packages/_assertions` (the only packages-resident private package legal for both `packages/*` and `e2e/*`). It is a dependency of 6 e2e lanes and **zero** `packages/*` consumers, and resolves through `main: ./dist/index.js` — so promoting would add 5 workspace edges and make 11 unit-test files depend on a built dist · **instrument:** Read all 12 declarations; `grep -l '@animus-ui/assertions' e2e/*/package.json packages/*/package.json`; `packages/_assertions/package.json` | A | +| 6 copies of the `isJsonObject`/`isJsonString`/`isJsonNumber`/`isJsonBoolean` quartet inside **one** package | boundary | `packages/next-plugin/tests/webpack-gauntlet/harness.ts:316-321`, `.../cache-epoch.test.ts:140-145`, `.../external-ingestion.test.ts:151-156`, `packages/next-plugin/tests/session-artifacts.test.ts:124-137`, `.../turbopack-orchestrator.test.ts:87-96` and again at `:154-163`, `.../plugin-pipeline.test.ts:262` | `webpack-gauntlet/harness.ts` is already the package's shared gauntlet test module (imports `../singleton-fixtures`) | **REUSE** — export the quartet from `harness.ts` (or a new `tests/json-domain.ts`) and delete the 5 copies. `turbopack-orchestrator.test.ts` declares the quartet **twice in one file** (`:87` and `:154`) — INLINE that one regardless | Bodies byte-identical: `Object.prototype.toString.call(value) === '[object X]'`. No semantic difference found · **dep:** Sibling relative import inside `packages/next-plugin/tests/` — zero new dependency edges, no dist. `harness.ts`'s copies are currently non-exported · **instrument:** Read all 6 sites; digest rows 21–49 | A | +| `vite-plugin` tests use a **different** object-guard body than the `[object Object]` family | boundary | `packages/vite-plugin/tests/plugin-context.test.ts:26`, `.../appearance-bootstrap-injection.test.ts:122`, `.../svelte-source-lifecycle.test.ts:29` | the `[object Object]` family above | **DISTINCT — rename before any consolidation.** If unified, name it `isNonArrayObject`, not `isJsonObject`; the current shared name across two different semantics is the collision | `value !== null && Object(value) === value && !Array.isArray(value)` **accepts** functions, `Date`, `Map`, boxed primitives; the `[object Object]` form rejects all of them. Over `JSON.parse` output the difference is unobservable, but the predicates are not interchangeable · **dep:** Same package, sibling test files · **instrument:** Read all three bodies; digest row 33 lists them as "equivalent short bodies" — the scanner's equivalence is textual, not semantic | A | +| `e2e/svelte-app` decodes `package.json` with a third object semantics | boundary | `e2e/svelte-app/scripts/assert-build.ts:41-75` | the two families above | **DISTINCT** — this one is a real ingress decoder with a throw policy, not a convenience narrow. Leave it | `!(value instanceof Object)` **rejects** null-prototype objects and cross-realm objects, which both other forms accept. Fails loud (`AssertionError`) rather than returning false — a genuine decoder, correctly placed at its ingress · **dep:** `e2e/* MAY import packages/*`; the lane already imports `@animus-ui/assertions` · **instrument:** Read `parseDependencyMap` / `parseManifestDependencies` | A | +| Digest's top-scored boundary candidate `isJsxAttributeName` ≡ `isIdentChar` is a false positive | boundary | `packages/extract/pipeline/svelte-source-adapter.ts:727` vs `packages/oracle/src/host/animus/selector.ts:35,37` | — | **DISTINCT** — record and close; do not consolidate | `isJsxAttributeName(name)` matches a **whole** JSX attribute name (`^[A-Za-z_$][A-Za-z0-9_$-]*$`, `$` legal); `isIdentChar(char)`/`isIdentStart(char)` classify **one** character of a CSS ident (`$` illegal). Different alphabets, different arities, different grammars · **dep:** `packages/extract` → `packages/oracle` would invert the dependency direction (oracle consumes extract's artifacts) — illegal in practice · **instrument:** Read both; digest line 5–8 (score 9) | A | +| `compact` duplicate #3 is a scanner false positive | boundary | digest cites `packages/extract/pipeline/assemble-stylesheet.ts:70` as a body-equivalent of `compact` | `packages/_assertions/src/assert-appearance.ts:27` (real), `e2e/svelte-app/scripts/assert-build.ts:120` (real) | **DISTINCT** for the pipeline site; see Cluster I for the two real copies | Line 70 is `stripLeadingLayerDeclaration`'s `css.replace(/^@layer\s+[^;{]+;\s*\n?/,'')` — a one-line `String.replace` body shape, not whitespace collapsing · **dep:** — · **instrument:** Read `assemble-stylesheet.ts:64-72` | A | + +--- + +### Cluster B — Config ingestion: four drivers, one shared validator, one second decoder + +| claim | lane | file:line | existing owner/analog | disposition | evidence | status | +| ----------------------------------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | +| The premise "several plugins independently parse the same animus config shape" is **refuted** for keys and values | boundary | `packages/cli/src/config.ts:207`, `packages/unplugin/src/options.ts:78`, `packages/vite-plugin/src/index.ts:166`, `packages/next-plugin/src/with-animus.ts:135` | `packages/extract/pipeline/core-options.ts:146 assertKnownOptionKeys` | **REUSE (already achieved)** — record as correctly owned; no action | All four drivers call the one shared validator at their own ingress. It owns unknown-key rejection, `mode` value validation, and `CORE_VALUE_GATES` for 9 core keys. The two failure policies (`warn` for the published plugin entry points, `throw` for CLI/unplugin) are declared in the shared type `AssertKnownOptionKeysOpts` and documented at the definition, not re-decided per driver. Only the **CLI** reads config _files_; the other three take inline options · **dep:** — · **instrument:** Read all four call sites and `core-options.ts` in full | A | +| The CLI re-narrows the same core keys a second time, with the opposite failure policy | boundary | `packages/cli/src/config.ts:216-244` (`parseLoadedCliConfig`) | `core-options.ts:195-242 CORE_VALUE_GATES` (called one line earlier, at `config.ts:207`) | **INLINE / narrow the second decoder** — the projection from `ConfigRecord` to `Partial` is a type-system need, but it should assert-and-cast against the already-validated record (or the gate should return the typed projection) rather than re-deciding validity with a silent-drop policy. Two answers to "is this option value valid?" is the collision | The shared gate **throws** `AnimusConfigError` naming the key and the expected type; the CLI's `isConfigString(raw.x) ? raw.x : undefined` **silently drops** to `undefined` for the same keys. Today unreachable-as-failure because the gate runs first — which is exactly the drift hazard: relax or reorder `CORE_VALUE_GATES` and the CLI starts silently defaulting instead of failing. Also two spellings of the primitive tests: shared uses `typeof`, CLI uses intrinsic-`valueOf`-in-`try` (`isConfigString`, `config.ts:122`) · **dep:** Same package boundary; the CLI already imports the owner · **instrument:** Read `config.ts:104-245` and `core-options.ts:183-242` | A | +| `staticCss` shape is validated by exactly one driver; the other three accept anything | boundary | `packages/cli/src/config.ts:134-202` (`isStaticCssConfig`, `isStaticCssComponentOverride`, `configStaticCss`) | `CORE_VALUE_GATES` has **no** `staticCss` entry (`core-options.ts:208-242`) | **PROMOTE** — move the shape predicate into the shared option core so all four drivers answer "is this staticCss valid?" identically. This is the one genuine PROMOTE in the lane | `staticCss` is a nested user-authored declaration (`components`/`variants`/`states`/`dynamicProps`/`systemProps`). CLI: full recursive validation, throws with the offending value. Vite/Next/unplugin: key accepted, shape unchecked, forwarded to the Rust engine as `staticCssJson` · **dep:** Adding a `staticCss` gate belongs in `core-options.ts` (the shared owner all four already import) · **instrument:** Read `config.ts:134-202`; grep for `staticCss` gate in `CORE_VALUE_GATES` (absent) | A | +| "Is `system` required, and what happens when it is missing" is answered in 4 places with 2 error classes and 4 messages | boundary | `packages/cli/src/config.ts:348-353`, `packages/unplugin/src/options.ts:50-84`, `packages/next-plugin/src/with-animus.ts:122-127`, and structurally at `packages/vite-plugin/src/index.ts` (no explicit check — relies on `CORE_VALUE_GATES`' `system` type gate) | `core-options.ts` `AnimusCoreOptions.system` is declared **required** in the type but only type-gated at runtime | **REUSE** — a single `assertRequiredSystem(options, hint)` in `core-options.ts` next to the other gates; each driver supplies only its remediation hint. Today a missing `system` is four different failures | CLI + unplugin throw `AnimusConfigError` (maps to CLI exit 2). Next throws a **plain `Error`** with an `[animus-extract]` prefix, _before_ `assertKnownOptionKeys` runs. Vite has no presence check at all — an options object with `system: undefined` passes `assertKnownOptionKeys` (the loop `continue`s on `undefined`) and fails later, deeper · **dep:** All four already import from `@animus-ui/extract/pipeline` · **instrument:** Read all four ingress functions; `core-options.ts:160-161` (`if (raw[key] === undefined) continue`) | A | +| Three mutually incompatible "is this a keyed object" semantics inside the config/ingest surface | boundary | `packages/cli/src/config.ts:104` (`isConfigRecord`), `packages/oracle/src/host/animus/manifest-types.ts:194` (`isRecord`, exported), `packages/vite-plugin/src/context.ts:92` (`isSerializedAnalysisSourceEntry`), `packages/cli/src/writer.ts:72` (`isFileExistsError`) | no single owner | **DISTINCT — document and rename.** Four names, four semantics, all currently correct for their own payload. The risk is a future pass reading them as one concept | `isConfigRecord` = `Object(v)===v && !Array.isArray && !isConfigCallable` → **accepts** `Date`/`Map`/boxed `String` (reachable: `animus.config.mjs` is an evaluated module). `isRecord` = `Object(v)===v && '[object Object]'` → **rejects** them. `context.ts` uses `value instanceof Object` → **rejects** null-prototype and cross-realm objects. `writer.ts` uses `error instanceof Object && 'code' in error` · **dep:** Cross-package reuse of oracle's `isRecord` is illegal in direction (extract/cli must not depend on oracle) and would import an `unknown` parameter that `no-unknown-parameters` forbids · **instrument:** Read all four bodies; `tools/oxlint/anti-slop/rules/no-unknown-parameters.ts:56-70` (no type-guard exception) | A | +| `isRecord(value: unknown)` is a live violation of the repo's own `no-unknown-parameters` rule | boundary | `packages/oracle/src/host/animus/manifest-types.ts:194`; same shape at `packages/extract/pipeline/core-options.ts:209-241` (`ok: (value: unknown) => boolean`) and `:205` (`isStringArray`) | `tools/oxlint/anti-slop/rules/no-unknown-parameters.ts` (rule is `'error'` repo-wide; only `packages/system`'s 33-file freeze list and `tools/oxlint/anti-slop/**` are exempt) | **PROMOTE** the type-predicate exemption upstream into `no-unknown-parameters` (the `TSTypePredicate` idiom already exists in sibling `no-runtime-typeof.ts:15-23`); `core-options.ts:210` `ok: (value: unknown) => boolean` returns bare boolean so that site changes regardless (V6) | The rule allows exactly one exempt parameter name: `cause`. There is no type-predicate exception, and neither file carries a suppression comment · **dep:** — · **instrument:** `vite.config.ts:100-136, 186-190` (ignorePatterns + rule levels); `rg 'oxlint-disable' packages/oracle/src packages/extract/pipeline` → no hits | A | + +--- + +### Cluster C — Cross-process artifact contracts: one file, several hand-written shapes + +| claim | lane | file:line | existing owner/analog | disposition | evidence | status | +| ------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | +| **`commit.json` — a spec-mandated external artifact — has its shape declared 3× in 2 packages, with 3 different acceptance policies** | boundary | `packages/cli/src/writer.ts:56-59` (`CommitRecord`), `packages/cli/src/writer.ts:70` (`PublishedCommitRecord`), `packages/extract/session/published-set.ts:27-30` (inline) | `packages/extract/session/published-set.ts` declares itself the owner: _"ONE implementation of 'does this flat artifact set verify against its commit record'"_ | **REUSE** — one exported `CommitRecord` type + one decoder in `published-set.ts` (the self-declared owner); writer and prune path consume it. The file header records that _"Two copies of either had already drifted once"_ — this seam has a drift incident on its record | Writer: `{schema: 1; payloads: Record}`, no read validation. Verifier: `{schema?: number; payloads?: Record}` + runtime `record.schema !== 1 \|\| typeof record.payloads !== 'object'` (which **accepts an array**, since `typeof [] === 'object'`). Prune path: `Object.keys(record.payloads ?? {})` with no validation at all — a `payloads` string would enumerate as character indices · **dep:** `packages/cli` already depends on `@animus-ui/extract/session` (imports `verifyCommitRecord`, `CLI_COMMIT_ARTIFACT`, …). Exporting the record type from `session/published-set.ts` is a pure addition · **instrument:** Read both files in full; `openspec/changes/standalone-extraction-cli/specs/standalone-extraction-cli/spec.md:57-69` mandates the record as an independently-verifiable external artifact | V | +| **The `.animus` advisory lock is written by one package and read by two, with two shapes and two liveness implementations** | boundary | writer: `packages/cli/src/writer.ts:101-155`; reader A: same file `:112-118` (`LockHolder {pid?: number; startedAt?: string}` + `isLockPid` + `isProcessAlive`); reader B: `packages/extract/session/extraction-session.ts:2088-2101` (`cliWriterHoldsLock`, inline `{pid?: number}` + `typeof holder.pid !== 'number'` + inline `process.kill`) | the _name_ has one owner (`CLI_LOCK_ARTIFACT`, shared constant); the _shape and liveness policy_ have none | **REUSE** — declare the lock record + `readLockHolder` in `extract/session` next to the constant it already shares. High risk today: a lock-format change in the CLI silently disables the session's debris-detection gate with no compiler signal | Reader A narrows `pid` with the intrinsic-`valueOf` idiom, reads `startedAt` **unvalidated** into a user-facing message (an object renders as `[object Object]`). Reader B uses plain `typeof`. Neither validates a schema field — the lock carries none. Two `process.kill(pid, 0)` implementations · **dep:** Same direction as the existing edge (`cli` → `session`); the session cannot import the CLI, so the type must live in `session/session-paths.ts` beside `CLI_LOCK_ARTIFACT` · **instrument:** Read both sites; `CLI_LOCK_ARTIFACT` import in `writer.ts:16` and `extraction-session.ts:2091` | V | +| The Turbopack loader re-declares `AnalysisCommit` inline and applies a weaker schema policy than the writer | boundary | `packages/next-plugin/src/turbopack-loader.ts:152-158` | `packages/extract/session/session-paths.ts:145 AnalysisCommit`, exported at `session/index.ts:42` | **REUSE** — import `AnalysisCommit`; decide the schema policy once. Cheapest high-value fix in this cluster | Writer-side reader `seedCommitFromDisk` (`extraction-session.ts:1910-1922`) requires `parsed.schema === 1 && sessionId match && typeof generation === 'number'`. The loader's inline shape **omits `generation` entirely and never checks `schema`** — a schema-2 commit would be consumed as schema-1 · **dep:** `turbopack-loader.ts:25` already does `import type { AnalysisStatus } from '@animus-ui/extract/session'`. `AnalysisCommit` sits in the same barrel · **instrument:** Read both; `session/index.ts:42` | A | +| The replacement-epoch artifact is decoded with two different inline shapes in one file | boundary | `packages/extract/session/extraction-session.ts:2046-2048` (`{epoch?: string}`, sibling) vs `:2072-2076` (`{schema?: number; epoch?: string}`, own) | none | **INLINE** — one local decoder for the epoch artifact; the asymmetric schema check across two reads of one format is unexplained | Sibling read accepts any epoch value with no schema check and, on a non-`ENOENT` error, **deletes the sibling's artifact** ("fail-safe invalidation"). Own read requires `schema === 1`. Same file format, two acceptance policies · **dep:** Same file · **instrument:** Read `extraction-session.ts:2035-2085` | A | +| `e2e/rollup-app` re-derives the commit verification instead of calling the owner — **and should keep doing so** | boundary | `e2e/rollup-app/scripts/assert-artifacts.mjs:35-41` | `packages/extract/session/published-set.ts:26 verifyCommitRecord` | **DISTINCT (deliberate)** — the script's header cites the spec requirement _"a reader holding only the record and the payload bytes can verify"_. Consolidating here would convert an independent witness into a tautology. Recorded so a later reuse pass does not "fix" it | The e2e loop re-reads the record and recomputes `contentHash` independently. Calling `verifyCommitRecord` would assert the implementation against itself · **dep:** Legal either way (`e2e/*` → `packages/*`; the lane already imports `contentHash` from `@animus-ui/extract/pipeline`) · **instrument:** Read the script header and lines 30-70; spec text at `openspec/changes/standalone-extraction-cli/.../spec.md:57-69` | A | + +--- + +### Cluster D — The producing package declares no wire type (`manifest.json`, `filesJson`) + +| claim | lane | file:line | existing owner/analog | disposition | evidence | status | +| ------------------------------------------------------------------------------------------------------------------------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | +| **Root cause: the pipeline that produces `manifest.json` reads it back as `any`, so no manifest owner exists anywhere** | boundary | `packages/extract/pipeline/run-analysis.ts:129` (`const manifest = JSON.parse(manifestJson)`), typed at `:23-24` as `manifest: any` with an `eslint-disable` | — (this _is_ the missing owner) | **PROMOTE (largest structural win in this lane, largest cost)** — declare the manifest wire type + one decoder in the producing package. Note this is an architectural change, not a cleanup; recording it as the named root cause is the deliverable here | Because the producer publishes no decoded type, every consumer invents one: `packages/oracle/src/host/animus/manifest-types.ts` (full adapter model), `packages/_integration/__tests__/manifest-shape.test.ts:26-62` (independent structural model + validators), `packages/cli/src/build.ts:170` (`as {components?: Record}`), `e2e/rollup-app/scripts/assert-artifacts.mjs:69` (untyped), `run-analysis.ts:141-143` (`manifest?.sheets?.global`, `manifest?.css`). Five readers, five partial models, no compiler forcing agreement · **dep:** Any shared manifest type must live in `packages/extract/pipeline` (already every consumer's dependency, except oracle by choice) · **instrument:** Read `run-analysis.ts:110-150`, `build.ts:160-176`, `manifest-types.ts`, `manifest-shape.test.ts:16-100` | V | +| Oracle's refusal to reuse the extractor's manifest types is argued and should stand | boundary | `packages/oracle/src/host/animus/manifest-types.ts:1-40` | `packages/extract/pipeline` | **DISTINCT (documented decision)** — but note the argument's premise is exactly the D-root-cause row above: if the producer ever declares the type, this decision should be revisited | The header states the reason: the pipeline's types sit behind a built `./pipeline` entry and the manifest is emitted as untyped JSON there anyway, so importing would couple oracle to the extractor's build output "for no added guarantee" · **dep:** oracle → extract would add a build-order edge · **instrument:** Read the module header | A | +| `filesJson` is decoded by three modules with three inline entry types and three failure policies, while a canonical entry type is exported | boundary | `packages/extract/pipeline/engine-adapter.ts:196-199` (`Array<{path,source}>`, no validation), `packages/next-plugin/src/turbopack-loader.ts:214-218` (`Array<{path,source,hash?}>`, no validation), `packages/vite-plugin/src/context.ts:92-115` (validates each entry, throws `TypeError`) | `packages/extract/pipeline/source-ingestion.ts:13 RawSourceEntry` / `:27 AnalysisSourceEntry`, both exported (`pipeline/index.ts:96`); the writer is `run-analysis.ts:64 filesJson: JSON.stringify(opts.fileEntries)` | **REUSE** — one `parseFilesJson` in the owning module; three call sites. Malformed `filesJson` currently takes three different paths depending on which reader sees it, which is the collision test failing | `RawSourceEntry` is exactly `{path: string; source: string; hash?: string}` — the shape all three hand-write. Failure policies differ: silent cast / silent cast inside a `torn` catch / loud `TypeError`. `context.ts`'s validator uses `String(v.path) === v.path`, an idiom neither other reader applies · **dep:** All three already import types from `@animus-ui/extract/pipeline` (`turbopack-loader.ts:21-24`, `context.ts` imports `RawSourceEntry`) · **instrument:** Read all three plus `source-ingestion.ts:13-32` | V | +| `AnalysisStatus` is the counter-example: one owned type, two runtime sides, but the reader still asserts rather than decodes | boundary | `packages/next-plugin/src/turbopack-loader.ts:251` (`JSON.parse(raw) as AnalysisStatus`) | `packages/extract/session/session-paths.ts:116 AnalysisStatus` (writer: `extraction-session.ts:1790-1800`) | **DISTINCT (type) / accept (assertion)** — the assertion is bounded: the loader never _depends_ on status for correctness ("Successful loader invocations never depend on it", `session-paths.ts:108-110`). Record, do not act | Type ownership is correct — a single exported interface, imported by both sides. The read is a bare assertion: a parseable-but-wrong payload passes straight through. The `catch → null` is documented ("torn status write — indistinguishable from absent") and is a legitimate fallback with a named contract · **dep:** — · **instrument:** Read `session-paths.ts:108-140` and `turbopack-loader.ts:247-255` | A | + +--- + +### Cluster E — `unknown` that never crossed a boundary + +| claim | lane | file:line | existing owner/analog | disposition | evidence | status | +| ------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | +| **Three guards decode a value that was produced, typed, in the same process, by the same package — the clearest boundary laundering found** | boundary | `packages/oracle/src/cli/render.ts:104-149` (`isComparatorPayload`, `isDiffEntryList`, `isContextClassCount`, `semanticDiffOf`) | producer: `packages/oracle/src/engines/diff.ts:494 toSemanticDiff` → assigned at `engines/simulate.ts:179` and `engines/diff.ts:764`; the field is `packages/oracle/src/core/probe.ts:145 semanticDiff?: unknown` | **REMOVE** — type the seam instead: make `ProbeResult` generic over the diff payload, or declare a minimal structural `SemanticDiffLike` in `core/`. Then all three guards and `semanticDiffOf` delete. No spec mandate found (`rg 'semanticDiff' openspec/specs` → 0 hits, grep calibrated non-vacuous against `Requirement`) | `ProbeResult` is **never** parsed from JSON — every producer and consumer is in-process (`rg 'ProbeResult'` shows only type positions and constructions). The `unknown` exists purely so `core/` imports no engine. But `render.ts:20` **already imports `SemanticDiff` and `SemanticDiffEntry` from `../engines/diff`** — the layering constraint that motivates the `unknown` does not bind the consumer at all. `isDiffEntryList` is `Array.isArray` widened to `readonly SemanticDiffEntry[]`, and its own doc concedes it is "a check that never runs" · **dep:** Entirely within `packages/oracle` · **instrument:** Read `render.ts:86-150`, `core/probe.ts:136-150`, `engines/diff.ts:494`, `engines/simulate.ts:179` | V | +| **The `prove` assertion contract is expressed in four places; only one is compiler-enforced** | boundary | union `packages/oracle/src/engines/prove.ts:65`; runtime array `prove.ts:94-100`; validator `prove.ts:114-140`; CLI ingress `packages/oracle/src/cli/args.ts:93-99` (a second `ASSERTION_KINDS`) + `:114-168` (`switch` + `default: throw UsageError`) | `cli/args.ts:114 parseAssertion` is the ingress decoder; `prove.ts:114 validate` is the engine re-check | **REUSE** — export one `ASSERTION_KINDS` (derived from the union) from `prove.ts`; the CLI's usage grammar becomes a per-kind field beside it. Keep `validate()` (prove is also a programmatic API via `engines/index.ts:118`) but let the CLI stop duplicating the kind set | The two `ASSERTION_KINDS` constants are **not the same list**: `prove.ts` holds bare kinds, `args.ts` holds grammar-annotated strings used only in the error text. So the closed set is enforced by the union (compiler), the array (runtime, prove), and the `switch` (runtime, CLI) — adding a kind takes four coordinated edits and only one of them fails to compile. Failure surfaces differ: `UsageError` (exit 2, CLI-shaped remediation) vs `TypeError` (engine-shaped) for the same malformed input · **dep:** Same package; `args.ts:16` already imports `OracleAssertion` from `engines/prove` · **instrument:** Read `prove.ts:65-140`, `args.ts:93-168`, diffed the two constants | A | +| `isProofString` / `isNumericDimensionValue` re-narrow already-typed fields inside the engine | boundary | `packages/oracle/src/engines/prove.ts:104-112`, used at `:127-140` | `cli/args.ts:114 parseAssertion` guarantees these fields on the CLI path | **DISTINCT (idiom is deliberate)** — record. The idiom family itself is coherent and correctly motivated by `no-runtime-typeof` | The intrinsic-read idiom (`Object(v)!==v` + `[object String]`) is an established, documented repo family — also at `core/identity.ts`, `cli/session.ts:133-143`, `cli/render.ts:120`. Here it narrows `string \| undefined`, i.e. it is a definedness check dressed as a type test, defensible only because `prove()` is a public programmatic entry point · **dep:** Same package · **instrument:** Read `prove.ts:94-145`, `args.ts:114-168` | A | +| The oracle session's stdin ingress is the pattern the rest of the repo should be measured against | boundary | `packages/oracle/src/cli/session.ts:129-143` (primitives), `:677-691` (`handleLine`), `parseSessionRequest` | — | **DISTINCT (exemplar)** — no action; cited as the reference shape for Clusters D and E | One `JSON.parse` at the stream boundary, one decoder producing a typed request, malformed input answered as a structured `{ok:false, kind:'usage'}` response rather than a throw. Guards are the decoder's own primitives, not downstream conveniences · **dep:** — · **instrument:** Read `session.ts:116-160, 655-745` | A | +| Thrown-value message extraction is centralized but lossy | boundary | `packages/cli/src/index.ts:64-75 readThrownMessage` | `packages/cli/src/build.ts:264` (`new ExtractionFailure(String(error))`), `watch.ts:167` | **DISTINCT / low severity** — one owner exists; note the `cause` loss as an observability cost, not a collision | `readThrownMessage` carries a SAFETY comment and is a single owner for "read `.message` off an unknown throw". But `ExtractionFailure(String(error))` **discards the original** (no `cause`), so the message reaching `readThrownMessage` is already a stringified stack-less summary · **dep:** Same package · **instrument:** Read `index.ts:64-84`, `build.ts:255-270` | A | + +--- + +### Cluster F — Silent degradation on the pipeline's own internal string channels + +Every payload in this cluster is produced by this repo's own code (system loader, plugin config builders), never by a user. A parse failure is therefore an internal invariant violation, and the reference's resolution pattern _"Catch masks an impossible internal state → Remove and allow the invariant violation to surface"_ applies. + +| claim | lane | file:line | existing owner/analog | disposition | evidence | status | +| ---------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | +| A diagnostics collector whose own doc says it "must fail loud" returns `[]` on malformed input | boundary | `packages/extract/pipeline/manifest-diagnostics.ts:94-99` | its own header at `:82-89`: _"The system-config boundary is where these must fail loud"_ | **REMOVE the catch** (or convert to a diagnostic). Fallback gate: protected contract — none nameable; consumer — none; owner — the system-config boundary itself. Defaults to REMOVE | `JSON.parse(selectorAliasesJson)` failing → `return []` → zero diagnostics → the selector-alias validation silently passes. The failure mode the function exists to prevent is exactly the one the catch creates · **dep:** — · **instrument:** Read `manifest-diagnostics.ts:82-115` | V | +| A parse failure is **memoized**, converting a transient failure into a process-lifetime policy | boundary | `packages/extract/pipeline/resolve-asset.ts:45-58 parseAliasTable` | — | **REMOVE the catch** (cache only successful parses at minimum). This is the sharpest of the five | `catch { aliases = [] }` then `aliasTableCache.set(pathAliasesJson, aliases)` — the empty result is cached against the same JSON string, so every subsequent asset resolution for that config silently resolves against no aliases. Worse than the plain silent catch: it persists · **dep:** — · **instrument:** Read `resolve-asset.ts:40-60` | V | +| Two more silent-empty parses on internally-produced JSON | boundary | `packages/extract/pipeline/correlate-external-tokens.ts:35-40` (`catch → empty index`), `packages/extract/pipeline/external-keyframes.ts:33-40` (`catch → pass the malformed payload through untouched`) and `:69-73` (`catch → continue`) | — | **REMOVE** both silent catches (throw) — both parse animus-produced internal wires (buildPathAliasesJson is the single authoritative encoder; sourceThemeManifests comes from the system loader), so failure = engine bug, not diagnosable user input; NOTE: `correlate-external-tokens.test.ts:133-146` currently PINS the swallow — reversal is a recorded contract decision (D11) | `external-keyframes.ts:37-38` names an owner in prose ("A malformed consumer payload is the loader's problem, not this merge's") — the only one of the five with a stated contract. `correlate-external-tokens` states nothing; a malformed `sourceThemeManifestsJson` silently disables the cross-source correlation diagnostic · **dep:** — · **instrument:** Read all three | V | +| Two catch-to-empty fallbacks that **do** pass the evidence gate | boundary | `packages/cli/src/writer.ts:168-181 publishedAssetNames`, `packages/next-plugin/src/turbopack-loader.ts:247-255 readStatus` | — | **DISTINCT (keep)** — recorded so the cluster is not read as uniformly bad | Both name the protected contract inline: "Absent/unreadable record → nothing is prunable" (bounds a destructive prune — the safe direction) and "torn status write — indistinguishable from absent" (the type already models absence). Both have an owner and a coherent removal condition · **dep:** — · **instrument:** Read both, plus the prune-ownership comment at `writer.ts:256-262` | A | + +--- + +### Cluster G — External tool report decoders in `scripts/hygiene` + +| claim | lane | file:line | existing owner/analog | disposition | evidence | status | +| -------------------------------------------------------------------------------------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | +| **The oxlint `--format=json` wire shape is declared three times, with different optionality and two opposite failure policies** | boundary | `scripts/hygiene/_emit-oxlint-receipts.ts:33-46`, `scripts/hygiene/delete-unused.ts:135-149`, `scripts/hygiene/delete-unused.test.ts:31-38` | none | **REUSE** — one decoder module. The emitter's silent-return is a vacuous-gate hazard: an empty receipt set reads as "no findings", which is precisely how a broken cascade would look | `OxlintReport` is `{diagnostics?: OxlintDiagnostic[]}` in the emitter and `{diagnostics: OxlintDiagnostic[]}` in the deleter. On malformed input the emitter does `catch { return }` — **exit 0, zero receipts, indistinguishable from a clean run**; the deleter does `console.error(...); process.exit(1)` and additionally rejects a missing `diagnostics` array by name · **dep:** Sibling modules in one directory; a shared `scripts/hygiene/oxlint-report.ts` needs no new dependency · **instrument:** Read all three declarations and both `main()` bodies | V | +| `classifyUnusedVar` — a parser of oxlint's human message text — is byte-identical in two files, and only one has drift detection | boundary | `scripts/hygiene/_emit-oxlint-receipts.ts:59-68` and `scripts/hygiene/delete-unused.ts:396-405`; `unwrapCode` at `:54-57` / `:386-389`; `readStdin` at `:48-52` / `:170-...` (also `_emit-knip-receipts.ts:67`) | `delete-unused.ts:479-491 detectCodeDrift` exists only in the deleter, and its comment says "live-integration test pins drift detection" | **REUSE** — hoist classifier + `unwrapCode` + `readStdin` beside the shared report decoder, so the drift guard covers both consumers. The whole hygiene cascade's correctness routes through this regex set | The bodies match character-for-character. The classifier depends on oxlint's message _prose_ (`^Identifier '…' is imported`), the most drift-prone contract in the cascade. The emitter has no drift guard and no test pinning it · **dep:** Same directory · **instrument:** Diffed both `classifyUnusedVar` bodies; read `detectCodeDrift` | V | +| The knip report decoder repeats the silent-return policy on a different payload | boundary | `scripts/hygiene/_emit-knip-receipts.ts:56-65, 119-125` | — | **DISTINCT (type) / REUSE (policy)** — the failure policy for "a receipts emitter got unparseable tool output" should be decided once | Distinct wire format (knip, not oxlint), so the _type_ is correctly separate. The **policy** (`catch { return }` → exit 0, no receipts) is the third copy of the same silent-success decision · **dep:** — · **instrument:** Read `_emit-knip-receipts.ts:113-130` | V | + +--- + +### Cluster H — Error envelopes and translation + +| claim | lane | file:line | existing owner/analog | disposition | evidence | status | +| --------------------------------------------------------------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------ | +| The CLI exit taxonomy has exactly one translation point | boundary | `packages/cli/src/index.ts:78-84 exitCodeFor`; classes at `build.ts:43-47` | — | **DISTINCT (correct)** — no action | One function maps error class → exit code; three purpose-named classes; the "unclassified throw during a build IS a failed extraction" default is stated. Matches "one translation boundary per layer" · **dep:** — · **instrument:** Read `index.ts:56-90`, `build.ts:35-50` | A | +| `AnimusConfigError` carries two meanings: "config error" and "route to exit 2" | boundary | `packages/cli/src/writer.ts:119-124` and `:146-149` (lock conflicts) | `packages/extract/pipeline/core-options.ts:115-122`: _"Configuration errors carry a stable name so drivers can map them to their config-error surface (the CLI's exit code 2)"_ | **DISTINCT** — split the class (typed lock-conflict error), keep exit routing; spec pins only config→2, lock conflict unpinned and design D5 self-contradicts (2 vs 3); spec-gap flagged for owner (V7) | A live concurrent lock holder is a runtime concurrency condition, not a configuration error. It is dressed as one to land in exit class 2. The nearby `PublishInconsistencyError` takes the opposite route: `build.ts:210` re-wraps it as `EnvironmentFailure` (exit 3). So two publication-path failures reach two different exit classes through two different mechanisms · **dep:** Same package; `EnvironmentFailure` is available and already used one function away · **instrument:** Read `writer.ts:99-155`, `build.ts:195-215`, `core-options.ts:115-122` | A | +| Eight error classes across five packages, each package owning its own | boundary | `_assertions/src/assert-css.ts:1`, `cli/src/build.ts:43,45,47`, `cli/src/writer.ts:90`, `extract/pipeline/core-options.ts:117`, `oracle/src/cli/args.ts:19`, `oracle/src/cli/session.ts:698`, `oracle/src/host/animus/errors.ts:33` | — | **DISTINCT (correct)** — recorded as a checked negative | No two classes claim the same domain. `AnimusConfigError` is the only one crossing a package boundary, and that crossing is its documented purpose · **dep:** — · **instrument:** `rg 'class .*(Error\|Failure)' across the in-scope packages` | A | +| `assertDir` — an fs-error → `AssertionError` envelope — is duplicated verbatim across two e2e lanes | boundary | `e2e/next-app/scripts/assert-build.ts:35-44`, `e2e/next16-app/scripts/assert-build.ts:25-34` | `packages/_assertions` (already a dependency of both lanes; exports `AssertionError`, `findBuildAssets`, …) | **REUSE** — move to `_assertions`; this is the shared-assertion package's stated purpose | Bodies identical: `stat` + `isDirectory()` + `catch → AssertionError('