diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 3b6cd524..9ac3ba1c 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -8,7 +8,8 @@ Link to issue or explain the motivation. ## Checklist -- [ ] Tests pass (`make test` and `go test ./...`) +- [ ] Deterministic tests pass (`make test`) +- [ ] Relevant E2E/process/Docker boundaries pass (`make test-integration`, when affected) - [ ] New/changed behavior is covered by tests - [ ] Documentation updated (USAGE.md, DESIGN.md, or README) if applicable - [ ] User-facing release-note impact described in this PR, if applicable diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ddc58e6a..33811054 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,6 +16,7 @@ concurrency: jobs: build-and-test: runs-on: ubuntu-latest + timeout-minutes: 30 steps: - uses: actions/checkout@v4 @@ -31,11 +32,5 @@ jobs: go.sum harness/go.sum - - name: Run unit tests - run: go test ./... - - - name: Run E2E tests + - name: Run deterministic test suite run: make test - - - name: Check release and Harness boundary - run: make harness-validate diff --git a/.github/workflows/harness-deep.yml b/.github/workflows/harness-deep.yml deleted file mode 100644 index 814ebd03..00000000 --- a/.github/workflows/harness-deep.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: Harness Deep Verification - -on: - pull_request: - branches: [master] - paths: - - "harness/**" - - "docs/harness/**" - - "Makefile" - - ".github/workflows/harness-deep.yml" - push: - branches: [master] - paths: - - "harness/**" - - "docs/harness/**" - - "Makefile" - - ".github/workflows/harness-deep.yml" - schedule: - - cron: "17 18 * * *" - workflow_dispatch: - -permissions: - contents: read - -concurrency: - group: harness-deep-${{ github.ref }} - cancel-in-progress: true - -jobs: - verify: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ github.event.pull_request.head.sha || github.sha }} - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version-file: go.mod - cache-dependency-path: | - go.sum - harness/go.sum - - - name: Run complete R7 evidence gate - env: - HARNESS_QUALITY_BASE_REF: ${{ github.event.pull_request.base.sha || github.event.before || github.sha }} - run: make harness-verify - - - name: Test trace observer - run: go -C harness test ./test/observer -count=1 - - - name: Test domain-operations Runtime boundary - run: harness/test/r7/domainops/run_live_oracle.sh - - - name: Test removable R8 selector - run: make harness-r8 diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml new file mode 100644 index 00000000..5091abf7 --- /dev/null +++ b/.github/workflows/integration.yml @@ -0,0 +1,33 @@ +name: Manual Integration + +on: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: integration-${{ github.ref }} + cancel-in-progress: true + +jobs: + verify: + runs-on: ubuntu-latest + timeout-minutes: 60 + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache-dependency-path: | + go.sum + harness/go.sum + + - name: Run integration suite + run: make test-integration diff --git a/.github/workflows/live.yml b/.github/workflows/live.yml new file mode 100644 index 00000000..8ebd1e98 --- /dev/null +++ b/.github/workflows/live.yml @@ -0,0 +1,36 @@ +name: Live Agent Evaluation + +on: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: live-agent-evaluation + cancel-in-progress: false + +jobs: + live: + runs-on: ubuntu-latest + timeout-minutes: 90 + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache-dependency-path: | + go.sum + harness/go.sum + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 22.19.0 + + - name: Run Pi and DeepSeek live evaluation + env: + DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }} + run: make test-live diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0a720d19..cec0eccc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,10 +23,7 @@ jobs: with: go-version-file: go.mod - - name: Run unit tests - run: go test ./... - - - name: Run E2E tests + - name: Run deterministic test suite run: make test release: diff --git a/AGENTS.md b/AGENTS.md index 32f87dfa..80088141 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,11 +3,12 @@ ## Development - Build with `go build -o mnemon .`. -- Run the E2E suite with `bash scripts/e2e_test.sh` or `make test`. -- Validate harness module manifests with `make harness-validate` when changing - harness module assets. -- Run `make harness-quality` for the pinned Harness quality ratchet and - `make harness-verify` for the complete local Harness build and test gate. +- Run `make test` for the deterministic CI suite. It excludes real daemon + readiness, CLI E2E, wall-clock scenarios, Docker, and provider calls. +- Run `make test-integration` explicitly for CLI E2E plus Harness timing, race, + process, and Docker boundaries; it is not a regular CI gate. +- Run `make test-live` only when explicitly validating the paid Pi/DeepSeek + scenarios. - Treat `harness/` as an experimental, not-yet-released harness layer. Do not use it as an implementation dependency for release-path commands such as `mnemon setup`; formal integrations belong under `cmd/` and `internal/`. @@ -25,9 +26,8 @@ - Keep authority, digest, fence, bounds, CAS cardinality, and fail-closed checks explicit. Every goroutine must have an owner, cancellation, bounded work, and a wait path. -- In a scope with a tracked engineering-quality baseline, new or modified code - must not increase it. Preserve independent test oracles while compressing - fixtures. +- Preserve independent replay, crash, authorization, and race oracles while + compressing fixtures and shared setup. ## Commit Discipline diff --git a/CLAUDE.md b/CLAUDE.md index 0ddf3406..a2de9ff4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,6 +4,7 @@ - **Build**: `go build -o mnemon .` - **Install**: `make install && mnemon setup` -- **Test**: `bash scripts/e2e_test.sh` +- **Test**: `make test` +- **Integration**: `make test-integration` for CLI E2E and Harness boundaries - **Dependencies**: `modernc.org/sqlite`, `spf13/cobra`, `google/uuid` - **Optional**: Ollama with `nomic-embed-text` for embedding support diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 47dc77e9..616d1efd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,15 +24,14 @@ make build ## Running Tests ```bash -make unit # Go unit tests (go test ./...) -make test # Full E2E test suite (scripts/e2e_test.sh) -make vet # Static analysis (go vet ./...) -make harness-quality # Pinned Harness format, static, architecture, and debt ratchets -make harness-verify # Harness binaries, declarations, quality, vet, and unit tests +make test # Required deterministic CI suite for both Go modules +make test-integration # Opt-in CLI E2E, timing, race, process, and Docker suite +make test-live # Explicit paid Pi/DeepSeek evaluation ``` -Both `make unit` and `make test` must pass before submitting a PR. A change to -the experimental Harness must also pass `make harness-verify`. +`make test` must pass before submitting a PR. Run `make test-integration` +proportionally when changing CLI E2E behavior or Harness process, timing, +transport, or Docker boundaries; it is intentionally outside regular CI. ## Code Style @@ -41,8 +40,7 @@ the experimental Harness must also pass `make harness-verify`. - All exported functions and types must have doc comments - Use `fmt.Errorf("context: %w", err)` for error wrapping -For architecture, concurrency, persistence, abstraction, test design, and the -incremental quality ratchet, follow the +For architecture, concurrency, persistence, abstraction, and test design, follow the [Go Engineering Standard](docs/development/go-engineering-standard.md). Design patterns and Go language features are tools for reducing change amplification, not quotas or substitutes for explicit safety checks. @@ -64,8 +62,8 @@ imperative form. The CHANGELOG filter excludes `docs:`, `test:`, `ci:`, and ## Submitting Changes 1. Fork the repository and create a feature branch from `master`. -2. Make your changes and run the gates that cover them, including - `make harness-verify` for Harness changes. +2. Make your changes and run the proportional test level, including + `make test-integration` for affected Harness boundary behavior. 3. Update documentation (USAGE.md, DESIGN.md, or README) if your change affects user-facing behavior. 4. For user-facing changes, describe the release-note impact in the PR body. Maintainers update `CHANGELOG.md` during release preparation unless they explicitly ask for a changelog entry in the PR. 5. Open a pull request against `master`. diff --git a/Makefile b/Makefile index 68b0cf3e..dfc9ce4d 100644 --- a/Makefile +++ b/Makefile @@ -9,14 +9,30 @@ HARNESS_LDFLAGS := -s -w -X main.version=$(VERSION) GO_VERSION := $(shell awk '$$1 == "go" { print $$2; exit }' go.mod) HARNESS_GO_VERSION := $(shell awk '$$1 == "go" { print $$2; exit }' harness/go.mod) HARNESS_GO := env GOTOOLCHAIN=go$(HARNESS_GO_VERSION) GOFLAGS=-mod=readonly go -C harness +HARNESS_DETERMINISTIC_PKGS := \ + ./cmd/mnemon-harness \ + ./internal/agency \ + ./internal/attach \ + ./internal/authority \ + ./internal/cas \ + ./internal/selector \ + ./internal/selector/simtest \ + ./test/architecture \ + ./test/observer \ + ./test/r7/domainops/trace \ + ./test/r8/network/runner/trace +HARNESS_TESTDATA_PKGS := \ + ./internal/selector/testdata/network/cmd/r8-peer \ + ./testdata/r7/domain-ops/cmd/domain-load \ + ./testdata/r7/domain-ops/cmd/domain-world \ + ./testdata/r7/domain-ops/cmd/domainctl \ + ./testdata/r7/domain-ops/world GOBIN := $(shell go env GOBIN) ifeq ($(GOBIN),) GOBIN := $(shell go env GOPATH)/bin endif -.PHONY: deps build harness-build install uninstall test unit vet harness-validate harness-quality harness-verify -.PHONY: harness-live-pi -.PHONY: harness-r8 harness-r8-docker harness-domain-ops harness-domain-ops-live +.PHONY: deps build harness-build install uninstall test test-integration test-live .PHONY: docker-build docker-run compose-up compose-down compose-dev release-snapshot clean help .DEFAULT_GOAL := help @@ -48,46 +64,25 @@ uninstall: ## Remove mnemon binary from $GOBIN # ── Test ───────────────────────────────────────────────────────────── -test: ## Run E2E test suite (the script builds the tested binary once) - bash scripts/e2e_test.sh - -unit: ## Run Go unit tests - go test ./... - -vet: ## Run go vet static analysis +test: ## Run deterministic tests without E2E, real daemon, or provider calls go vet ./... + go test ./... + $(HARNESS_GO) vet ./... $(HARNESS_TESTDATA_PKGS) + $(HARNESS_GO) test $(HARNESS_DETERMINISTIC_PKGS) -count=1 -harness-validate: ## Validate the R7 projection, contract, and evidence bindings - $(HARNESS_GO) test ./internal/attach ./tools/corecontract ./test/contracts -count=1 - -harness-quality: ## Run pinned, non-mutating Harness quality gates - @base_ref="$${HARNESS_QUALITY_BASE_REF:-HEAD}"; \ - $(HARNESS_GO) run ./tools/quality check --root .. --base-ref "$$base_ref" - $(HARNESS_GO) vet ./... - $(HARNESS_GO) test ./tools/quality -count=1 - -harness-live-pi: ## Run the opt-in Pi/DeepSeek live smoke - @test "$${LIVE_PI:-}" = 1 || { echo "error: set LIVE_PI=1" >&2; exit 2; } - @test -n "$${DEEPSEEK_API_KEY:-}" || { echo "error: DEEPSEEK_API_KEY is required" >&2; exit 2; } - harness/test/r7/runner/run_live_pi.sh - -harness-r8: ## Test the optional, removable R8 selector and its proof adapters - $(HARNESS_GO) test ./internal/selector ./internal/selector/simtest ./internal/selector/testdata/network/cmd/r8-peer -count=1 - $(HARNESS_GO) test -race ./internal/selector ./internal/selector/simtest ./internal/selector/testdata/network/cmd/r8-peer -count=1 - -harness-r8-docker: harness-r8 ## Run the isolated five-peer R8 network proof - harness/test/r8/network/runner/run_docker.sh - -harness-domain-ops: ## Run the opt-in real-service federated operations world +test-integration: ## Run opt-in E2E, timing, race, process, and Docker tests + bash scripts/e2e_test.sh + $(HARNESS_GO) test -p 1 ./... $(HARNESS_TESTDATA_PKGS) -count=1 + $(HARNESS_GO) test -race -p 1 ./internal/... $(HARNESS_TESTDATA_PKGS) -count=1 + harness/test/r7/runner/run_cases.sh + harness/test/r7/runtime/pi/run_delegate_oracle.sh harness/test/r7/domainops/run_world.sh + harness/test/r8/network/runner/run_docker.sh -harness-domain-ops-live: ## Run the paid autonomous Pi/DeepSeek operations case - @test "$${LIVE_DOMAIN_OPS:-}" = 1 || { echo "error: set LIVE_DOMAIN_OPS=1" >&2; exit 2; } +test-live: ## Run the paid Pi/DeepSeek smoke and federated operations case @test -n "$${DEEPSEEK_API_KEY:-}" || { echo "error: DEEPSEEK_API_KEY is required" >&2; exit 2; } - harness/test/r7/domainops/run_live.sh - -harness-verify: harness-quality ## Run the complete exact-tree R7 evidence gate and write its report - $(HARNESS_GO) run ./tools/corecontract/cmd/core-gate --root .. + LIVE_PI=1 harness/test/r7/runner/run_live_pi.sh + LIVE_DOMAIN_OPS=1 harness/test/r7/domainops/run_live.sh # ── Containers / Deployment ────────────────────────────────────────── diff --git a/README.md b/README.md index f2bb3604..333b6285 100644 --- a/README.md +++ b/README.md @@ -382,7 +382,8 @@ Mnemon architecture. ```bash make build # build binary make install # build + install to $GOBIN -make test # run E2E test suite +make test # run deterministic CI tests +make test-integration # opt-in CLI E2E and Harness boundary tests mnemon setup # interactive setup mnemon setup --eject # remove all integrations make help # show all targets @@ -395,7 +396,7 @@ See [Development and Deployment](docs/DEPLOYMENT.md) for Docker, Compose, Ollama ## Documentation - [Mnemon Harness Beta](harness/README.md) — experimental host-agent lifecycle state -- [Go Engineering Standard](docs/development/go-engineering-standard.md) — maintainability, concurrency, persistence, testing, and quality ratchets +- [Go Engineering Standard](docs/development/go-engineering-standard.md) — maintainability, concurrency, persistence, testing, and review thresholds - [Design & Architecture](docs/DESIGN.md) — current engine architecture, algorithms, integration design - [Usage & Reference](docs/USAGE.md) — CLI commands, embedding support, architecture overview - [Memory Import Guide](docs/IMPORT.md) — schema and LLM prompt for importing historical chats diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index d7cc5761..1dc44eec 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -6,15 +6,15 @@ Prerequisites: - Go 1.24.6 or newer in the 1.24 series - `make` -- `jq` for the E2E test script +- `jq` only when running the opt-in CLI E2E/integration suite Common commands: ```bash make deps make build -make unit -make test +make test # deterministic CI suite +make test-integration # optional E2E/process/Docker suite make install ``` diff --git a/docs/development/go-engineering-standard.md b/docs/development/go-engineering-standard.md index 301461d8..6779b5e3 100644 --- a/docs/development/go-engineering-standard.md +++ b/docs/development/go-engineering-standard.md @@ -218,13 +218,10 @@ does not own durable business policy; it owns process lifecycle. ## 8. Tests and change discipline - A behavior change MUST carry a focused test in the same logical commit. -- Requirement evidence MUST bind every declared `path::test-symbol` to the - accepted history that introduced or accepted it. When a manifest stores test - symbols and accepted commits as separate arrays, each test symbol MUST exist - in the current tree and in at least one of that requirement's accepted commit - trees; a declaration with no accepted commit is invalid. This existential - rule avoids adding positional pairing semantics while allowing one commit to - accept multiple tests and multiple commits to contribute separate tests. +- Protocol documents describe required behavior; ordinary tests directly + assert its observable consequences. Do not introduce a second manifest that + binds prose, test symbols, commands, and commit history merely to certify the + tests themselves. - A behavior-preserving refactor SHOULD begin with characterization tests when the current invariant is not already executable. - Test builders MAY compress setup. They MUST NOT merge independent replay, @@ -238,12 +235,11 @@ does not own durable business policy; it owns process lifecycle. - Feature work and broad behavior-neutral refactoring SHOULD be separate logical commits. Each commit remains buildable, reviewable, and revertible. -## 9. Quality ratchet +## 9. Review thresholds -Mnemon uses new-code thresholds immediately. A scope MAY adopt a tracked -baseline ratchet so existing debt does not force an unsafe big-bang rewrite; -R5 is required to do so at its 7Q checkpoint. Until a scope has a baseline, -reviews apply the thresholds qualitatively and MUST NOT claim baseline evidence. +The following values are review signals for new or meaningfully rewritten +production code. They help reviewers find unclear ownership or excessive +change amplification; they are not a separate source of architectural truth. For new or meaningfully rewritten production code: @@ -256,96 +252,27 @@ For new or meaningfully rewritten production code: | control-flow nesting | <= 4 | > 4 without an exact exception | | normalized duplicate block | none | >= 150 tokens | -R5 additionally targets at most 400 lines for a hand-written production file -and 800 lines for an individual hand-written test file. These are -responsibility-split signals, not permission to hide code generation or combine -statements. - -In a scope with an adopted baseline, existing violations MUST NOT increase. The -baseline MUST use a stable identity appropriate to the rule rather than a line -number: function metrics use rule + path + symbol, file metrics use rule + path, -and duplicate groups use an immutable repository-assigned debt ID plus the -sorted owning path/symbol tuple. A normalized content fingerprint is matching -evidence, not the primary identity, so partial cleanup does not become false -new debt. In the v1 duplicate ratchet, a debt ID's fingerprint is immutable and -its owners may only remain exact or shrink to a strict subset; rebinding the -fingerprint, adding an owner, or matching ambiguous evidence is prohibited. If -strict owner cleanup removes the first sorted owner, the duplicate entry's -derived path follows the first remaining owner without changing its debt ID. -Anonymous-function identities MUST use stable lexical context, a -descendant-independent direct shape/metric key, and an ordinal from their first -appearance; sibling cardinality and source-line position are not identity -inputs. Closures may share an ordinal group only when their complete ratcheted -observations—including actual metrics, duplicate tokens, and recursive child -structure—are interchangeable; an otherwise ambiguous collision fails closed. -When a measured value improves but remains above threshold, the same change -MUST lower its baseline ceiling; it may not retain the old allowance. A removed -or repaired violation is removed from the baseline; the baseline only -decreases. The history gate retains per-commit lineage ledgers from the fixed -baseline source commit, merges those ledgers across actual Git parent edges, -and checks each merge against every relevant parent. An identity that ever -appeared in the baseline cannot later become an exception, and a removed -exception becomes a lifetime tombstone that cannot be resurrected. An exception -MUST be exact, reviewed, justified with risk, and include an owner or removal -checkpoint. Wildcard exceptions and unexplained `//nolint` directives are -prohibited. - -When a scope enforces these rules automatically, new-code exceptions MUST live -in a separate machine-readable manifest rather than raising the debt baseline. -The manifest identifies the exact rule/path/symbol or component, reason, risk, -owner, removal checkpoint, and a measured ceiling that follows the same -non-increasing ratchet. A tracked baseline identity cannot be reclassified as -an exception. An exception cannot waive correctness, security, -release-isolation, authority/parity, unowned-goroutine, unbounded-resource, or -required-race rules, nor the absolute prohibition on new cyclomatic complexity -above 30. R5 creates its tracked manifest at -`harness/test/contracts/go_quality_exceptions.json` during 7Q. - -Generated Go and Go files under `testdata` are included by default. Excluding -either category from complexity, size, and duplication measurement requires an -exact entry in the canonical tracked -`harness/test/contracts/go_quality_exclusions.json`; a generated entry also -requires Go's canonical generated-code directive. Such an entry is metric-only: -format, explained `//nolint`, and dependency checks still inspect the file. - -Static architecture findings and their machine-readable debt entries are an -exact bidirectional set: a new finding requires an entry, and an auto-detected -entry becomes stale as soon as its finding disappears. Lifecycle, resource, -authority, and similar manually reviewed rules remain path/symbol-evidence -contracts rather than pretending to be auto-detected. - -The ratchet is a change-safety mechanism, not an instruction to optimize a -global score. Security guards, protocol stages, and distinct test oracles do not -count as wasteful duplication. +Large files and functions SHOULD be split when that creates clearer ownership, +not to satisfy a line-count score. Security guards, protocol stages, and +independent failure oracles do not count as wasteful duplication. If a future +analyzer materially improves review, it must remain a direct check with one +configuration source; it must not grow a parallel evidence ledger. ## 10. Executable gates -The repository currently provides these relevant commands: +The repository exposes three independently invoked test levels: ```sh -gofmt -w -go build -o mnemon . -go test ./... -go vet ./... -bash scripts/e2e_test.sh - -make harness-build -go -C harness test ./... -go -C harness test -race ./... -go -C harness vet ./... -make harness-validate # managed-asset and action-declaration validation -make harness-quality # pinned format/static/dependency/debt ratchets -make harness-verify # complete exact-tree R7 evidence report +make test # deterministic CI: unit, projection, and static architecture +make test-integration # opt-in CLI E2E, timing, race, process, and Docker boundaries +make test-live # explicit paid Pi/DeepSeek evaluation ``` -`make harness-validate` is not a full Harness quality or verification gate. -The Harness uses the pinned, tracked `make harness-quality` target for -format/static analysis, dependency checks, and complexity/duplication ratchets. -`make harness-verify` remains the complete R7 evidence gate, including the -expensive race, Docker-case, and deletion proofs; it is run for -Harness-affecting changes and scheduled deep verification rather than every -release-path change. Focused package and scenario checks should be invoked -directly instead of adding overlapping umbrella targets. +Regular CI runs only `make test` and must not depend on real daemon readiness, +wall-clock scenario outcomes, Docker, or a provider. Integration deliberately +rechecks the complete Harness under race, process, and Docker boundaries; paid +provider evaluation remains a separate explicit gate. Focused packages may be +invoked directly during development, but do not add another umbrella target. Any added analyzer MUST have a repository-owned version and configuration and be reproducible in CI. Do not depend on a developer's global tool version or @@ -366,4 +293,5 @@ Before accepting a Go change, answer: point with completeness tests? 7. Did the change preserve independent failure oracles and run the proportional build, test, race, and static gates? -8. Does the quality baseline stay level or improve, with no broader exception? +8. Did the change avoid introducing a second test registry, evidence ledger, or + duplicate orchestration path? diff --git a/docs/harness/QUICKSTART.md b/docs/harness/QUICKSTART.md index f007d20f..2543f765 100644 --- a/docs/harness/QUICKSTART.md +++ b/docs/harness/QUICKSTART.md @@ -81,12 +81,16 @@ required Artifacts, and re-admitting the delivery. Repository maintainers run: ```sh -make harness-verify +make test +make test-integration ``` -During local iteration, run the focused Go package or scenario being changed; -do not add another umbrella Make target for each test grouping. +Regular CI runs only `make test`. Run `make test-integration` explicitly when +CLI E2E, timing, process, transport, or Docker boundaries change. During local +iteration, run the focused Go package or scenario being changed. The levels are +deliberately separate and do not invoke one another. -The gate proves the ten R7 invariants, local continuity, federated -re-admission, three data-only collaboration cases, and that deleting all case -descriptions leaves the generic Core conformance suite working. +The direct suites prove the ten R7 invariants, local continuity, federated +re-admission, and the data-only collaboration cases. A plain Go architecture +test keeps case vocabulary, fixtures, and the optional R8 selector outside the +R7 Core dependency graph. diff --git a/docs/harness/USAGE.md b/docs/harness/USAGE.md index 879e4a78..45d12ba6 100644 --- a/docs/harness/USAGE.md +++ b/docs/harness/USAGE.md @@ -100,21 +100,17 @@ arbitrary same-UID code with local shell and file access. ```sh make harness-build -make harness-quality +make test ``` -For managed integration asset changes: +For race, process, and Docker boundaries: ```sh -make harness-validate +make test-integration ``` -For the complete evidence gate: - -```sh -make harness-verify -``` - -`make harness-verify` is the complete local R7 evidence gate. See +Regular CI runs only `make test`; the integration command is an explicit local +or manually dispatched boundary check. Paid Pi/DeepSeek evaluation uses +`make test-live`. See [r7-core-contract.md](r7-core-contract.md) for normative behavior and [r7-module-layout.md](r7-module-layout.md) for the enforced package boundary. diff --git a/docs/harness/r7-core-contract.md b/docs/harness/r7-core-contract.md index 11703df1..fa3e8ae8 100644 --- a/docs/harness/r7-core-contract.md +++ b/docs/harness/r7-core-contract.md @@ -783,46 +783,29 @@ Not guaranteed: A collaboration description may require an Agent to choose a different target. mnemond does not promote that requirement into a global rule. -## 9. Conformance gates +## 9. Verification levels -| Gate | Requirement | +R7 uses direct tests rather than a second contract registry: + +| Level | Requirement | |---|---| -| `G-R7-CORE` | Unit, race, process, and pinned Pi Runtime suites cover P-01 through P-10 and every named sub-assertion in section 10 with independent oracles. | -| `G-R7-CASES` | `review/`, `contract-net/`, and `blackboard/` fixtures all exist under `harness/testdata/r7/cases/`, run, and pass their independent deterministic oracles. | -| `G-R7-PATTERN-FREE` | In a temporary copy, deleting both `harness/testdata/r7/examples/` and `harness/testdata/r7/cases/` leaves the P-01 through P-10 Core conformance command passing; case acceptance and case-presence checks are excluded from this deletion run. | -| `G-R7-NO-CASE-KIND` | No production Go source contains a case-specific kind literal such as `review.request`. | -| `G-R7-CASE-DATA-ONLY` | Every executable or behavior-bearing case definition, prompt, playbook, expected output, oracle, and fixture is confined to `harness/testdata/r7/cases/`. Files under `harness/testdata/r7/examples/` are non-executable generic syntax illustrations; runners and case oracles never read them. Contracts and registries may name cases and gates but cannot encode their behavior; no production Go, schema, managed asset, or example contains case behavior. | -| `G-R7-ONE-PATH` | One candidate binary digest runs all three cases through the same CLI, Event structure, Handling lifecycle, and peer path. | -| `G-R7-CONTINUITY` | After daemon restart, a fresh Runtime process with no prior transcript resumes from a newly projected View derived from durable Event, Handling, Reference, and Artifact state. | -| `G-R7-FEDERATION` | On a pre-enrolled node retaining durable identity and trust, a fresh Runtime resumes from locally re-admitted PeerDelivery, verified Artifact, and referenced description alone. | -| `G-R7-ROOT-ISOLATION` | No release-path command imports `harness/`. | -| `G-R7-AUTHORITY-CUTOVER` | In activation mode, the candidate tree has exactly one ACTIVE Core contract, marks R5 and older authority claims RETIRED/HISTORICAL, points every parser, registry, Make/CI gate, and active Harness document to R7, and is the exact tree bound by the activation report. | - -The generality proof is the conjunction of P-02, `G-R7-CASES`, -`G-R7-PATTERN-FREE`, `G-R7-NO-CASE-KIND`, `G-R7-CASE-DATA-ONLY`, and -`G-R7-ONE-PATH`. This replaces -"two Go packages share an interface", which can be satisfied by designing for -symmetry. The data-only gate is change-isolation evidence, not by itself a -claim that the protocol is universal. - -## 10. Evidence bindings - -Each invariant and gate has an evidence result derived from the current run; -that result is never written into this document as contract lifecycle status. -The tracked `harness/test/contracts/r7-requirements.json` is the sole evidence -binding authority. Its versioned schema contains two closed lists: - -- `invariants`: P-01 through P-10, each bound to exact - `package::test-symbol` or deterministic oracle names; -- `gates`: every gate in section 9, each bound to its required step IDs, exact - argv, and independent oracle. - -A machine-generated gate report binds the source tree, this contract digest, -requirements registry digest, exact executed commands, exit results, and -output digests. The validator rejects a missing, unknown, duplicate, skipped, -or unexecuted binding; commands hard-coded only in validator code have no -authority. This contract cannot activate while any invariant or gate is -unbound, partially proven, or failing. +| `make test` | Deterministic unit, projection, observer, and static architecture tests pass without real daemon readiness, CLI E2E, wall-clock scenarios, Docker, or providers. | +| `make test-integration` | When explicitly requested, CLI E2E, full Harness package tests, Core race checks, the generic R7 Docker case runner, the Pi attachment boundary, the real service world, and the isolated R8 network pass once each. | +| `make test-live` | When explicitly authorized, real Pi/DeepSeek turns exercise the local and federated product scenarios. Model prose is never the pass/fail oracle. | + +The ordinary Go architecture tests enforce root/Harness isolation, the exact +package dependency graph, one interactive attachment issuer, absence of +case-specific Core vocabulary, and data-only case fixtures. The Docker cases +then prove that one candidate image runs different collaboration descriptions +through the same CLI, Event, Handling, Artifact, Receipt, and peer paths. + +## 10. Direct evidence + +The table below is a human-readable review index, not a machine registry. Test +names and fixture layout may be refactored without synchronizing a second +manifest, provided the direct observable behaviors remain independently +asserted. A passing test command is evidence; a generated report about that +command is not another authority. | ID | Required independently asserted behavior | |---|---| @@ -837,30 +820,17 @@ unbound, partially proven, or failing. | P-09 | Any missing or mismatched Artifact keeps peer input unadmitted and cannot activate a Reference or complete; every Agent/peer resource bound, including the 64 reply-observations-per-anchor limit, fails closed independently and payload cannot raise it; pending-reply inspection is indexed by the exact local anchor rather than scanning retained delivery history; Pi executes no more than sixteen exploration calls plus two calls to its fixed no-shell Effect-settlement tool per governed run, never re-enables a Host-disabled tool, blocks excess calls, and after cutoff allows only one bounded final-response turn after the last settlement attempt; automatic continuation cannot refresh either budget; the maximum accepted payload, Artifact, Reference, route, current, observation, and related combination remains representable; related evidence has explicit prefix/count/truncation, and JSON-escaped current plus related payloads cannot overflow the focus or canonical View budget; more than the expiry-maintenance limit settles only the bounded prefix and leaves all excess claims unchanged for later natural turns. | | P-10 | Only explicit completed with attached verified Artifact projects completed; other terminal outcomes close without Artifact; final/exit/idle/provider/ACK/disposition, terminal reply observations, and the pending-observation projection cannot complete or settle the requester anchor or prohibit an explicit non-completed resolution. | -Ten rows is the point. A row may bind several test symbols, but it is verified -only when every named behavior in that row has independent evidence. A ledger -small enough to close is worth more than a comprehensive one that never does. +The ten rows keep the protocol reviewable. They do not require ten test files +or freeze test-symbol names. Independent replay, stale-fence, restart, +corruption, authorization, and race failures must not be merged merely to make +the suite smaller. ## 11. Activation and retirement -There is never a period in which an unmeasured contract holds authority. - -``` -The activated tree, with evidence 10/10 and every gate in section 9 green - R7 marked ACTIVE - R5 marked RETIRED - all contract parsers, registries, Make targets, CI, and active Harness docs - point to R7 - make harness-verify runs the R7 gate - -On a non-authority branch this remains a candidate status marker. The switch -becomes effective atomically when that unchanged tree reaches the protected -authority branch. -``` - -Status markers on a non-authority branch are candidates, not authority. CI -proves the candidate tree rather than a commit hash, so the same tree can become -authoritative after merge without a self-referential evidence cycle. +R7 is ACTIVE and R5 is RETIRED. An ordinary architecture test scans the tracked +Core contract headers and requires exactly one ACTIVE document. No parser, +registry, commit-history ledger, or generated activation report participates in +protocol authority. On retirement, `docs/harness/r5-core-contract.md` records in its header: the contract that supersedes it, the reason, that retirement takes effect in the @@ -869,27 +839,11 @@ stops growing, that it is reproducible only on a historical branch or tag, and that it no longer constrains the active Harness. It must not embed the hash of the commit that contains its own retirement text. -The candidate and authority gates machine-check that exactly one tracked Core -contract is marked ACTIVE. At the same switch, every older tracked Harness -document that calls itself an authority, frozen face, or active ABI is either -updated for R7 or explicitly marked HISTORICAL/RETIRED; active quickstarts must -not teach an R7-forbidden registry or schema-loader path. - -### 11.1 Engineering prerequisites - -These are prerequisites for running the evidence, not part of the protocol -model: - -- `cd harness && go build ./...` succeeds on darwin; -- the contract tool parses `PROPOSED | ACTIVE | RETIRED`, rejects an activation - candidate tree without exactly one ACTIVE Core contract, and binds reports to - the exact candidate tree; -- the fast release-path CI gate cannot generate or claim a complete R7 evidence - report; Harness-affecting changes and scheduled verification run the complete - `make harness-verify` gate. - -Without them the ledger cannot be produced on the maintainer's own machine or -enforced on merge, and `10/10` would be a number in a document. +Active quickstarts must not teach an R7-forbidden registry or schema-loader +path. `make test` is the only regular CI gate. Boundary-affecting changes run +`make test-integration` explicitly before merge; its timing and process tests +are not regular CI. The paid `make test-live` evaluation remains explicit and +cannot make an otherwise failing deterministic tree authoritative. ## 12. Non-normative material @@ -898,7 +852,7 @@ Nothing in this section is a requirement. | Artifact | Role | |---|---| | `harness/.../mnemond.md` | One-page Agent-facing projection: how to read a View, submit an Intent, read a Receipt, and save or supersede a collaboration description. It is a projection, never authority, and must not contain an ordered workflow. | -| `harness/testdata/r7/examples/view-intent-receipt.md` | A non-executable, pattern-neutral syntax illustration. It contains no topology, fault schedule, expected outcome, or oracle; runners never read it. It must be deletable — see `G-R7-PATTERN-FREE`. | +| `harness/testdata/r7/examples/view-intent-receipt.md` | A non-executable, pattern-neutral syntax illustration. It contains no topology, fault schedule, expected outcome, or oracle; runners never read it. Deleting it must leave the Core tests green. | | Case 1, Case 2, Case 3 | Review, Contract Net, and Blackboard exist only as Markdown descriptions, fixtures, and independent oracles under `harness/testdata/r7/cases/`; each case directory is its sole behavior authority. | | `.mnemon-dev/architecture/r7/` | Derivation history. No authority. | | `.mnemon-dev/research/magent-wiki/` | A pattern corpus an Agent may read on demand. No pattern is built in. | diff --git a/docs/harness/r7-module-layout.md b/docs/harness/r7-module-layout.md index 45eeee9e..6d365121 100644 --- a/docs/harness/r7-module-layout.md +++ b/docs/harness/r7-module-layout.md @@ -264,25 +264,24 @@ no longer exists. C6 landed in the exact candidate tree that: -- has R7 evidence at 10/10 with every section 9 gate green; +- passed the direct R7 unit, architecture, process, and Docker suites; - marks R7 ACTIVE and R5 RETIRED; -- points every parser, registry, Make target, and CI gate at R7; +- points active Harness documentation and test entry points at R7; -and satisfies `G-R7-AUTHORITY-CUTOVER`. +with no remaining R5 implementation dependency. -## 5. Steady-state structural oracles +## 5. Steady-state architecture tests -These checks are machine-readable evidence under the closed Core gates; they -are not additional Gate identifiers. R8 deletion remains an R8 authorization -condition and cannot expand the R7 gate set. +These checks are ordinary Go tests under `harness/test/architecture`. They are +direct assertions, not entries in a separate evidence registry. -| Existing binding | Structural oracle | +| Structural assertion | Meaning | |---|---| -| `G-R7-AUTHORITY-CUTOVER` | `agency` has an empty internal import set. | -| `G-R7-AUTHORITY-CUTOVER` | `authority`'s internal import set is exactly `{agency}`. | -| R8 deletion condition | After removing `internal/selector`, both R7 commands build and every R7 Go conformance package passes. | -| `G-R7-NO-CASE-KIND` | No production Go contains `channel`, `teamwork`, `review`, `contract-net`, `blackboard`, or `memory.wiki` as a semantic identifier. They may appear only as opaque `kind` values in testdata. | -| `G-R7-AUTHORITY-CUTOVER` | `internal/` contains exactly the seven R7 packages in section 2 and may additionally contain only the optional `selector`; no dependency edge contradicts the graph there. | +| `agency` has no internal import and `authority` imports only `agency`. | Canonical values do not depend on execution or storage. | +| No R7 Core package imports `selector`. | The R8 mechanism remains an optional island. | +| Production Go contains no case-specific semantic literal or fixture path. | Collaboration patterns remain data, not Core dispatch. | +| Attachment issuance has one authority declaration and one daemon caller. | T0 has an interactive boundary, not a hidden managed wake path. | +| `internal/` contains exactly the packages in section 2 plus optional `selector`. | A second domain model cannot silently return. | One human-readable check accompanies them: **every package states what it owns in one sentence.** Today's `store` cannot — it holds channel, peer, artifact, @@ -291,8 +290,7 @@ R8 selector owns only its private selection state. ## 6. What this document does not authorize -- Any change to `r7-core-contract.md` behavior, invariants, gates, or evidence - bindings. +- Any change to `r7-core-contract.md` behavior or invariants. - Wiring `selector`. R8 activation is gated by its own preconditions, including a proven local outcome projection. - Any release-path change. The root `mnemon`, `mnemon setup`, and Legacy Memory diff --git a/docs/zh/README.md b/docs/zh/README.md index 2fd5f0e9..5c1e50c4 100644 --- a/docs/zh/README.md +++ b/docs/zh/README.md @@ -331,7 +331,8 @@ Sub-agent 委派是可选执行策略。当 runtime 支持时,主 agent 可以 ```bash make build # 构建二进制 make install # 构建 + 安装到 $GOBIN -make test # 运行 E2E 测试套件 +make test # 运行确定性 CI 测试 +make test-integration # 按需运行 CLI E2E 与 Harness 边界测试 mnemon setup # 交互式设置(检测环境 + 部署钩子/技能/引导) mnemon setup --eject # 移除所有集成 make help # 显示所有目标 diff --git a/docs/zh/harness/USAGE.md b/docs/zh/harness/USAGE.md index bcec56be..7ce90243 100644 --- a/docs/zh/harness/USAGE.md +++ b/docs/zh/harness/USAGE.md @@ -59,13 +59,16 @@ go -C harness build -o ../mnemon-harness ./cmd/mnemon-harness ## 4. 验证声明 -仓库维护者可以验证规范的 managed Host 资产和 Teamwork action 声明: +仓库维护者可以运行确定性测试与真实集成测试: ```sh -make harness-validate +make test +make test-integration ``` -这是开发检查,不是普通用户工作流的一部分。 +普通 CI 只运行 `make test`;涉及 CLI E2E、时序、进程、传输或 Docker +边界时,才显式运行 `make test-integration`。付费的 Pi/DeepSeek 场景使用 +`make test-live`。这些是开发检查,不是普通用户工作流的一部分。 ## Trust model — a governance contract, not a sandbox diff --git a/harness/README.md b/harness/README.md index b2f3fbbe..6ddba0b0 100644 --- a/harness/README.md +++ b/harness/README.md @@ -21,23 +21,24 @@ Collaboration patterns are data-only descriptions and fixtures. The Core does not contain a Channel model, Teamwork registry, workflow engine, or semantic dispatch by Event kind. -Use the fast development path for ordinary changes: +Use the fast deterministic path for ordinary changes: ```sh make harness-build -make harness-quality +make test ``` -Run `make harness-validate` when changing managed integration assets. Run the -complete evidence path only when required: +Run the opt-in CLI E2E, timing, race, process, and Docker boundary suite when +those surfaces change: ```sh -make harness-verify +make test-integration ``` -`harness-verify` is the full exact-tree evidence gate, including race, Docker -case, and deletion proofs. Observer, domain-operations, and R8 checks remain -focused suites rather than additional umbrella Make targets. +Regular CI runs only `make test`. Integration and paid Pi/DeepSeek evaluation +are explicit `make test-integration` and `make test-live` operations. The three +levels do not invoke one another, and direct behavior tests—not a separate +evidence registry—decide pass or fail. See [the Harness documentation](../docs/harness/README.md), the [quickstart](../docs/harness/QUICKSTART.md), and the active diff --git a/harness/internal/daemon/ensure_test.go b/harness/internal/daemon/ensure_test.go index ca789a64..df0a76ec 100644 --- a/harness/internal/daemon/ensure_test.go +++ b/harness/internal/daemon/ensure_test.go @@ -304,17 +304,26 @@ func startEnsureRuntime(t *testing.T, state string) (*Runtime, chan error) { serveErrors := make(chan error, 1) go func() { serveErrors <- runtime.Serve(context.Background()) }() deadline := time.Now().Add(5 * time.Second) - for { - ready, err := probeDaemonStatus(context.Background(), state) - if err == nil && ready { + var lastProbeErr error + for time.Now().Before(deadline) { + ready, probeErr := probeDaemonStatus(context.Background(), state) + if probeErr == nil && ready { return runtime, serveErrors } - if err != nil || !time.Now().Before(deadline) { - stopEnsureRuntime(t, runtime, serveErrors) - t.Fatalf("started daemon did not become ready: %v", err) + if probeErr != nil { + lastProbeErr = probeErr + } + select { + case serveErr := <-serveErrors: + t.Fatalf("started daemon exited before readiness: serve=%v last_probe=%v", + serveErr, lastProbeErr) + default: } time.Sleep(10 * time.Millisecond) } + stopEnsureRuntime(t, runtime, serveErrors) + t.Fatalf("started daemon did not become ready before deadline: last_probe=%v", lastProbeErr) + return nil, nil } func stopEnsureRuntime(t *testing.T, runtime *Runtime, serveErrors chan error) { diff --git a/harness/internal/selector/simtest/simulator_test.go b/harness/internal/selector/simtest/simulator_test.go index c3bd6c09..2988c0ae 100644 --- a/harness/internal/selector/simtest/simulator_test.go +++ b/harness/internal/selector/simtest/simulator_test.go @@ -193,26 +193,6 @@ func TestR8FrozenProfileExposesOppositeThresholdCounterexample(t *testing.T) { } } -func TestR8PartitionAtTauIsCharacterized(t *testing.T) { - experiment := experiment{ - nodes: 64, percentA: 50, fault: faultPartition, - partitionDuration: marginThreshold, - } - if experiment.effectivePartitionDuration() < marginThreshold { - t.Fatalf("partition duration %d is below tau %d", - experiment.effectivePartitionDuration(), marginThreshold) - } - for _, seed := range experimentSeeds { - t.Run(experiment.name(seed), func(t *testing.T) { - got := runSelection(t, experiment, seed, false) - assertSelectionAccounting(t, experiment, got, sampleSize) - t.Logf("tau-partition threshold=%dA/%dB opposite=%t inconclusive=%d epochs=%d node_rounds=%d messages=%d", - got.thresholdA, got.thresholdB, got.oppositeThreshold, got.inconclusive, - got.epochs, got.nodeRounds, got.messages) - }) - } -} - func TestR8StrategicByzantineUsesOneRequesterSpecificVote(t *testing.T) { experiment := experiment{ nodes: 32, percentA: 50, fault: faultStrategic, faultPercent: 20, @@ -245,29 +225,6 @@ func TestR8StrategicByzantineUsesOneRequesterSpecificVote(t *testing.T) { } } -func TestR8N128TwentyPercentFaultCharacterization(t *testing.T) { - experiments := []experiment{ - {nodes: 128, percentA: 55, fault: faultRefusal, faultPercent: 20}, - {nodes: 128, percentA: 55, fault: faultEquivocate, faultPercent: 20}, - {nodes: 128, percentA: 55, fault: faultStrategic, faultPercent: 20}, - { - nodes: 128, percentA: 50, fault: faultPartition, - partitionDuration: marginThreshold, - }, - } - for _, experiment := range experiments { - for _, seed := range experimentSeeds { - t.Run(experiment.name(seed), func(t *testing.T) { - sampled := runSelection(t, experiment, seed, false) - assertSelectionAccounting(t, experiment, sampled, sampleSize) - t.Logf("adversarial threshold=%dA/%dB opposite=%t inconclusive=%d epochs=%d node_rounds=%d messages=%d", - sampled.thresholdA, sampled.thresholdB, sampled.oppositeThreshold, sampled.inconclusive, - sampled.epochs, sampled.nodeRounds, sampled.messages) - }) - } - } -} - func TestR8FrozenHoldoutDistributions(t *testing.T) { experiments := []experiment{ {nodes: 128, percentA: 55, fault: faultNone}, diff --git a/harness/test/architecture/harness_structure_test.go b/harness/test/architecture/harness_structure_test.go new file mode 100644 index 00000000..c25363a6 --- /dev/null +++ b/harness/test/architecture/harness_structure_test.go @@ -0,0 +1,337 @@ +package architecture_test + +import ( + "go/ast" + "go/parser" + "go/token" + "os" + "path/filepath" + "slices" + "strconv" + "strings" + "testing" +) + +func TestHarnessArchitecture(t *testing.T) { + root := harnessModuleRoot(t) + t.Run("package dependencies match the frozen graph", func(t *testing.T) { + assertPackageGraph(t, root) + }) + t.Run("collaboration cases stay out of Core", func(t *testing.T) { + assertNoCaseKindsInProduction(t, root) + }) + t.Run("attachments have one interactive issuer", func(t *testing.T) { + assertInteractiveAttachmentOnly(t, root) + }) + t.Run("selector is not a Core dependency", func(t *testing.T) { + assertCoreDoesNotImportSelector(t, root) + }) + t.Run("case semantics stay in fixtures", func(t *testing.T) { + assertCaseFixturesAreDataOnly(t, root) + }) + t.Run("Core does not depend on fixture paths", func(t *testing.T) { + assertNoFixturePathsInProduction(t, root) + }) +} + +func TestExactlyOneActiveHarnessContract(t *testing.T) { + directory := filepath.Join(repositoryRoot(t), "docs", "harness") + entries, err := os.ReadDir(directory) + if err != nil { + t.Fatal(err) + } + var active []string + for _, entry := range entries { + if entry.IsDir() || !strings.HasSuffix(entry.Name(), "-core-contract.md") { + continue + } + raw, err := os.ReadFile(filepath.Join(directory, entry.Name())) + if err != nil { + t.Fatal(err) + } + lines := strings.SplitN(string(raw), "\n", 8) + for _, line := range lines { + if strings.HasPrefix(strings.TrimSpace(line), "Status: **ACTIVE**") { + active = append(active, entry.Name()) + break + } + } + } + if len(active) != 1 || active[0] != "r7-core-contract.md" { + t.Fatalf("active Harness contracts = %v, want [r7-core-contract.md]", active) + } +} + +func assertNoCaseKindsInProduction(t *testing.T, root string) { + t.Helper() + forEachProductionGoFile(t, root, func(path string, file *ast.File) { + ast.Inspect(file, func(node ast.Node) bool { + literal, ok := node.(*ast.BasicLit) + if !ok || literal.Kind != token.STRING { + return true + } + value, err := strconv.Unquote(literal.Value) + if err != nil { + t.Errorf("%s: unquote string: %v", path, err) + return true + } + for _, forbidden := range []string{ + "review.", "contract-net.", "blackboard.", + "memory.wiki.", "teamwork.", "channel.", + } { + if strings.Contains(strings.ToLower(value), forbidden) { + t.Errorf("%s contains case-specific production literal %q", path, value) + } + } + return true + }) + }) +} + +func assertPackageGraph(t *testing.T, root string) { + t.Helper() + want := map[string][]string{ + "internal/agency": {}, + "internal/attach": {}, + "internal/authority": {"internal/agency"}, + "internal/cas": {"internal/agency"}, + "internal/cli": {"internal/agency"}, + "internal/daemon": {"internal/agency", "internal/authority", "internal/cas", "internal/peerlink"}, + "internal/peerlink": {"internal/agency", "internal/cas"}, + "internal/selector": {"internal/agency"}, + "cmd/mnemon-harness": {"internal/attach", "internal/cli", "internal/daemon"}, + "cmd/mnemond": {"internal/daemon"}, + } + got := make(map[string]map[string]struct{}, len(want)) + for component := range want { + got[component] = map[string]struct{}{} + } + forEachProductionGoFile(t, root, func(path string, file *ast.File) { + component := harnessComponent(t, root, path) + if _, ok := want[component]; !ok { + t.Errorf("unexpected production component %q", component) + return + } + for _, spec := range file.Imports { + importPath, err := strconv.Unquote(spec.Path.Value) + if err != nil { + t.Errorf("%s: unquote import: %v", path, err) + continue + } + if importPath == "github.com/libp2p/go-libp2p-core" || + strings.HasPrefix(importPath, "github.com/libp2p/go-libp2p-core/") { + t.Errorf("%s imports retired libp2p Core path %q", path, importPath) + } + const prefix = modulePath + "/harness/" + if !strings.HasPrefix(importPath, prefix) { + continue + } + dependency := harnessImportComponent(strings.TrimPrefix(importPath, prefix)) + if dependency != component { + got[component][dependency] = struct{}{} + } + } + }) + for component, expected := range want { + actual := make([]string, 0, len(got[component])) + for dependency := range got[component] { + actual = append(actual, dependency) + } + slices.Sort(actual) + slices.Sort(expected) + if !slices.Equal(actual, expected) { + t.Errorf("%s dependencies = %v, want %v", component, actual, expected) + } + } +} + +func assertInteractiveAttachmentOnly(t *testing.T, root string) { + t.Helper() + var declarations, calls []string + forEachProductionGoFile(t, root, func(path string, file *ast.File) { + ast.Inspect(file, func(node ast.Node) bool { + switch value := node.(type) { + case *ast.FuncDecl: + if strings.HasPrefix(value.Name.Name, "Issue") && + strings.HasSuffix(value.Name.Name, "Attachment") { + declarations = append(declarations, filepath.ToSlash(path)+"::"+value.Name.Name) + } + case *ast.CallExpr: + selector, ok := value.Fun.(*ast.SelectorExpr) + if ok && strings.HasPrefix(selector.Sel.Name, "Issue") && + strings.HasSuffix(selector.Sel.Name, "Attachment") { + calls = append(calls, filepath.ToSlash(path)+"::"+selector.Sel.Name) + } + } + return true + }) + }) + assertSingleArchitectureMatch(t, declarations, "/internal/authority/", "IssueInteractiveAttachment", + "attachment issuer declaration") + assertSingleArchitectureMatch(t, calls, "/internal/daemon/", "IssueInteractiveAttachment", + "attachment issuer call") +} + +func assertCoreDoesNotImportSelector(t *testing.T, root string) { + t.Helper() + selectorImport := modulePath + "/harness/internal/selector" + forEachProductionGoFile(t, root, func(path string, file *ast.File) { + if strings.Contains(filepath.ToSlash(path), "/internal/selector/") { + return + } + for _, spec := range file.Imports { + importPath, err := strconv.Unquote(spec.Path.Value) + if err != nil { + t.Errorf("%s: unquote import: %v", path, err) + continue + } + if importPath == selectorImport || strings.HasPrefix(importPath, selectorImport+"/") { + t.Errorf("%s imports optional selector package %q", path, importPath) + } + } + }) +} + +func assertCaseFixturesAreDataOnly(t *testing.T, root string) { + t.Helper() + casesRoot := filepath.Join(root, "testdata", "r7", "cases") + entries, err := os.ReadDir(casesRoot) + if err != nil { + t.Fatal(err) + } + var names []string + for _, entry := range entries { + if !entry.IsDir() { + continue + } + name := entry.Name() + names = append(names, name) + for _, file := range []string{"nodes.txt", "playbook.md", "oracle.sh"} { + info, err := os.Stat(filepath.Join(casesRoot, name, file)) + if err != nil || info.Size() == 0 { + t.Errorf("case fixture %s/%s is missing or empty", name, file) + } + if file == "oracle.sh" && err == nil && info.Mode()&0o111 == 0 { + t.Errorf("case fixture %s/%s is not executable", name, file) + } + } + } + if len(names) == 0 { + t.Fatal("no R7 collaboration case fixtures") + } + + for _, runnerName := range []string{"lib.sh", "run_cases.sh"} { + runner, err := os.ReadFile(filepath.Join(root, "test", "r7", "runner", runnerName)) + if err != nil { + t.Fatal(err) + } + text := strings.ToLower(string(runner)) + for _, forbidden := range append(slices.Clone(names), "examples/") { + if strings.Contains(text, strings.ToLower(forbidden)) { + t.Errorf("generic runner %s contains case-specific token %q", runnerName, forbidden) + } + } + } + + examplesRoot := filepath.Join(root, "testdata", "r7", "examples") + err = filepath.WalkDir(examplesRoot, func(path string, entry os.DirEntry, walkErr error) error { + if walkErr != nil { + return walkErr + } + if !entry.IsDir() { + info, err := entry.Info() + if err != nil { + return err + } + if info.Mode()&0o111 != 0 { + t.Errorf("example is executable: %s", path) + } + } + return nil + }) + if err != nil { + t.Fatal(err) + } +} + +func assertNoFixturePathsInProduction(t *testing.T, root string) { + t.Helper() + forEachProductionGoFile(t, root, func(path string, file *ast.File) { + ast.Inspect(file, func(node ast.Node) bool { + literal, ok := node.(*ast.BasicLit) + if !ok || literal.Kind != token.STRING { + return true + } + value, err := strconv.Unquote(literal.Value) + if err != nil { + return true + } + for _, forbidden := range []string{ + "testdata/r7/examples", "testdata/r7/cases", "testdata/r7/domain-ops", + } { + if strings.Contains(filepath.ToSlash(value), forbidden) { + t.Errorf("%s refers to fixture path %q", path, value) + } + } + return true + }) + }) +} + +func forEachProductionGoFile(t *testing.T, root string, visit func(string, *ast.File)) { + t.Helper() + for _, base := range []string{filepath.Join(root, "internal"), filepath.Join(root, "cmd")} { + err := filepath.WalkDir(base, func(path string, entry os.DirEntry, walkErr error) error { + if walkErr != nil { + return walkErr + } + if entry.IsDir() { + if entry.Name() == "testdata" { + return filepath.SkipDir + } + return nil + } + if !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") { + return nil + } + file, err := parser.ParseFile(token.NewFileSet(), path, nil, parser.SkipObjectResolution) + if err != nil { + return err + } + visit(path, file) + return nil + }) + if err != nil { + t.Fatalf("scan %s: %v", base, err) + } + } +} + +func harnessComponent(t *testing.T, root, path string) string { + t.Helper() + relative, err := filepath.Rel(root, path) + if err != nil { + t.Fatal(err) + } + parts := strings.Split(filepath.ToSlash(relative), "/") + if len(parts) < 3 || parts[0] != "internal" && parts[0] != "cmd" { + t.Fatalf("production source has no component: %s", relative) + } + return parts[0] + "/" + parts[1] +} + +func harnessImportComponent(importPath string) string { + parts := strings.Split(importPath, "/") + if len(parts) < 2 { + return importPath + } + return parts[0] + "/" + parts[1] +} + +func assertSingleArchitectureMatch(t *testing.T, matches []string, directory, want, label string) { + t.Helper() + if len(matches) != 1 || !strings.Contains(matches[0], directory) || + !strings.HasSuffix(matches[0], "::"+want) { + t.Fatalf("%s = %v, want one %s in %s", label, matches, want, directory) + } +} diff --git a/harness/test/contracts/release_boundary_test.go b/harness/test/architecture/release_boundary_test.go similarity index 93% rename from harness/test/contracts/release_boundary_test.go rename to harness/test/architecture/release_boundary_test.go index f7657e3f..c1d39a5a 100644 --- a/harness/test/contracts/release_boundary_test.go +++ b/harness/test/architecture/release_boundary_test.go @@ -1,4 +1,4 @@ -package contracts_test +package architecture_test import ( "bytes" @@ -96,7 +96,7 @@ func assertHarnessPackages(t *testing.T, root string) { assertHarnessPackageDirectory(t, filepath.Join(root, "harness", "internal")) } -func TestR7InternalPackageSetAllowsSelectorDeletion(t *testing.T) { +func TestHarnessInternalPackageSet(t *testing.T) { assertHarnessPackageDirectory(t, filepath.Join(harnessModuleRoot(t), "internal")) } @@ -156,20 +156,6 @@ func assertRootHelpIsReleaseOnly(t *testing.T, root string) { } } -func TestR7HasNoManagedWakeIssuancePath(t *testing.T) { - root := repositoryRoot(t) - command := exec.Command("bash", "harness/test/r7/runner/run_no_managed_wake.sh") - command.Dir = root - output, err := command.CombinedOutput() - if err != nil { - t.Fatalf("managed-wake structural oracle: %v\n%s", err, output) - } - want := []byte("r7 static oracle passed: no managed attachment issuance surface\n") - if !bytes.Equal(output, want) { - t.Fatalf("managed-wake structural oracle output = %q, want %q", output, want) - } -} - type moduleBoundary struct { path, goVersion string packages []string diff --git a/harness/test/contracts/repository_hygiene_test.go b/harness/test/architecture/repository_hygiene_test.go similarity index 92% rename from harness/test/contracts/repository_hygiene_test.go rename to harness/test/architecture/repository_hygiene_test.go index a62d5978..becd98cd 100644 --- a/harness/test/contracts/repository_hygiene_test.go +++ b/harness/test/architecture/repository_hygiene_test.go @@ -1,4 +1,4 @@ -package contracts_test +package architecture_test import ( "bytes" @@ -92,10 +92,6 @@ func TestRepositoryHygieneRulesRejectGeneratedFiles(t *testing.T) { func TestRepositoryHygieneRulesAcceptDurableJSONCategories(t *testing.T) { for _, trackedPath := range []string{ - "harness/test/contracts/r7-requirements.json", - "harness/test/contracts/go_quality_baseline.json", - "harness/test/contracts/go_architecture_debt.json", - "harness/test/observer/trace-schema.json", "internal/setup/assets/openclaw/plugin/openclaw.plugin.json", "internal/setup/assets/openclaw/plugin/package.json", } { @@ -239,16 +235,6 @@ func temporaryJSONNameReason(trackedPath string) string { func durableJSONCategory(trackedPath string) string { switch { - case trackedPath == "harness/test/contracts/r7-requirements.json": - return "contract registry" - case trackedPath == "harness/test/contracts/go_architecture_debt.json": - return "quality" - case strings.HasPrefix(trackedPath, "harness/test/contracts/go_quality_") && - !strings.Contains(strings.TrimPrefix(trackedPath, - "harness/test/contracts/"), "/"): - return "quality" - case trackedPath == "harness/test/observer/trace-schema.json": - return "test trace schema" case strings.HasPrefix(trackedPath, "internal/setup/assets/"): return "managed asset" default: diff --git a/harness/test/contracts/go_architecture_debt.json b/harness/test/contracts/go_architecture_debt.json deleted file mode 100644 index 41f976f0..00000000 --- a/harness/test/contracts/go_architecture_debt.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "schema_version": 1, - "source_commit": "0be35cbb4251645d74b5daf5d60f7a4183661087", - "entries": [] -} diff --git a/harness/test/contracts/go_quality_baseline.json b/harness/test/contracts/go_quality_baseline.json deleted file mode 100644 index e96b07a9..00000000 --- a/harness/test/contracts/go_quality_baseline.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "schema_version": 1, - "tool_version": "harness-quality/v1", - "source_commit": "0be35cbb4251645d74b5daf5d60f7a4183661087", - "thresholds": [ - { - "rule": "cognitive_complexity", - "limit": 25 - }, - { - "rule": "control_flow_nesting", - "limit": 4 - }, - { - "rule": "cyclomatic_complexity", - "limit": 20 - }, - { - "rule": "function_logical_lines", - "limit": 80 - }, - { - "rule": "function_statements", - "limit": 50 - }, - { - "rule": "normalized_duplicate_tokens", - "limit": 149 - }, - { - "rule": "paired_test_file_lines", - "limit": 800 - }, - { - "rule": "production_file_lines", - "limit": 400 - } - ], - "entries": [] -} diff --git a/harness/test/contracts/go_quality_exceptions.json b/harness/test/contracts/go_quality_exceptions.json deleted file mode 100644 index df0e369a..00000000 --- a/harness/test/contracts/go_quality_exceptions.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "schema_version": 1, - "entries": [] -} diff --git a/harness/test/contracts/go_quality_exclusions.json b/harness/test/contracts/go_quality_exclusions.json deleted file mode 100644 index df0e369a..00000000 --- a/harness/test/contracts/go_quality_exclusions.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "schema_version": 1, - "entries": [] -} diff --git a/harness/test/contracts/r7-requirements.json b/harness/test/contracts/r7-requirements.json deleted file mode 100644 index 03b08c7c..00000000 --- a/harness/test/contracts/r7-requirements.json +++ /dev/null @@ -1,877 +0,0 @@ -{ - "schema_version": 1, - "invariants": [ - { - "id": "P-01", - "oracles": [ - { - "id": "forged-agent-authority-fails-closed", - "test": "./internal/agency::TestParseAgentIntentJSONRejectsAuthorityAndMalformedShapes" - }, - { - "id": "peer-origin-remains-provenance", - "test": "./internal/authority::TestPeerDeliveryRoundTripUsesTwoLocalAdmissions" - }, - { - "id": "pending-reply-observation-is-machine-derived", - "test": "./internal/authority::TestCurrentProjectsPendingReplyObservationAfterRemoteRoot" - }, - { - "id": "related-evidence-is-provenance-only", - "test": "./internal/agency::TestAgentViewProjectsRelatedEvidenceWithoutWritableSubjectAuthority" - }, - { - "id": "reply-observation-stales-prior-view", - "test": "./internal/authority::TestTerminalReplyObservationMakesPriorViewStale" - }, - { - "id": "stale-view-authority-fails-closed", - "test": "./internal/authority::TestAdmissionRequiresExactDurablyIssuedView" - }, - { - "id": "unoffered-handles-fail-closed", - "test": "./internal/agency::TestR7GapP01UnofferedHandlesFailClosed" - } - ] - }, - { - "id": "P-02", - "oracles": [ - { - "id": "machine-only-observation-consequences", - "test": "./internal/agency::TestObservationConsequencesRemainMachineOnly" - }, - { - "id": "open-labels-closed-shapes", - "test": "./internal/agency::TestR7GapP02OpenLabelsAndClosedShapes" - } - ] - }, - { - "id": "P-03", - "oracles": [ - { - "id": "accepted-reference-ends-agent-facing-boundary", - "test": "./internal/cli::TestAcceptedReferenceEndsAttachmentAndRejectsFurtherMutation" - }, - { - "id": "attachment-begin-replays-exactly-across-restart", - "test": "./internal/authority::TestInteractiveAttachmentBeginExactlyReplaysAcrossRestart" - }, - { - "id": "different-current-operations-share-one-claim", - "test": "./internal/authority::TestConcurrentDifferentCurrentOperationsCreateOneLiveClaim" - }, - { - "id": "ended-boundary-cannot-be-revived", - "test": "./internal/authority::TestInteractiveAttachmentBeginCannotReviveEndedBoundary" - }, - { - "id": "expired-attachment-outcome-never-reports-ready", - "test": "./internal/cli::TestHookAttachRejectsExpiredAuthorityOutcomeBeforeJournalCommit" - }, - { - "id": "hook-end-cannot-destroy-unpresented-receipt-replay", - "test": "./internal/cli::TestHookEndCannotDestroyUnpresentedReceiptReplay" - }, - { - "id": "hook-end-finishes-presented-terminal-without-intent-replay", - "test": "./internal/cli::TestHookEndFinishesPresentedHandlingWithoutIntentReplay" - }, - { - "id": "interactive-root-creates-durable-handling", - "test": "./internal/authority::TestLocalHandlingLoopRejectsStaleFenceAndRequiresArtifactForCompleted" - }, - { - "id": "managed-wake-is-not-reserved", - "test": "./internal/authority::TestAttachmentSchemaDoesNotReserveManagedWakeMode" - }, - { - "id": "managed-wake-issuance-path-is-absent", - "test": "./test/contracts::TestR7HasNoManagedWakeIssuancePath" - }, - { - "id": "missing-journal-new-boundary-delegates-authority-replacement", - "test": "./internal/cli::TestHookAttachMissingJournalDelegatesNewBoundaryReplacementToAuthority" - }, - { - "id": "new-boundary-finishes-presented-terminal-without-intent-replay", - "test": "./internal/cli::TestAcceptedHandlingReceiptRetainsReplayUntilBoundaryEndCommits" - }, - { - "id": "one-unended-attachment-per-principal", - "test": "./internal/authority::TestAttachmentSchemaAllowsOneUnendedInteractiveBoundaryPerPrincipal" - }, - { - "id": "ordinary-peer-request-creates-handling", - "test": "./internal/authority::TestOrdinaryPeerDeliveryStillCreatesHandling" - }, - { - "id": "pi-host-boundary-has-one-cue-and-bounded-receipt", - "test": "./internal/attach::TestLoadHasOneFixedCueOneBoundedReceiptAndNoAuthorityOrSecretSurface" - }, - { - "id": "pi-host-retries-one-private-boundary", - "test": "./internal/attach::TestPiHookRetriesOnePrivateBoundaryAndEmitsNoCueOnFailure" - }, - { - "id": "presented-terminal-cannot-reactivate-after-end-failure", - "test": "./internal/cli::TestPresentedTerminalCannotReactivateAfterBoundaryEndFailure" - }, - { - "id": "private-host-boundary-controls-attachment-lifecycle", - "test": "./internal/cli::TestHookAttachNewBoundaryEndsPredecessorAndRotatesEmptyCurrent" - }, - { - "id": "same-boundary-authority-replay-must-match-journal", - "test": "./internal/cli::TestHookAttachSameBoundaryRejectsDivergentAuthorityReplay" - }, - { - "id": "same-completed-boundary-never-reports-ready", - "test": "./internal/cli::TestSameHookAttachFinishesPresentedHandlingButNeverReturnsReady" - }, - { - "id": "terminal-reply-observation-creates-no-handling", - "test": "./internal/authority::TestTerminalReplyObservationCreatesNoHandlingAndLeavesAnchorOpen" - }, - { - "id": "wrong-attachment-cannot-claim", - "test": "./internal/authority::TestCurrentRejectsExpiredAndWrongAttachmentProof" - }, - { - "id": "wrong-principal-cannot-claim", - "test": "./internal/authority::TestCurrentNeverClaimsAnotherPrincipalsHandling" - } - ] - }, - { - "id": "P-04", - "oracles": [ - { - "id": "boundary-end-clears-only-occupancy", - "test": "./internal/authority::TestEndInteractiveAttachmentReleasesClaimWithoutDomainEffect" - }, - { - "id": "boundary-end-fault-rolls-back", - "test": "./internal/authority::TestEndInteractiveAttachmentFaultRollsBackEndAndClaim" - }, - { - "id": "boundary-end-races-admission-atomically", - "test": "./internal/authority::TestEndInteractiveAttachmentRacesAdmissionWithoutPartialEffect" - }, - { - "id": "expiry-clears-only-occupancy", - "test": "./internal/authority::TestFreshCurrentDurablySettlesExpiredClaimWithoutDomainEffect" - }, - { - "id": "fresh-boundaries-prevent-handling-attention-starvation", - "test": "./internal/authority::TestCurrentLeastAttendedSelectionSurvivesBoundaryWithoutAdvance" - }, - { - "id": "fresh-boundary-replacement-clears-only-occupancy", - "test": "./internal/authority::TestFreshBoundaryAtomicallyReplacesPredecessorWithoutDomainEffect" - }, - { - "id": "stale-fence-and-completion-floor", - "test": "./internal/authority::TestLocalHandlingLoopRejectsStaleFenceAndRequiresArtifactForCompleted" - } - ] - }, - { - "id": "P-05", - "oracles": [ - { - "id": "bound-intent-transaction-fault-matrix", - "test": "./internal/authority::TestBoundIntentFaultMatrixCommitsWholeOriginOutcomeOrNone" - }, - { - "id": "correlated-terminal-reply-transaction-fault", - "test": "./internal/authority::TestCorrelatedTerminalReplyOutboxFaultRestoresResponderHandling" - }, - { - "id": "peer-delivery-transaction-fault-matrix", - "test": "./internal/authority::TestVerifiedPeerDeliveryFaultMatrixCommitsWholeReceiverOutcomeOrNone" - }, - { - "id": "reference-transaction-fault-matrix", - "test": "./internal/authority::TestReferenceFaultMatrixCommitsLineageAndHeadAtomically" - }, - { - "id": "terminal-reply-observation-transaction-fault", - "test": "./internal/authority::TestTerminalReplyObservationFaultRollsBackEventAndInboxSettlement" - } - ] - }, - { - "id": "P-06", - "oracles": [ - { - "id": "correlated-terminal-reply-revalidates-route", - "test": "./internal/authority::TestCorrelatedTerminalReplyRejectsRevokedBoundRoute" - }, - { - "id": "delivery-id-envelope-conflict", - "test": "./internal/authority::TestPeerInboxRejectsSameDeliveryIDDifferentEnvelope" - }, - { - "id": "exact-correlated-terminal-reply-shape", - "test": "./internal/agency::TestExactCorrelatedTerminalReplyMayCloseResponderAnchor" - }, - { - "id": "guide-completed-terminal-binds", - "test": "./internal/attach::TestGuideResponseExampleAtomicallyClosesAndReturnsCorrelatedEvidence" - }, - { - "id": "guide-declined-terminal-binds", - "test": "./internal/attach::TestGuideDeclineExampleReturnsCorrelatedDisposition" - }, - { - "id": "imported-current-projects-authenticated-reply-role", - "test": "./internal/authority::TestImportedCurrentProjectsOneAuthenticatedReplyTarget" - }, - { - "id": "imported-handling-preserves-reply-context-across-advance", - "test": "./internal/authority::TestImportedHandlingKeepsReplyContextAcrossLocalAdvance" - }, - { - "id": "missing-artifact-creates-no-fact", - "test": "./internal/authority::TestMissingPeerArtifactExpiresWithoutCreatingDomainState" - }, - { - "id": "ordinary-imported-work-keeps-explicit-reply-role", - "test": "./internal/authority::TestOrdinaryImportedWorkCannotBecomeNoReplyBySemanticKind" - }, - { - "id": "ordinary-peer-delivery-still-creates-handling", - "test": "./internal/authority::TestOrdinaryPeerDeliveryStillCreatesHandling" - }, - { - "id": "ordinary-remote-effects-retain-anchor", - "test": "./internal/agency::TestRemoteEffectsRequireLocalResponsibilityAnchor" - }, - { - "id": "peer-route-projection-is-principal-scoped", - "test": "./internal/authority::TestPeerRouteProjectionIsScopedToAttachmentPrincipal" - }, - { - "id": "pending-reply-observation-clears-after-exact-terminal-observation", - "test": "./internal/authority::TestPendingReplyObservationClearsAfterExactTerminalObservation" - }, - { - "id": "pending-reply-observation-survives-settlement-and-local-advance", - "test": "./internal/authority::TestPendingReplyObservationSurvivesDeliverySettlementAndLocalAdvance" - }, - { - "id": "remote-failure-preserves-origin-anchor", - "test": "./internal/authority::TestRemoteRejectionAndExpiryLeaveOriginAnchorOpen" - }, - { - "id": "reply-required-projection-is-sealed", - "test": "./internal/agency::TestParseAgentViewCanonicalJSONRejectsNoncanonicalAndDivergentProjection" - }, - { - "id": "sole-target-terminal-origin-is-only-candidate", - "test": "./internal/agency::TestPeerDeliveryIdentifiesOnlySoleTargetTerminalReplyCandidate" - }, - { - "id": "terminal-reply-follows-bound-subject-advance-anchor", - "test": "./internal/authority::TestTerminalReplyFollowsBoundSubjectAdvanceAnchor" - }, - { - "id": "terminal-reply-observation-bound-fails-closed", - "test": "./internal/authority::TestTerminalReplyObservationBoundFailsClosedAtSixtyFour" - }, - { - "id": "terminal-reply-observation-concurrent-unique", - "test": "./internal/authority::TestConcurrentTerminalReplyObservationsAcceptExactlyOne" - }, - { - "id": "terminal-reply-observation-creates-no-handling", - "test": "./internal/authority::TestTerminalReplyObservationCreatesNoHandlingAndLeavesAnchorOpen" - }, - { - "id": "terminal-reply-observation-is-unique-per-outbound", - "test": "./internal/authority::TestTerminalReplyObservationIsUniquePerOutboundDelivery" - }, - { - "id": "terminal-reply-observation-projects-into-fresh-view", - "test": "./internal/authority::TestTerminalReplyObservationProjectsIntoFreshView" - }, - { - "id": "terminal-reply-observation-requires-exact-delivery-binding", - "test": "./internal/authority::TestTerminalReplyObservationRequiresExactInReplyToDeliveryBinding" - }, - { - "id": "terminal-reply-observation-revalidates-authority-tuple", - "test": "./internal/authority::TestTerminalReplyObservationRejectsWrongRouteRootPrincipalOrClosedAnchor" - }, - { - "id": "terminal-reply-observation-stales-prior-view", - "test": "./internal/authority::TestTerminalReplyObservationMakesPriorViewStale" - }, - { - "id": "trace-terminal-observation-binds-exact-outbound", - "test": "./test/r7/domainops/trace::TestGlobalDeliveryValidationBindsTerminalObservationToExactOutboundRequest" - }, - { - "id": "two-local-admissions", - "test": "./internal/authority::TestPeerDeliveryRoundTripUsesTwoLocalAdmissions" - } - ] - }, - { - "id": "P-07", - "oracles": [ - { - "id": "admission-digest-conflict-is-stable", - "test": "./internal/authority::TestAdmissionRejectsSameOperationKeyWithDifferentRequestDigest" - }, - { - "id": "admission-replay-precedes-mutable-authority", - "test": "./internal/authority::TestAcceptedOperationReplayPrecedesStaleSubjectAndReferenceAuthority" - }, - { - "id": "attachment-begin-operation-conflict-is-stable", - "test": "./internal/authority::TestInteractiveAttachmentBeginRejectsSameOperationDifferentPrincipal" - }, - { - "id": "attachment-begin-response-replays-byte-stably-across-restart", - "test": "./internal/daemon::TestAttachmentBeginResponseExactlyReplaysAcrossDaemonRestart" - }, - { - "id": "concurrent-current-replay-creates-one-effect", - "test": "./internal/authority::TestConcurrentCurrentReplayCreatesOneClaimAndOneOperation" - }, - { - "id": "current-replay-survives-restart-and-expiry", - "test": "./internal/authority::TestCurrentOperationReplaysFrozenViewAfterRestartAndExpiry" - }, - { - "id": "disposition-replay-survives-restart", - "test": "./internal/authority::TestClaimExpiryReplaySurvivesRestartAndRejectsDigestConflict" - }, - { - "id": "frozen-focus-does-not-absorb-later-events", - "test": "./internal/authority::TestCurrentKeepsOldestAnchorWritableAndProjectsCorrelatedPeerResult" - }, - { - "id": "reply-observation-deduplicates-outbound-delivery", - "test": "./internal/authority::TestTerminalReplyObservationIsUniquePerOutboundDelivery" - } - ] - }, - { - "id": "P-08", - "oracles": [ - { - "id": "citation-is-exact-and-nonmutating", - "test": "./internal/authority::TestReferenceCitationRecordsExactHeadWithoutMutatingLineage" - }, - { - "id": "concurrent-first-publish-has-one-winner", - "test": "./internal/authority::TestConcurrentReferenceCASAcceptsExactlyOneCandidate" - }, - { - "id": "concurrent-mutation-has-one-winner", - "test": "./internal/authority::TestConcurrentExistingReferenceHeadCASAcceptsExactlyOneMutation" - }, - { - "id": "forward-head-fails-closed", - "test": "./internal/authority::TestReferenceRejectsForwardHead" - }, - { - "id": "invalid-key-fails-closed", - "test": "./internal/agency::TestR7GapP08InvalidReferenceKeysFailClosed" - }, - { - "id": "stale-head-fails-closed", - "test": "./internal/authority::TestReferenceExistingHeadCASRejectsStaleMutation" - }, - { - "id": "tombstone-can-be-reactivated", - "test": "./internal/authority::TestReferenceCanRetractThenSupersedeTombstone" - }, - { - "id": "tombstone-retract-replays-only-same-operation", - "test": "./internal/authority::TestReferenceTombstoneRejectsFreshRetractAndReplaysOriginal" - } - ] - }, - { - "id": "P-09", - "oracles": [ - { - "id": "agent-view-last-line-bound-fails-closed", - "test": "./internal/agency::TestAgentViewFailsClosedAboveCanonicalByteLimit" - }, - { - "id": "artifact-input-bound", - "test": "./internal/authority::TestVerifyArtifactEnforcesExistingCASObjectBound" - }, - { - "id": "bounded-expiry-maintenance", - "test": "./internal/authority::TestClaimExpiryMaintenanceIsBoundedAndLeavesExcessClaimsExact" - }, - { - "id": "canonical-object-bounds-fail-closed", - "test": "./internal/agency::TestCanonicalObjectsHaveHardTotalByteLimits" - }, - { - "id": "causal-depth-fails-closed", - "test": "./internal/authority::TestCausalDepthRejectsMissingOrMismatchedAcceptedEvent" - }, - { - "id": "escaped-current-does-not-let-related-overflow-view", - "test": "./internal/authority::TestCurrentKeepsEscapedCurrentAndOmitsRelatedBeyondEncodedBudget" - }, - { - "id": "focus-prefix-and-payload-budget-are-explicit", - "test": "./internal/authority::TestFocusProjectionUsesDeterministicBoundedPrefixAndPayloadBudget" - }, - { - "id": "maximum-accepted-world-remains-readable", - "test": "./internal/authority::TestMaximumAcceptedWorldKeepsCurrentReadable" - }, - { - "id": "maximum-reference-and-payload-view-remains-readable", - "test": "./internal/agency::TestAgentViewMaximumReferenceAndPayloadShapeRemainsReadable" - }, - { - "id": "open-handling-count-fails-closed", - "test": "./internal/authority::TestOpenHandlingBoundRejectsAdditionalSuccessor" - }, - { - "id": "peer-artifacts-must-be-complete", - "test": "./internal/agency::TestVerifiedPeerDeliveryRequiresParsedEnvelopeAndCompleteArtifacts" - }, - { - "id": "peer-input-bounds-fail-closed", - "test": "./internal/agency::TestVerifiedPeerArtifactBounds" - }, - { - "id": "pending-reply-observation-query-is-anchor-bounded", - "test": "./internal/authority::TestPendingReplyObservationUsesAnchorIndex" - }, - { - "id": "pi-effect-settlement-is-fixed-and-no-shell", - "test": "./internal/attach::TestPiEffectSettlementUsesOneNativeBoundedToolWithoutShellInference" - }, - { - "id": "pi-exploration-and-effect-budgets-fail-closed", - "test": "./internal/attach::TestPiHookSeparatesExplorationFromBoundedEffectSettlement" - }, - { - "id": "reference-projection-bound-fails-closed", - "test": "./internal/authority::TestReferenceProjectionBoundDoesNotOfferPublishAboveLimit" - }, - { - "id": "reply-observation-bound-fails-closed", - "test": "./internal/authority::TestTerminalReplyObservationBoundFailsClosedAtSixtyFour" - }, - { - "id": "reply-observation-projection-rotation-is-bounded", - "test": "./internal/authority::TestTerminalReplyObservationRotationIsDeterministicAndBounded" - }, - { - "id": "semantic-payload-json-budget-fails-closed", - "test": "./internal/agency::TestPeerDeliveryRejectsOversizeCanonicalInputs" - }, - { - "id": "successor-bound-fails-closed", - "test": "./internal/agency::TestR7GapP09SuccessorBoundFailsClosed" - } - ] - }, - { - "id": "P-10", - "oracles": [ - { - "id": "completion-requires-verified-artifact", - "test": "./internal/authority::TestLocalHandlingLoopRejectsStaleFenceAndRequiresArtifactForCompleted" - }, - { - "id": "pending-reply-observation-does-not-prohibit-explicit-resolution", - "test": "./internal/authority::TestExplicitResolveRemainsLegalWithPendingReplyObservation" - }, - { - "id": "runtime-observations-cannot-complete", - "test": "./test/r7/process::TestHandlingSurvivesProcessAndDaemonBoundaries" - }, - { - "id": "terminal-reply-observation-cannot-settle-anchor", - "test": "./internal/authority::TestTerminalReplyObservationCreatesNoHandlingAndLeavesAnchorOpen" - } - ] - } - ], - "gates": [ - { - "id": "G-R7-AUTHORITY-CUTOVER", - "steps": [ - { - "id": "contract", - "kind": "go-test", - "argv": [ - "go", - "-C", - "harness", - "test", - "-json", - "./tools/corecontract", - "./tools/corecontract/cmd/core-gate", - "./test/contracts", - "-count=1" - ], - "oracles": [ - "test:./test/contracts::TestR7AuthorityCutover", - "test:./test/contracts::TestR7HasNoManagedWakeIssuancePath", - "test:./test/contracts::TestR7RequirementsRegistry", - "test:./test/contracts::TestReleaseBoundary" - ] - } - ] - }, - { - "id": "G-R7-CASE-DATA-ONLY", - "steps": [ - { - "id": "case-data-only", - "kind": "shell", - "argv": [ - "bash", - "harness/test/r7/runner/run_case_data_only.sh" - ], - "oracles": [ - "stdout:r7 static oracle passed: case behavior is data-only" - ] - } - ] - }, - { - "id": "G-R7-CASES", - "steps": [ - { - "id": "cases", - "kind": "shell", - "argv": [ - "bash", - "harness/test/r7/runner/run_cases.sh" - ], - "oracles": [ - "stdout:r7 case passed: blackboard", - "stdout:r7 case passed: contract-net", - "stdout:r7 case passed: review" - ] - } - ] - }, - { - "id": "G-R7-CONTINUITY", - "steps": [ - { - "id": "process", - "kind": "go-test", - "argv": [ - "go", - "-C", - "harness", - "test", - "-json", - "./test/r7/process", - "-count=1" - ], - "oracles": [ - "test:./test/r7/process::TestHandlingSurvivesProcessAndDaemonBoundaries" - ] - } - ] - }, - { - "id": "G-R7-CORE", - "steps": [ - { - "id": "core-race", - "kind": "go-test", - "argv": [ - "go", - "-C", - "harness", - "test", - "-json", - "-race", - "./internal/agency", - "./internal/authority", - "./internal/cas", - "./internal/peerlink", - "./internal/daemon", - "./internal/cli", - "./internal/attach", - "-count=1" - ], - "oracles": [ - "test:./internal/authority::TestOpenRejectsSecondWriter" - ] - }, - { - "id": "core-unit", - "kind": "go-test", - "argv": [ - "go", - "-C", - "harness", - "test", - "-json", - "./internal/agency", - "./internal/authority", - "./internal/cas", - "./internal/peerlink", - "./internal/daemon", - "./internal/cli", - "./internal/attach", - "-count=1" - ], - "oracles": [ - "test:./internal/agency::TestAgentViewFailsClosedAboveCanonicalByteLimit", - "test:./internal/agency::TestAgentViewMaximumReferenceAndPayloadShapeRemainsReadable", - "test:./internal/agency::TestAgentViewProjectsRelatedEvidenceWithoutWritableSubjectAuthority", - "test:./internal/agency::TestCanonicalObjectsHaveHardTotalByteLimits", - "test:./internal/agency::TestExactCorrelatedTerminalReplyMayCloseResponderAnchor", - "test:./internal/agency::TestObservationConsequencesRemainMachineOnly", - "test:./internal/agency::TestParseAgentIntentJSONRejectsAuthorityAndMalformedShapes", - "test:./internal/agency::TestParseAgentViewCanonicalJSONRejectsNoncanonicalAndDivergentProjection", - "test:./internal/agency::TestPeerDeliveryIdentifiesOnlySoleTargetTerminalReplyCandidate", - "test:./internal/agency::TestPeerDeliveryRejectsOversizeCanonicalInputs", - "test:./internal/agency::TestR7GapP01UnofferedHandlesFailClosed", - "test:./internal/agency::TestR7GapP02OpenLabelsAndClosedShapes", - "test:./internal/agency::TestR7GapP08InvalidReferenceKeysFailClosed", - "test:./internal/agency::TestR7GapP09SuccessorBoundFailsClosed", - "test:./internal/agency::TestRemoteEffectsRequireLocalResponsibilityAnchor", - "test:./internal/agency::TestVerifiedPeerArtifactBounds", - "test:./internal/agency::TestVerifiedPeerDeliveryRequiresParsedEnvelopeAndCompleteArtifacts", - "test:./internal/attach::TestGuideDeclineExampleReturnsCorrelatedDisposition", - "test:./internal/attach::TestGuideResponseExampleAtomicallyClosesAndReturnsCorrelatedEvidence", - "test:./internal/attach::TestLoadHasOneFixedCueOneBoundedReceiptAndNoAuthorityOrSecretSurface", - "test:./internal/attach::TestPiEffectSettlementUsesOneNativeBoundedToolWithoutShellInference", - "test:./internal/attach::TestPiHookRetriesOnePrivateBoundaryAndEmitsNoCueOnFailure", - "test:./internal/attach::TestPiHookSeparatesExplorationFromBoundedEffectSettlement", - "test:./internal/authority::TestAcceptedOperationReplayPrecedesStaleSubjectAndReferenceAuthority", - "test:./internal/authority::TestAdmissionRejectsSameOperationKeyWithDifferentRequestDigest", - "test:./internal/authority::TestAdmissionRequiresExactDurablyIssuedView", - "test:./internal/authority::TestAttachmentSchemaAllowsOneUnendedInteractiveBoundaryPerPrincipal", - "test:./internal/authority::TestAttachmentSchemaDoesNotReserveManagedWakeMode", - "test:./internal/authority::TestBoundIntentFaultMatrixCommitsWholeOriginOutcomeOrNone", - "test:./internal/authority::TestCausalDepthRejectsMissingOrMismatchedAcceptedEvent", - "test:./internal/authority::TestClaimExpiryMaintenanceIsBoundedAndLeavesExcessClaimsExact", - "test:./internal/authority::TestClaimExpiryReplaySurvivesRestartAndRejectsDigestConflict", - "test:./internal/authority::TestConcurrentCurrentReplayCreatesOneClaimAndOneOperation", - "test:./internal/authority::TestConcurrentDifferentCurrentOperationsCreateOneLiveClaim", - "test:./internal/authority::TestConcurrentExistingReferenceHeadCASAcceptsExactlyOneMutation", - "test:./internal/authority::TestConcurrentReferenceCASAcceptsExactlyOneCandidate", - "test:./internal/authority::TestConcurrentTerminalReplyObservationsAcceptExactlyOne", - "test:./internal/authority::TestCorrelatedTerminalReplyOutboxFaultRestoresResponderHandling", - "test:./internal/authority::TestCorrelatedTerminalReplyRejectsRevokedBoundRoute", - "test:./internal/authority::TestCurrentKeepsEscapedCurrentAndOmitsRelatedBeyondEncodedBudget", - "test:./internal/authority::TestCurrentKeepsOldestAnchorWritableAndProjectsCorrelatedPeerResult", - "test:./internal/authority::TestCurrentLeastAttendedSelectionSurvivesBoundaryWithoutAdvance", - "test:./internal/authority::TestCurrentNeverClaimsAnotherPrincipalsHandling", - "test:./internal/authority::TestCurrentOperationReplaysFrozenViewAfterRestartAndExpiry", - "test:./internal/authority::TestCurrentProjectsPendingReplyObservationAfterRemoteRoot", - "test:./internal/authority::TestCurrentRejectsExpiredAndWrongAttachmentProof", - "test:./internal/authority::TestEndInteractiveAttachmentFaultRollsBackEndAndClaim", - "test:./internal/authority::TestEndInteractiveAttachmentRacesAdmissionWithoutPartialEffect", - "test:./internal/authority::TestEndInteractiveAttachmentReleasesClaimWithoutDomainEffect", - "test:./internal/authority::TestExplicitResolveRemainsLegalWithPendingReplyObservation", - "test:./internal/authority::TestFocusProjectionUsesDeterministicBoundedPrefixAndPayloadBudget", - "test:./internal/authority::TestFreshBoundaryAtomicallyReplacesPredecessorWithoutDomainEffect", - "test:./internal/authority::TestFreshCurrentDurablySettlesExpiredClaimWithoutDomainEffect", - "test:./internal/authority::TestImportedCurrentProjectsOneAuthenticatedReplyTarget", - "test:./internal/authority::TestImportedHandlingKeepsReplyContextAcrossLocalAdvance", - "test:./internal/authority::TestInteractiveAttachmentBeginCannotReviveEndedBoundary", - "test:./internal/authority::TestInteractiveAttachmentBeginExactlyReplaysAcrossRestart", - "test:./internal/authority::TestInteractiveAttachmentBeginRejectsSameOperationDifferentPrincipal", - "test:./internal/authority::TestLocalHandlingLoopRejectsStaleFenceAndRequiresArtifactForCompleted", - "test:./internal/authority::TestMaximumAcceptedWorldKeepsCurrentReadable", - "test:./internal/authority::TestMissingPeerArtifactExpiresWithoutCreatingDomainState", - "test:./internal/authority::TestOpenHandlingBoundRejectsAdditionalSuccessor", - "test:./internal/authority::TestOrdinaryImportedWorkCannotBecomeNoReplyBySemanticKind", - "test:./internal/authority::TestOrdinaryPeerDeliveryStillCreatesHandling", - "test:./internal/authority::TestPeerDeliveryRoundTripUsesTwoLocalAdmissions", - "test:./internal/authority::TestPeerInboxRejectsSameDeliveryIDDifferentEnvelope", - "test:./internal/authority::TestPeerRouteProjectionIsScopedToAttachmentPrincipal", - "test:./internal/authority::TestPendingReplyObservationClearsAfterExactTerminalObservation", - "test:./internal/authority::TestPendingReplyObservationSurvivesDeliverySettlementAndLocalAdvance", - "test:./internal/authority::TestPendingReplyObservationUsesAnchorIndex", - "test:./internal/authority::TestReferenceCanRetractThenSupersedeTombstone", - "test:./internal/authority::TestReferenceCitationRecordsExactHeadWithoutMutatingLineage", - "test:./internal/authority::TestReferenceExistingHeadCASRejectsStaleMutation", - "test:./internal/authority::TestReferenceFaultMatrixCommitsLineageAndHeadAtomically", - "test:./internal/authority::TestReferenceProjectionBoundDoesNotOfferPublishAboveLimit", - "test:./internal/authority::TestReferenceRejectsForwardHead", - "test:./internal/authority::TestReferenceTombstoneRejectsFreshRetractAndReplaysOriginal", - "test:./internal/authority::TestRemoteRejectionAndExpiryLeaveOriginAnchorOpen", - "test:./internal/authority::TestTerminalReplyFollowsBoundSubjectAdvanceAnchor", - "test:./internal/authority::TestTerminalReplyObservationBoundFailsClosedAtSixtyFour", - "test:./internal/authority::TestTerminalReplyObservationCreatesNoHandlingAndLeavesAnchorOpen", - "test:./internal/authority::TestTerminalReplyObservationFaultRollsBackEventAndInboxSettlement", - "test:./internal/authority::TestTerminalReplyObservationIsUniquePerOutboundDelivery", - "test:./internal/authority::TestTerminalReplyObservationMakesPriorViewStale", - "test:./internal/authority::TestTerminalReplyObservationProjectsIntoFreshView", - "test:./internal/authority::TestTerminalReplyObservationRejectsWrongRouteRootPrincipalOrClosedAnchor", - "test:./internal/authority::TestTerminalReplyObservationRequiresExactInReplyToDeliveryBinding", - "test:./internal/authority::TestTerminalReplyObservationRotationIsDeterministicAndBounded", - "test:./internal/authority::TestVerifiedPeerDeliveryFaultMatrixCommitsWholeReceiverOutcomeOrNone", - "test:./internal/authority::TestVerifyArtifactEnforcesExistingCASObjectBound", - "test:./internal/cli::TestAcceptedHandlingReceiptRetainsReplayUntilBoundaryEndCommits", - "test:./internal/cli::TestAcceptedReferenceEndsAttachmentAndRejectsFurtherMutation", - "test:./internal/cli::TestHookAttachMissingJournalDelegatesNewBoundaryReplacementToAuthority", - "test:./internal/cli::TestHookAttachNewBoundaryEndsPredecessorAndRotatesEmptyCurrent", - "test:./internal/cli::TestHookAttachRejectsExpiredAuthorityOutcomeBeforeJournalCommit", - "test:./internal/cli::TestHookAttachSameBoundaryRejectsDivergentAuthorityReplay", - "test:./internal/cli::TestHookEndCannotDestroyUnpresentedReceiptReplay", - "test:./internal/cli::TestHookEndFinishesPresentedHandlingWithoutIntentReplay", - "test:./internal/cli::TestPresentedTerminalCannotReactivateAfterBoundaryEndFailure", - "test:./internal/cli::TestSameHookAttachFinishesPresentedHandlingButNeverReturnsReady", - "test:./internal/daemon::TestAttachmentBeginResponseExactlyReplaysAcrossDaemonRestart" - ] - }, - { - "id": "pi-runtime", - "kind": "shell", - "argv": [ - "bash", - "harness/test/r7/runtime/pi/run_delegate_oracle.sh" - ], - "oracles": [ - "stdout:pi Runtime oracle: PASS" - ] - }, - { - "id": "process", - "kind": "go-test", - "argv": [ - "go", - "-C", - "harness", - "test", - "-json", - "./test/r7/process", - "-count=1" - ], - "oracles": [ - "test:./test/r7/process::TestHandlingSurvivesProcessAndDaemonBoundaries" - ] - } - ] - }, - { - "id": "G-R7-FEDERATION", - "steps": [ - { - "id": "cases", - "kind": "shell", - "argv": [ - "bash", - "harness/test/r7/runner/run_cases.sh" - ], - "oracles": [ - "stdout:r7 case passed: blackboard", - "stdout:r7 case passed: contract-net", - "stdout:r7 case passed: review" - ] - }, - { - "id": "trace", - "kind": "go-test", - "argv": [ - "go", - "-C", - "harness", - "test", - "-json", - "./test/r7/domainops/trace", - "-count=1" - ], - "oracles": [ - "test:./test/r7/domainops/trace::TestGlobalDeliveryValidationBindsTerminalObservationToExactOutboundRequest" - ] - } - ] - }, - { - "id": "G-R7-NO-CASE-KIND", - "steps": [ - { - "id": "no-case-kind", - "kind": "shell", - "argv": [ - "bash", - "harness/test/r7/runner/run_no_case_kind.sh" - ], - "oracles": [ - "stdout:r7 static oracle passed: no production case-specific kind" - ] - } - ] - }, - { - "id": "G-R7-ONE-PATH", - "steps": [ - { - "id": "cases", - "kind": "shell", - "argv": [ - "bash", - "harness/test/r7/runner/run_cases.sh" - ], - "oracles": [ - "stdout:r7 case passed: blackboard", - "stdout:r7 case passed: contract-net", - "stdout:r7 case passed: review" - ] - } - ] - }, - { - "id": "G-R7-PATTERN-FREE", - "steps": [ - { - "id": "pattern-free", - "kind": "shell", - "argv": [ - "bash", - "harness/test/r7/runner/run_pattern_free.sh" - ], - "oracles": [ - "stdout:r7 static oracle passed: Core is pattern-free after deleting examples and cases" - ] - } - ] - }, - { - "id": "G-R7-ROOT-ISOLATION", - "steps": [ - { - "id": "contract", - "kind": "go-test", - "argv": [ - "go", - "-C", - "harness", - "test", - "-json", - "./tools/corecontract", - "./tools/corecontract/cmd/core-gate", - "./test/contracts", - "-count=1" - ], - "oracles": [ - "test:./test/contracts::TestR7AuthorityCutover", - "test:./test/contracts::TestR7HasNoManagedWakeIssuancePath", - "test:./test/contracts::TestR7RequirementsRegistry", - "test:./test/contracts::TestReleaseBoundary" - ] - } - ] - } - ] -} diff --git a/harness/test/contracts/requirements_test.go b/harness/test/contracts/requirements_test.go deleted file mode 100644 index 3fb6477c..00000000 --- a/harness/test/contracts/requirements_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package contracts_test - -import ( - "os" - "path/filepath" - "testing" - - "github.com/mnemon-dev/mnemon/harness/tools/corecontract" -) - -func TestR7RequirementsRegistry(t *testing.T) { - root := repositoryRoot(t) - contract, err := corecontract.Load(root) - if err != nil { - t.Fatal(err) - } - registry, err := corecontract.LoadRegistry(root) - if err != nil { - t.Fatal(err) - } - if err := corecontract.ValidateBindings(root, contract, registry); err != nil { - t.Fatal(err) - } -} - -func TestR7AuthorityCutover(t *testing.T) { - if err := corecontract.ValidateAuthorityCutover(repositoryRoot(t)); err != nil { - t.Fatal(err) - } -} - -func TestR7RequirementsPathIsTrackedAtTheOnlyCanonicalName(t *testing.T) { - root := repositoryRoot(t) - if _, err := os.Stat(filepath.Join(root, filepath.FromSlash(corecontract.RegistryPath))); err != nil { - t.Fatal(err) - } - if _, err := os.Stat(filepath.Join(root, "harness", "test", "contracts", "requirements.json")); !os.IsNotExist(err) { - t.Fatalf("legacy requirements.json remains: %v", err) - } -} diff --git a/harness/test/contracts/requirements_validation_test.go b/harness/test/contracts/requirements_validation_test.go deleted file mode 100644 index 42b386c0..00000000 --- a/harness/test/contracts/requirements_validation_test.go +++ /dev/null @@ -1,78 +0,0 @@ -package contracts_test - -import ( - "os" - "path/filepath" - "strings" - "testing" - - "github.com/mnemon-dev/mnemon/harness/tools/corecontract" -) - -func TestR7RegistryRejectsUnknownNullAndUnsortedBindings(t *testing.T) { - root := repositoryRoot(t) - data, err := os.ReadFile(filepath.Join(root, filepath.FromSlash(corecontract.RegistryPath))) - if err != nil { - t.Fatal(err) - } - contract, err := corecontract.Load(root) - if err != nil { - t.Fatal(err) - } - tests := []struct { - name string - edit func(corecontract.Registry) corecontract.Registry - want string - }{ - { - name: "unknown invariant", - edit: func(registry corecontract.Registry) corecontract.Registry { - registry.Invariants[0].ID = "P-99" - return registry - }, - want: "invariant IDs", - }, - { - name: "null invariant oracles", - edit: func(registry corecontract.Registry) corecontract.Registry { - registry.Invariants[0].Oracles = nil - return registry - }, - want: "no non-null oracle", - }, - { - name: "different shared step", - edit: func(registry corecontract.Registry) corecontract.Registry { - for index := range registry.Gates { - if registry.Gates[index].ID == "G-R7-ROOT-ISOLATION" { - registry.Gates[index].Steps[0].Argv = append( - registry.Gates[index].Steps[0].Argv, "-run=TestReleaseBoundary") - } - } - return registry - }, - want: "shared step contract differs", - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - registry, err := corecontract.DecodeRegistry(data) - if err != nil { - t.Fatal(err) - } - registry = test.edit(registry) - if err := corecontract.ValidateBindings(root, contract, registry); err == nil || - !strings.Contains(err.Error(), test.want) { - t.Fatalf("error = %v, want containing %q", err, test.want) - } - }) - } -} - -func TestR7RegistryStrictJSONRejectsUnknownFields(t *testing.T) { - data := []byte(`{"schema_version":1,"invariants":[],"gates":[],"requirements":[]}`) - if _, err := corecontract.DecodeRegistry(data); err == nil || - !strings.Contains(err.Error(), "unknown field") { - t.Fatalf("error = %v, want unknown field", err) - } -} diff --git a/harness/test/observer/README.md b/harness/test/observer/README.md index 40cbda3b..91287c0f 100644 --- a/harness/test/observer/README.md +++ b/harness/test/observer/README.md @@ -49,8 +49,8 @@ Truncation never changes trace integrity or the reported test result. ## Trace contract -`trace-schema.json` is the closed JSON Schema for one JSONL record. A complete -file has exactly this shape: +The Go `Writer` and strict decoder are the single closed definition of one +JSONL trace. A complete file has exactly this shape: ```text run header @@ -75,7 +75,7 @@ Every fact declares one evidence class: | `local_preference` | R8-local seed, color, round, or observation | | `assertion` | Independent test-oracle result | -The schema also closes the relation between each known `kind`, its allowed +The Go validator closes the relation between each known `kind`, its allowed `source.class`, and its `truth` class. A runtime or transport observation cannot rename itself as an accepted R7 effect; `r7.delivery.readmitted` is authored by the receiving local authority after re-admission, not by transport. Every R8 @@ -100,7 +100,7 @@ mismatch, or digest mismatch makes a trace incomplete. ## Mandatory redaction -The schema is metadata-only. A conforming trace must not contain: +The trace is metadata-only. A conforming trace must not contain: - prompts, messages, transcripts, model reasoning, or chain of thought; - shell commands, command arguments, environment contents, or tool results; @@ -119,7 +119,7 @@ inside a script element. ## Integration boundary -Runners may sanitize a runtime's temporary JSON stream into this schema before +Runners may sanitize a runtime's temporary JSON stream into this format before destroying the raw stream. A test-only, read-only exporter may snapshot durable R7 objects after a run and validate their canonical bytes. R8 test adapters may emit round summaries that they already own. None of these paths may: @@ -151,9 +151,8 @@ Run the focused observer checks with: go -C harness test ./test/observer ``` -The deep Harness workflow runs this focused command explicitly. The observer -is not part of the R7 Core evidence ledger, so release-path CI and -`make harness-verify` do not repeat it. -The checks validate the schema asset, strict JSONL decoding, bounds, +The deterministic Harness test sweep includes this package. The observer is a +diagnostic surface, not a protocol oracle and not a second evidence authority. +The checks validate strict JSONL decoding, bounds, redaction, trace linkage, footer digests, fixtures, Content Security Policy, and the absence of external resources or markup injection APIs. diff --git a/harness/test/observer/classification_test.go b/harness/test/observer/classification_test.go index e99139a4..a05fef6a 100644 --- a/harness/test/observer/classification_test.go +++ b/harness/test/observer/classification_test.go @@ -1,7 +1,6 @@ package observer import ( - "slices" "sort" "strings" "testing" @@ -33,15 +32,11 @@ func factClassificationRows() []string { return result } -func TestFactClassificationMatchesSchemaAndBrowser(t *testing.T) { +func TestFactClassificationMatchesBrowser(t *testing.T) { html := string(readFile(t, "index.html")) - root := decodeJSONObject(t, readFile(t, "trace-schema.json"), "schema root") - factVariant := schemaRecordVariant(t, arrayField(t, root, "oneOf"), "fact") - - assertSameStrings(t, "schema fact classifications", - schemaFactClassificationRows(t, factVariant), factClassificationRows()) assertSameStrings(t, "browser fact classifications", - javascriptStringArray(t, html, "const FACT_CLASSIFICATION_ROWS"), factClassificationRows()) + javascriptStringArray(t, html, "const FACT_CLASSIFICATION_ROWS"), + factClassificationRows()) } func TestFactClassificationFailsClosed(t *testing.T) { @@ -74,51 +69,3 @@ func TestFactClassificationFailsClosed(t *testing.T) { t.Fatal("R8 fact without SelectionID was accepted") } } - -func schemaFactClassificationRows(t *testing.T, factVariant map[string]any) []string { - t.Helper() - constraints := arrayField(t, factVariant, "allOf") - if len(constraints) != 1 { - t.Fatalf("fact classification constraint count = %d, want 1", len(constraints)) - } - constraint, ok := constraints[0].(map[string]any) - if !ok { - t.Fatal("fact classification constraint is not an object") - } - branches := arrayField(t, constraint, "oneOf") - var rows []string - for _, value := range branches { - branch, ok := value.(map[string]any) - if !ok { - t.Fatal("fact classification branch is not an object") - } - properties := objectField(t, branch, "properties") - kinds := stringArrayField(t, objectField(t, properties, "kind"), "enum") - source := objectField(t, objectField(t, properties, "source"), "properties") - sourceClass, _ := objectField(t, source, "class")["const"].(string) - truth, _ := objectField(t, properties, "truth")["const"].(string) - if sourceClass == "" || truth == "" { - t.Fatal("fact classification branch has no source or truth constant") - } - for _, kind := range kinds { - rows = append(rows, kind+"|"+sourceClass+"|"+truth) - } - } - if len(rows) != len(factClassifications) { - t.Fatalf("schema classification count = %d, want %d", len(rows), len(factClassifications)) - } - if !slices.Equal(knownFactKinds(), sortedKindsFromRows(rows)) { - t.Fatal("schema classifications do not cover every known kind exactly") - } - return rows -} - -func sortedKindsFromRows(rows []string) []string { - result := make([]string, 0, len(rows)) - for _, row := range rows { - kind, _, _ := strings.Cut(row, "|") - result = append(result, kind) - } - sort.Strings(result) - return result -} diff --git a/harness/test/observer/display_contract_test.go b/harness/test/observer/display_contract_test.go index cb104c56..b46b1782 100644 --- a/harness/test/observer/display_contract_test.go +++ b/harness/test/observer/display_contract_test.go @@ -3,20 +3,11 @@ package observer import ( "fmt" "reflect" - "slices" "strings" "testing" ) -func TestTraceVersionMatchesSchemaAndBrowser(t *testing.T) { - root := decodeJSONObject(t, readFile(t, "trace-schema.json"), "schema root") - for _, record := range []string{"run", "fact", "result"} { - variant := schemaRecordVariant(t, arrayField(t, root, "oneOf"), record) - version, ok := objectField(t, variant, "properties")["version"].(map[string]any) - if !ok || version["const"] != float64(traceVersion) { - t.Fatalf("%s schema version = %#v, want %d", record, version["const"], traceVersion) - } - } +func TestTraceVersionMatchesBrowser(t *testing.T) { html := string(readFile(t, "index.html")) want := fmt.Sprintf("const TRACE_VERSION = %d;", traceVersion) if !strings.Contains(html, want) { @@ -24,6 +15,16 @@ func TestTraceVersionMatchesSchemaAndBrowser(t *testing.T) { } } +func TestBrowserVocabularyMatchesGo(t *testing.T) { + html := string(readFile(t, "index.html")) + assertSameStrings(t, "browser fact fields", + javascriptStringArray(t, html, "const FACT_FIELDS"), jsonFields(reflect.TypeOf(factsWire{}))) + assertSameStrings(t, "browser source classes", + javascriptStringArray(t, html, "const SOURCE_CLASSES"), sourceClasses) + assertSameStrings(t, "browser truth classes", + javascriptStringArray(t, html, "const TRUTH_CLASSES"), truthClasses) +} + func TestObserverKeepsScenarioSemanticsInTraceData(t *testing.T) { html := string(readFile(t, "index.html")) for _, forbidden := range []string{ @@ -54,91 +55,6 @@ func TestObserverKeepsScenarioSemanticsInTraceData(t *testing.T) { } } -func TestTraceSchemaRequiresMinimumDisplayEvidence(t *testing.T) { - root := decodeJSONObject(t, readFile(t, "trace-schema.json"), "schema root") - factVariant := schemaRecordVariant(t, arrayField(t, root, "oneOf"), "fact") - outer := arrayField(t, factVariant, "allOf") - if len(outer) != 1 { - t.Fatalf("fact constraint count = %d, want one combined constraint", len(outer)) - } - combined, ok := outer[0].(map[string]any) - if !ok { - t.Fatal("combined fact constraint is not an object") - } - conditions := arrayField(t, combined, "allOf") - actual := make(map[string][]string, len(conditions)) - for _, value := range conditions { - condition, ok := value.(map[string]any) - if !ok { - t.Fatal("display-evidence condition is not an object") - } - ifProperties := objectField(t, objectField(t, condition, "if"), "properties") - kind, _ := objectField(t, ifProperties, "kind")["const"].(string) - if kind == "" { - t.Fatal("display-evidence condition has no kind") - } - thenProperties := objectField(t, objectField(t, condition, "then"), "properties") - for _, objectName := range []string{"facts", "refs"} { - object, present := thenProperties[objectName].(map[string]any) - if !present { - continue - } - for _, field := range stringArrayField(t, object, "required") { - actual[kind] = append(actual[kind], objectName+"."+field) - } - } - slices.Sort(actual[kind]) - } - expected := map[string][]string{ - "runtime.domain.operation": {"facts.action", "facts.attempt_count", "facts.batched_unattributed_count", "facts.invalid_result_count", "facts.success_count", "facts.tool_error_count"}, - "runtime.view.received": {"facts.action", "facts.has_current", "facts.open_total", "facts.related_projected", "facts.related_total", "facts.truncated"}, - "runtime.intent.denied": {"facts.action", "facts.code", "facts.count"}, - "r7.event.accepted": {"facts.consequence", "facts.semantic_kind", "refs.event", "refs.event_digest"}, - "r7.handling.resolved": {"facts.outcome", "facts.state", "refs.handling"}, - "test.attention.wave": {"facts.episode", "facts.occupied_claims", "facts.open_unclaimed", "facts.role", "facts.round", "facts.turn_limit", "facts.turns_used"}, - "test.attention.outcome": {"facts.episode", "facts.goal_digest", "facts.goal_satisfied", "facts.occupied_claims", "facts.open_unclaimed", "facts.role", "facts.round", "facts.turn_limit", "facts.turns_used"}, - "test.attention.exhausted": {"facts.episode", "facts.goal_digest", "facts.goal_satisfied", "facts.occupied_claims", "facts.open_unclaimed", "facts.role", "facts.round", "facts.turn_limit", "facts.turns_used"}, - "test.attention.quiescent": {"facts.episode", "facts.goal_digest", "facts.goal_satisfied", "facts.occupied_claims", "facts.open_unclaimed", "facts.role", "facts.round", "facts.turn_limit", "facts.turns_used"}, - "test.attention.occupied": {"facts.episode", "facts.occupied_claims", "facts.open_unclaimed", "facts.role", "facts.round", "facts.turn_limit", "facts.turns_used"}, - "test.gate.checked": {"facts.gate_id", "facts.status"}, - "r8.selection.seeded": {"facts.phase", "facts.preference_after"}, - "r8.round.frozen": {"facts.alpha", "facts.margin_before", "facts.preference_before", "facts.round", "facts.sample_size"}, - "r8.vote.observed": {"facts.authenticated", "facts.round", "facts.votes_a", "facts.votes_b"}, - "r8.round.settled": {"facts.margin_after", "facts.margin_before", "facts.phase", "facts.preference_after", "facts.preference_before", "facts.recolored", "facts.round"}, - "r8.observation.produced": {"facts.margin_after", "facts.phase", "facts.preference_after", "facts.result", "facts.round"}, - } - if !reflect.DeepEqual(actual, expected) { - t.Fatalf("display-evidence schema = %#v, want %#v", actual, expected) - } -} - -func TestFinalAttentionSemanticsMatchSchema(t *testing.T) { - root := decodeJSONObject(t, readFile(t, "trace-schema.json"), "schema root") - factsDefinition := objectField(t, objectField(t, root, "$defs"), "facts") - paired := objectField(t, factsDefinition, "dependentRequired") - if !slices.Equal(stringArrayField(t, paired, "goal_digest"), []string{"goal_satisfied"}) || - !slices.Equal(stringArrayField(t, paired, "goal_satisfied"), []string{"goal_digest"}) { - t.Fatalf("goal metadata pairing = %#v", paired) - } - factVariant := schemaRecordVariant(t, arrayField(t, root, "oneOf"), "fact") - combined := arrayField(t, factVariant, "allOf")[0].(map[string]any) - conditions := arrayField(t, combined, "allOf") - want := map[string]map[string]any{ - "test.attention.outcome": {"goal_satisfied": true, "occupied_claims": float64(0)}, - "test.attention.exhausted": {"goal_satisfied": false, "occupied_claims": float64(0)}, - "test.attention.quiescent": {"goal_satisfied": false, "occupied_claims": float64(0), "open_unclaimed": float64(0)}, - } - got := collectAttentionConstants(t, conditions, want) - if !reflect.DeepEqual(got, want) { - t.Fatalf("attention schema semantics = %#v, want %#v", got, want) - } - occupiedForbiddenFields := collectOccupiedForbiddenFields(t, conditions) - slices.Sort(occupiedForbiddenFields) - if !slices.Equal(occupiedForbiddenFields, []string{"goal_digest", "goal_satisfied"}) { - t.Fatal("attention schema permits occupied evidence to depend on an external goal") - } -} - func TestFinalAttentionSemanticsMatchBrowser(t *testing.T) { html := string(readFile(t, "index.html")) for _, required := range []string{ @@ -158,99 +74,7 @@ func TestFinalAttentionSemanticsMatchBrowser(t *testing.T) { } } -func collectAttentionConstants(t *testing.T, conditions []any, - want map[string]map[string]any, -) map[string]map[string]any { - t.Helper() - result := make(map[string]map[string]any, len(want)) - for _, value := range conditions { - condition, ok := value.(map[string]any) - if !ok { - t.Fatal("attention condition is not an object") - } - kind := schemaConditionKind(t, condition) - if _, tracked := want[kind]; !tracked { - continue - } - facts := schemaConditionFacts(t, condition) - properties := objectField(t, facts, "properties") - result[kind] = make(map[string]any, len(properties)) - for field, value := range properties { - definition, ok := value.(map[string]any) - if !ok { - t.Fatalf("%s.%s constraint is not an object", kind, field) - } - result[kind][field] = definition["const"] - } - } - return result -} - -func collectOccupiedForbiddenFields(t *testing.T, conditions []any) []string { - t.Helper() - for _, value := range conditions { - condition, ok := value.(map[string]any) - if !ok { - t.Fatal("attention condition is not an object") - } - if schemaConditionKind(t, condition) != "test.attention.occupied" { - continue - } - var result []string - facts := schemaConditionFacts(t, condition) - for _, raw := range arrayField(t, objectField(t, facts, "not"), "anyOf") { - forbidden, ok := raw.(map[string]any) - if !ok { - t.Fatal("occupied forbidden-goal condition is not an object") - } - result = append(result, stringArrayField(t, forbidden, "required")...) - } - return result - } - t.Fatal("occupied attention condition is absent") - return nil -} - -func schemaConditionKind(t *testing.T, condition map[string]any) string { - t.Helper() - kind, _ := objectField(t, - objectField(t, objectField(t, condition, "if"), "properties"), "kind")["const"].(string) - return kind -} - -func schemaConditionFacts(t *testing.T, condition map[string]any) map[string]any { - t.Helper() - return objectField(t, - objectField(t, objectField(t, condition, "then"), "properties"), "facts") -} - -func TestGateSettlementSemanticsMatchSchemaAndBrowser(t *testing.T) { - root := decodeJSONObject(t, readFile(t, "trace-schema.json"), "schema root") - gate := objectField(t, objectField(t, root, "$defs"), "gate") - gateConditions := arrayField(t, gate, "allOf") - if len(gateConditions) != 2 { - t.Fatalf("gate settlement condition count = %d, want 2", len(gateConditions)) - } - gateJSON := fmt.Sprintf("%v", gateConditions) - for _, required := range []string{"pass", "fail", "unknown", "minItems:1", "maxItems:0"} { - if !strings.Contains(gateJSON, required) { - t.Fatalf("gate schema is missing settlement constraint %q", required) - } - } - result := schemaRecordVariant(t, arrayField(t, root, "oneOf"), "result") - resultConditions := arrayField(t, result, "allOf") - if len(resultConditions) != 2 { - t.Fatalf("result settlement condition count = %d, want 2", len(resultConditions)) - } - resultJSON := fmt.Sprintf("%v", resultConditions) - for _, required := range []string{ - "passed", "pass", "not_applicable", "contains", "minContains:1", "failed", "fail", - } { - if !strings.Contains(resultJSON, required) { - t.Fatalf("result schema is missing settlement constraint %q", required) - } - } - +func TestGateSettlementSemanticsMatchBrowser(t *testing.T) { html := string(readFile(t, "index.html")) for _, required := range []string{ `gateIDs.has(gate.id)`, @@ -267,34 +91,19 @@ func TestGateSettlementSemanticsMatchSchemaAndBrowser(t *testing.T) { } } -func TestRuntimeIntentDenialVocabularyMatchesBrowserAndSchema(t *testing.T) { +func TestRuntimeIntentDenialVocabularyMatchesBrowser(t *testing.T) { html := string(readFile(t, "index.html")) - root := decodeJSONObject(t, readFile(t, "trace-schema.json"), "schema root") - factVariant := schemaRecordVariant(t, arrayField(t, root, "oneOf"), "fact") - outer := arrayField(t, factVariant, "allOf") - combined, ok := outer[0].(map[string]any) - if !ok { - t.Fatal("combined fact constraint is not an object") - } - conditions := arrayField(t, combined, "allOf") - var schemaCodes []string - for _, value := range conditions { - condition, ok := value.(map[string]any) - if !ok { - t.Fatal("display-evidence condition is not an object") - } - ifProperties := objectField(t, objectField(t, condition, "if"), "properties") - if objectField(t, ifProperties, "kind")["const"] != "runtime.intent.denied" { - continue + assertSameStrings(t, "browser Intent denial codes", + javascriptStringArray(t, html, "const INTENT_DENIAL_CODES"), intentDenialCodes) +} + +func jsonFields(value reflect.Type) []string { + fields := make([]string, 0, value.NumField()) + for index := 0; index < value.NumField(); index++ { + name, _, _ := strings.Cut(value.Field(index).Tag.Get("json"), ",") + if name != "" && name != "-" { + fields = append(fields, name) } - thenProperties := objectField(t, objectField(t, condition, "then"), "properties") - facts := objectField(t, thenProperties, "facts") - fields := objectField(t, facts, "properties") - schemaCodes = stringArrayField(t, objectField(t, fields, "code"), "enum") - } - if len(schemaCodes) == 0 { - t.Fatal("runtime.intent.denied schema has no closed code vocabulary") } - assertSameStrings(t, "browser Intent denial codes", - javascriptStringArray(t, html, "const INTENT_DENIAL_CODES"), schemaCodes) + return fields } diff --git a/harness/test/observer/observer_test.go b/harness/test/observer/observer_test.go index fcd434ad..72eb0d56 100644 --- a/harness/test/observer/observer_test.go +++ b/harness/test/observer/observer_test.go @@ -9,7 +9,6 @@ import ( "io" "os" "path/filepath" - "reflect" "slices" "strings" "testing" @@ -207,64 +206,6 @@ func TestObserverFilePickerAcceptsDocumentedTraceExtension(t *testing.T) { } } -func TestTraceSchemaMatchesGoFactDefinitions(t *testing.T) { - root := decodeJSONObject(t, readFile(t, "trace-schema.json"), "schema root") - definitions := objectField(t, root, "$defs") - factsDefinition := objectField(t, definitions, "facts") - schemaFields := objectKeys(objectField(t, factsDefinition, "properties")) - goFields := jsonFieldNames(reflect.TypeOf(factsWire{})) - assertSameStrings(t, "facts properties", schemaFields, goFields) - sourceDefinition := objectField(t, definitions, "source") - sourceProperties := objectField(t, sourceDefinition, "properties") - assertSameStrings(t, "source class enum", stringArrayField(t, objectField(t, sourceProperties, "class"), "enum"), sourceClasses) - - factVariant := schemaRecordVariant(t, arrayField(t, root, "oneOf"), "fact") - factProperties := objectField(t, factVariant, "properties") - kindDefinition := objectField(t, factProperties, "kind") - schemaKinds := stringArrayField(t, kindDefinition, "enum") - assertSameStrings(t, "fact kind enum", schemaKinds, knownFactKinds()) - truthDefinition := objectField(t, factProperties, "truth") - assertSameStrings(t, "truth class enum", stringArrayField(t, truthDefinition, "enum"), truthClasses) -} - -func TestBrowserValidatorMatchesSchemaAndGoDefinitions(t *testing.T) { - html := string(readFile(t, "index.html")) - root := decodeJSONObject(t, readFile(t, "trace-schema.json"), "schema root") - definitions := objectField(t, root, "$defs") - schemaFields := objectKeys(objectField(t, objectField(t, definitions, "facts"), "properties")) - factVariant := schemaRecordVariant(t, arrayField(t, root, "oneOf"), "fact") - factProperties := objectField(t, factVariant, "properties") - schemaKinds := stringArrayField(t, objectField(t, factProperties, "kind"), "enum") - - assertSameStrings(t, "browser facts properties", javascriptStringArray(t, html, "const FACT_FIELDS"), schemaFields) - assertSameStrings(t, "browser fact kinds", sortedKindsFromRows( - javascriptStringArray(t, html, "const FACT_CLASSIFICATION_ROWS")), schemaKinds) - assertSameStrings(t, "browser source classes", javascriptStringArray(t, html, "const SOURCE_CLASSES"), sourceClasses) - assertSameStrings(t, "browser truth classes", javascriptStringArray(t, html, "const TRUTH_CLASSES"), truthClasses) -} - -func TestTraceSchemaIsClosedAndMetadataOnly(t *testing.T) { - raw := readFile(t, "trace-schema.json") - if !json.Valid(raw) { - t.Fatal("trace-schema.json is not valid JSON") - } - var root map[string]any - if err := json.Unmarshal(raw, &root); err != nil { - t.Fatal(err) - } - oneOf, ok := root["oneOf"].([]any) - if !ok || len(oneOf) != 3 { - t.Fatalf("schema variants = %#v, want run/fact/result", root["oneOf"]) - } - assertClosedObjects(t, root, "root") - assertNoDangerousKeys(t, root) - for _, required := range []string{"mnemon.test.trace", "accepted_local_fact", "local_preference", "additionalProperties"} { - if !bytes.Contains(raw, []byte(required)) { - t.Fatalf("trace schema is missing %q", required) - } - } -} - func TestObserverFixturesAreStrictRedactedRenderInputs(t *testing.T) { paths, err := filepath.Glob("fixtures/*.trace") if err != nil || len(paths) != 2 { @@ -627,83 +568,6 @@ func readFile(t *testing.T, path string) []byte { return raw } -func decodeJSONObject(t *testing.T, raw []byte, label string) map[string]any { - t.Helper() - var value map[string]any - if err := json.Unmarshal(raw, &value); err != nil { - t.Fatalf("%s: %v", label, err) - } - return value -} - -func objectField(t *testing.T, object map[string]any, name string) map[string]any { - t.Helper() - value, ok := object[name].(map[string]any) - if !ok { - t.Fatalf("schema field %q is not an object", name) - } - return value -} - -func arrayField(t *testing.T, object map[string]any, name string) []any { - t.Helper() - value, ok := object[name].([]any) - if !ok { - t.Fatalf("schema field %q is not an array", name) - } - return value -} - -func schemaRecordVariant(t *testing.T, variants []any, record string) map[string]any { - t.Helper() - for _, value := range variants { - variant, ok := value.(map[string]any) - if !ok { - t.Fatal("schema variant is not an object") - } - properties := objectField(t, variant, "properties") - recordDefinition := objectField(t, properties, "record") - if constant, _ := recordDefinition["const"].(string); constant == record { - return variant - } - } - t.Fatalf("schema has no %q record variant", record) - return nil -} - -func stringArrayField(t *testing.T, object map[string]any, name string) []string { - t.Helper() - values := arrayField(t, object, name) - result := make([]string, 0, len(values)) - for _, value := range values { - text, ok := value.(string) - if !ok { - t.Fatalf("schema field %q contains a non-string value", name) - } - result = append(result, text) - } - return result -} - -func objectKeys(object map[string]any) []string { - keys := make([]string, 0, len(object)) - for key := range object { - keys = append(keys, key) - } - return keys -} - -func jsonFieldNames(typ reflect.Type) []string { - fields := make([]string, 0, typ.NumField()) - for index := 0; index < typ.NumField(); index++ { - name, _, _ := strings.Cut(typ.Field(index).Tag.Get("json"), ",") - if name != "" && name != "-" { - fields = append(fields, name) - } - } - return fields -} - func javascriptStringArray(t *testing.T, source, marker string) []string { t.Helper() markerIndex := strings.Index(source, marker) @@ -778,23 +642,3 @@ func assertNoDangerousKeys(t *testing.T, value any) { } } } - -func assertClosedObjects(t *testing.T, value any, path string) { - t.Helper() - switch typed := value.(type) { - case map[string]any: - if _, hasProperties := typed["properties"]; hasProperties && typed["type"] == "object" { - closed, present := typed["additionalProperties"] - if !present || closed != false { - t.Fatalf("schema object %s is not closed", path) - } - } - for key, child := range typed { - assertClosedObjects(t, child, path+"/"+key) - } - case []any: - for index, child := range typed { - assertClosedObjects(t, child, fmt.Sprintf("%s/%d", path, index)) - } - } -} diff --git a/harness/test/observer/trace-schema.json b/harness/test/observer/trace-schema.json deleted file mode 100644 index eb1f9541..00000000 --- a/harness/test/observer/trace-schema.json +++ /dev/null @@ -1,1610 +0,0 @@ -{ - "$defs": { - "digest": { - "pattern": "^sha256:[0-9a-f]{64}$", - "type": "string" - }, - "facts": { - "additionalProperties": false, - "dependentRequired": { - "goal_digest": [ - "goal_satisfied" - ], - "goal_satisfied": [ - "goal_digest" - ] - }, - "properties": { - "action": { - "enum": [ - "current", - "submit", - "capture", - "read", - "probe", - "mutation", - "other" - ] - }, - "alpha": { - "maximum": 64, - "minimum": 1, - "type": "integer" - }, - "artifact_count": { - "maximum": 64, - "minimum": 0, - "type": "integer" - }, - "attempt_count": { - "maximum": 256, - "minimum": 0, - "type": "integer" - }, - "batched_unattributed_count": { - "maximum": 256, - "minimum": 0, - "type": "integer" - }, - "authenticated": { - "type": "boolean" - }, - "bypassed_hook": { - "type": "boolean" - }, - "byte_size": { - "maximum": 16777216, - "minimum": 0, - "type": "integer" - }, - "code": { - "$ref": "#/$defs/token" - }, - "count": { - "maximum": 256, - "minimum": 1, - "type": "integer" - }, - "consequence": { - "enum": [ - "handling.create", - "handling.advance", - "handling.resolve.completed", - "handling.resolve.declined", - "handling.resolve.unresolved", - "reference.publish", - "reference.supersede", - "reference.retract", - "observation.completed", - "observation.declined", - "observation.unresolved" - ] - }, - "duration_ms": { - "maximum": 3600000, - "minimum": 0, - "type": "integer" - }, - "episode": { - "$ref": "#/$defs/token" - }, - "gate_id": { - "$ref": "#/$defs/token" - }, - "goal_digest": { - "$ref": "#/$defs/digest" - }, - "goal_satisfied": { - "type": "boolean" - }, - "has_current": { - "type": "boolean" - }, - "hook_cue": { - "type": "boolean" - }, - "invalid_votes": { - "maximum": 128, - "minimum": 0, - "type": "integer" - }, - "invalid_result_count": { - "maximum": 256, - "minimum": 0, - "type": "integer" - }, - "margin_after": { - "maximum": 1024, - "minimum": -1024, - "type": "integer" - }, - "margin_before": { - "maximum": 1024, - "minimum": -1024, - "type": "integer" - }, - "no_vote": { - "type": "boolean" - }, - "no_votes": { - "maximum": 64, - "minimum": 0, - "type": "integer" - }, - "occupied_claims": { - "maximum": 64, - "minimum": 0, - "type": "integer" - }, - "open_total": { - "maximum": 64, - "minimum": 0, - "type": "integer" - }, - "open_unclaimed": { - "maximum": 64, - "minimum": 0, - "type": "integer" - }, - "outcome": { - "enum": [ - "accepted", - "rejected", - "replayed", - "completed", - "declined", - "unresolved" - ] - }, - "payload_bytes": { - "maximum": 32768, - "minimum": 0, - "type": "integer" - }, - "phase": { - "enum": [ - "awaiting_seed", - "active", - "observed" - ] - }, - "preference_after": { - "enum": [ - "A", - "B" - ] - }, - "preference_before": { - "enum": [ - "A", - "B" - ] - }, - "recolored": { - "type": "boolean" - }, - "replayed": { - "type": "boolean" - }, - "reply_required": { - "type": "boolean" - }, - "related_projected": { - "maximum": 1, - "minimum": 0, - "type": "integer" - }, - "related_total": { - "maximum": 128, - "minimum": 0, - "type": "integer" - }, - "result": { - "enum": [ - "threshold_reached", - "inconclusive" - ] - }, - "round": { - "maximum": 1024, - "minimum": 0, - "type": "integer" - }, - "role": { - "$ref": "#/$defs/token" - }, - "sample_size": { - "maximum": 64, - "minimum": 0, - "type": "integer" - }, - "semantic_kind": { - "$ref": "#/$defs/token" - }, - "state": { - "enum": [ - "open", - "active", - "pending", - "settled", - "expired", - "retracted", - "terminal" - ] - }, - "status": { - "enum": [ - "pass", - "fail", - "incomplete", - "unknown", - "not_applicable" - ] - }, - "success_count": { - "maximum": 256, - "minimum": 0, - "type": "integer" - }, - "target_count": { - "maximum": 16, - "minimum": 0, - "type": "integer" - }, - "tool_error_count": { - "maximum": 256, - "minimum": 0, - "type": "integer" - }, - "targets": { - "items": { - "$ref": "#/$defs/token" - }, - "maxItems": 16, - "type": "array" - }, - "timed_out": { - "type": "boolean" - }, - "truncated": { - "type": "boolean" - }, - "turn_limit": { - "maximum": 256, - "minimum": 1, - "type": "integer" - }, - "turns_used": { - "maximum": 256, - "minimum": 0, - "type": "integer" - }, - "view_nonempty": { - "type": "boolean" - }, - "votes_a": { - "maximum": 64, - "minimum": 0, - "type": "integer" - }, - "votes_b": { - "maximum": 64, - "minimum": 0, - "type": "integer" - } - }, - "type": "object" - }, - "gate": { - "additionalProperties": false, - "allOf": [ - { - "if": { - "properties": { - "status": { - "enum": [ - "pass", - "fail" - ] - } - }, - "required": [ - "status" - ] - }, - "then": { - "properties": { - "evidence": { - "minItems": 1 - } - } - } - }, - { - "if": { - "properties": { - "status": { - "const": "unknown" - } - }, - "required": [ - "status" - ] - }, - "then": { - "properties": { - "evidence": { - "maxItems": 0 - } - } - } - } - ], - "properties": { - "evidence": { - "items": { - "$ref": "#/$defs/traceID" - }, - "maxItems": 32, - "type": "array", - "uniqueItems": true - }, - "id": { - "$ref": "#/$defs/token" - }, - "status": { - "enum": [ - "pass", - "fail", - "unknown", - "not_applicable" - ] - } - }, - "required": [ - "id", - "status", - "evidence" - ], - "type": "object" - }, - "participant": { - "additionalProperties": false, - "properties": { - "agent": { - "$ref": "#/$defs/token" - }, - "model": { - "$ref": "#/$defs/token" - }, - "node": { - "$ref": "#/$defs/token" - }, - "runtime": { - "$ref": "#/$defs/token" - } - }, - "required": [ - "node" - ], - "type": "object" - }, - "refs": { - "additionalProperties": false, - "properties": { - "artifact": { - "$ref": "#/$defs/digest" - }, - "correlation": { - "$ref": "#/$defs/token" - }, - "delivery": { - "$ref": "#/$defs/token" - }, - "event": { - "$ref": "#/$defs/token" - }, - "event_digest": { - "$ref": "#/$defs/digest" - }, - "handling": { - "$ref": "#/$defs/token" - }, - "principal": { - "$ref": "#/$defs/token" - }, - "reference_head": { - "$ref": "#/$defs/token" - }, - "selection": { - "$ref": "#/$defs/digest" - } - }, - "type": "object" - }, - "source": { - "additionalProperties": false, - "properties": { - "class": { - "enum": [ - "runtime", - "r7_authority", - "transport", - "r8_selector", - "oracle", - "runner" - ] - }, - "node": { - "$ref": "#/$defs/token" - } - }, - "required": [ - "class", - "node" - ], - "type": "object" - }, - "timestamp": { - "format": "date-time", - "maxLength": 35, - "type": "string" - }, - "token": { - "maxLength": 128, - "minLength": 1, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]*$", - "type": "string" - }, - "traceID": { - "pattern": "^trace:[A-Za-z0-9][A-Za-z0-9._:-]{0,121}$", - "type": "string" - } - }, - "oneOf": [ - { - "additionalProperties": false, - "properties": { - "candidate_digest": { - "$ref": "#/$defs/digest" - }, - "participants": { - "items": { - "$ref": "#/$defs/participant" - }, - "maxItems": 32, - "type": "array" - }, - "record": { - "const": "run" - }, - "redaction": { - "const": "metadata" - }, - "run_id": { - "$ref": "#/$defs/token" - }, - "scenario": { - "additionalProperties": false, - "properties": { - "digest": { - "$ref": "#/$defs/digest" - }, - "id": { - "$ref": "#/$defs/token" - } - }, - "required": [ - "id", - "digest" - ], - "type": "object" - }, - "schema": { - "const": "mnemon.test.trace" - }, - "started_at": { - "$ref": "#/$defs/timestamp" - }, - "version": { - "const": 2 - } - }, - "required": [ - "schema", - "version", - "record", - "run_id", - "scenario", - "redaction", - "started_at", - "participants" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "agent": { - "$ref": "#/$defs/token" - }, - "captured_at": { - "$ref": "#/$defs/timestamp" - }, - "causes": { - "items": { - "$ref": "#/$defs/traceID" - }, - "maxItems": 16, - "type": "array", - "uniqueItems": true - }, - "facts": { - "$ref": "#/$defs/facts" - }, - "id": { - "$ref": "#/$defs/traceID" - }, - "kind": { - "enum": [ - "runtime.turn.started", - "runtime.hook.cue", - "runtime.delegate.invoked", - "runtime.domain.operation", - "runtime.view.received", - "runtime.intent.denied", - "runtime.intent.submitted", - "runtime.turn.ended", - "runtime.turn.timed_out", - "system.node.restarted", - "r7.receipt.accepted", - "r7.receipt.rejected", - "r7.receipt.replayed", - "r7.event.accepted", - "r7.handling.created", - "r7.handling.advanced", - "r7.handling.resolved", - "r7.reference.published", - "r7.reference.superseded", - "r7.reference.retracted", - "r7.delivery.pending", - "r7.delivery.readmitted", - "r7.delivery.settled", - "r7.delivery.expired", - "r7.artifact.captured", - "r7.artifact.read", - "r7.artifact.verified", - "r8.selection.seeded", - "r8.round.frozen", - "r8.vote.observed", - "r8.round.settled", - "r8.observation.produced", - "test.attention.wave", - "test.attention.outcome", - "test.attention.exhausted", - "test.attention.quiescent", - "test.attention.occupied", - "test.gate.checked" - ] - }, - "record": { - "const": "fact" - }, - "refs": { - "$ref": "#/$defs/refs" - }, - "schema": { - "const": "mnemon.test.trace" - }, - "seq": { - "maximum": 100000, - "minimum": 1, - "type": "integer" - }, - "source": { - "$ref": "#/$defs/source" - }, - "truth": { - "enum": [ - "observation", - "accepted_local_fact", - "derived_projection", - "local_preference", - "assertion" - ] - }, - "turn": { - "$ref": "#/$defs/token" - }, - "version": { - "const": 2 - } - }, - "required": [ - "schema", - "version", - "record", - "seq", - "id", - "captured_at", - "source", - "kind", - "truth", - "causes", - "refs", - "facts" - ], - "type": "object", - "allOf": [ - { - "oneOf": [ - { - "properties": { - "kind": { - "enum": [ - "runtime.turn.started", - "runtime.hook.cue", - "runtime.delegate.invoked", - "runtime.domain.operation", - "runtime.intent.denied", - "runtime.intent.submitted", - "runtime.turn.ended", - "runtime.turn.timed_out", - "r7.artifact.read" - ] - }, - "source": { - "properties": { - "class": { - "const": "runtime" - } - }, - "required": [ - "class" - ] - }, - "truth": { - "const": "observation" - } - }, - "required": [ - "kind", - "source", - "truth" - ] - }, - { - "properties": { - "kind": { - "enum": [ - "runtime.view.received" - ] - }, - "source": { - "properties": { - "class": { - "const": "runtime" - } - }, - "required": [ - "class" - ] - }, - "truth": { - "const": "derived_projection" - } - }, - "required": [ - "kind", - "source", - "truth" - ] - }, - { - "properties": { - "kind": { - "enum": [ - "system.node.restarted" - ] - }, - "source": { - "properties": { - "class": { - "const": "runner" - } - }, - "required": [ - "class" - ] - }, - "truth": { - "const": "observation" - } - }, - "required": [ - "kind", - "source", - "truth" - ] - }, - { - "properties": { - "kind": { - "enum": [ - "r7.receipt.accepted", - "r7.receipt.rejected", - "r7.receipt.replayed", - "r7.event.accepted", - "r7.handling.created", - "r7.handling.advanced", - "r7.handling.resolved", - "r7.reference.published", - "r7.reference.superseded", - "r7.reference.retracted", - "r7.delivery.pending", - "r7.delivery.settled", - "r7.delivery.expired", - "r7.artifact.captured", - "r7.artifact.verified" - ] - }, - "source": { - "properties": { - "class": { - "const": "r7_authority" - } - }, - "required": [ - "class" - ] - }, - "truth": { - "const": "accepted_local_fact" - } - }, - "required": [ - "kind", - "source", - "truth" - ] - }, - { - "properties": { - "kind": { - "enum": [ - "r7.delivery.readmitted" - ] - }, - "source": { - "properties": { - "class": { - "const": "r7_authority" - } - }, - "required": [ - "class" - ] - }, - "truth": { - "const": "accepted_local_fact" - } - }, - "required": [ - "kind", - "source", - "truth" - ] - }, - { - "properties": { - "kind": { - "enum": [ - "r8.selection.seeded", - "r8.round.frozen", - "r8.round.settled", - "r8.observation.produced" - ] - }, - "refs": { - "required": [ - "selection" - ] - }, - "source": { - "properties": { - "class": { - "const": "r8_selector" - } - }, - "required": [ - "class" - ] - }, - "truth": { - "const": "local_preference" - } - }, - "required": [ - "kind", - "refs", - "source", - "truth" - ] - }, - { - "properties": { - "kind": { - "enum": [ - "r8.vote.observed" - ] - }, - "refs": { - "required": [ - "selection" - ] - }, - "source": { - "properties": { - "class": { - "const": "r8_selector" - } - }, - "required": [ - "class" - ] - }, - "truth": { - "const": "observation" - } - }, - "required": [ - "kind", - "refs", - "source", - "truth" - ] - }, - { - "properties": { - "kind": { - "enum": [ - "test.attention.wave", - "test.attention.outcome", - "test.attention.exhausted", - "test.attention.quiescent", - "test.attention.occupied", - "test.gate.checked" - ] - }, - "source": { - "properties": { - "class": { - "const": "oracle" - } - }, - "required": [ - "class" - ] - }, - "truth": { - "const": "assertion" - } - }, - "required": [ - "kind", - "source", - "truth" - ] - } - ], - "allOf": [ - { - "if": { - "properties": { - "kind": { - "const": "runtime.domain.operation" - } - }, - "required": [ - "kind" - ] - }, - "then": { - "properties": { - "causes": { - "maxItems": 0 - }, - "facts": { - "properties": { - "action": { - "enum": [ - "read", - "probe", - "mutation" - ] - }, - "attempt_count": { - "minimum": 1 - } - }, - "required": [ - "action", - "attempt_count", - "batched_unattributed_count", - "invalid_result_count", - "success_count", - "tool_error_count" - ] - } - } - } - }, - { - "if": { - "properties": { - "kind": { - "const": "runtime.view.received" - } - }, - "required": [ - "kind" - ] - }, - "then": { - "properties": { - "facts": { - "allOf": [ - { - "if": { - "properties": { - "has_current": { - "const": true - } - }, - "required": [ - "has_current" - ] - }, - "then": { - "required": [ - "reply_required" - ] - } - }, - { - "if": { - "properties": { - "has_current": { - "const": false - } - }, - "required": [ - "has_current" - ] - }, - "then": { - "not": { - "required": [ - "reply_required" - ] - } - } - } - ], - "properties": { - "action": { - "const": "current" - } - }, - "required": [ - "action", - "has_current", - "open_total", - "related_projected", - "related_total", - "truncated" - ] - } - } - } - }, - { - "if": { - "properties": { - "kind": { - "const": "runtime.intent.denied" - } - }, - "required": [ - "kind" - ] - }, - "then": { - "properties": { - "causes": { - "maxItems": 0 - }, - "facts": { - "properties": { - "action": { - "const": "submit" - }, - "code": { - "enum": [ - "invalid_argument", - "content_required", - "content_too_large", - "artifact_invalid", - "artifact_too_large", - "authentication_failed", - "context_required", - "context_stale", - "asset_revision_mismatch", - "action_not_allowed", - "operation_mismatch", - "operation_pending", - "mnemond_unavailable", - "internal" - ] - } - }, - "required": [ - "action", - "code", - "count" - ] - } - } - } - }, - { - "if": { - "properties": { - "kind": { - "const": "r7.event.accepted" - } - }, - "required": [ - "kind" - ] - }, - "then": { - "properties": { - "facts": { - "required": [ - "semantic_kind", - "consequence" - ] - }, - "refs": { - "required": [ - "event", - "event_digest" - ] - } - } - } - }, - { - "if": { - "properties": { - "kind": { - "const": "r7.handling.resolved" - } - }, - "required": [ - "kind" - ] - }, - "then": { - "properties": { - "facts": { - "properties": { - "outcome": { - "enum": [ - "completed", - "declined", - "unresolved" - ] - }, - "state": { - "const": "terminal" - } - }, - "required": [ - "outcome", - "state" - ] - }, - "refs": { - "required": [ - "handling" - ] - } - } - } - }, - { - "if": { - "properties": { - "kind": { - "const": "r8.selection.seeded" - } - }, - "required": [ - "kind" - ] - }, - "then": { - "properties": { - "facts": { - "required": [ - "preference_after", - "phase" - ] - } - } - } - }, - { - "if": { - "properties": { - "kind": { - "const": "r8.round.frozen" - } - }, - "required": [ - "kind" - ] - }, - "then": { - "properties": { - "facts": { - "required": [ - "round", - "sample_size", - "alpha", - "preference_before", - "margin_before" - ] - } - } - } - }, - { - "if": { - "properties": { - "kind": { - "const": "r8.vote.observed" - } - }, - "required": [ - "kind" - ] - }, - "then": { - "properties": { - "facts": { - "required": [ - "round", - "votes_a", - "votes_b", - "authenticated" - ] - } - } - } - }, - { - "if": { - "properties": { - "kind": { - "const": "r8.round.settled" - } - }, - "required": [ - "kind" - ] - }, - "then": { - "properties": { - "facts": { - "required": [ - "round", - "preference_before", - "preference_after", - "margin_before", - "margin_after", - "recolored", - "phase" - ] - } - } - } - }, - { - "if": { - "properties": { - "kind": { - "const": "r8.observation.produced" - } - }, - "required": [ - "kind" - ] - }, - "then": { - "properties": { - "facts": { - "required": [ - "round", - "result", - "preference_after", - "margin_after", - "phase" - ] - } - } - } - }, - { - "if": { - "properties": { - "kind": { - "const": "test.gate.checked" - } - }, - "required": [ - "kind" - ] - }, - "then": { - "properties": { - "facts": { - "properties": { - "status": { - "enum": [ - "pass", - "fail", - "unknown", - "not_applicable" - ] - } - }, - "required": [ - "gate_id", - "status" - ] - } - } - } - }, - { - "if": { - "properties": { - "kind": { - "const": "test.attention.wave" - } - }, - "required": [ - "kind" - ] - }, - "then": { - "properties": { - "causes": { - "maxItems": 0 - }, - "facts": { - "properties": { - "occupied_claims": { - "const": 0 - } - }, - "required": [ - "episode", - "role", - "round", - "open_unclaimed", - "occupied_claims", - "turn_limit", - "turns_used" - ] - } - } - } - }, - { - "if": { - "properties": { - "kind": { - "const": "test.attention.outcome" - } - }, - "required": [ - "kind" - ] - }, - "then": { - "properties": { - "causes": { - "maxItems": 0 - }, - "facts": { - "properties": { - "goal_satisfied": { - "const": true - }, - "occupied_claims": { - "const": 0 - } - }, - "required": [ - "episode", - "role", - "round", - "open_unclaimed", - "occupied_claims", - "turn_limit", - "turns_used", - "goal_digest", - "goal_satisfied" - ] - } - } - } - }, - { - "if": { - "properties": { - "kind": { - "const": "test.attention.quiescent" - } - }, - "required": [ - "kind" - ] - }, - "then": { - "properties": { - "causes": { - "maxItems": 0 - }, - "facts": { - "properties": { - "goal_satisfied": { - "const": false - }, - "occupied_claims": { - "const": 0 - }, - "open_unclaimed": { - "const": 0 - } - }, - "required": [ - "episode", - "role", - "round", - "open_unclaimed", - "occupied_claims", - "turn_limit", - "turns_used", - "goal_digest", - "goal_satisfied" - ] - } - } - } - }, - { - "if": { - "properties": { - "kind": { - "const": "test.attention.exhausted" - } - }, - "required": [ - "kind" - ] - }, - "then": { - "properties": { - "causes": { - "maxItems": 0 - }, - "facts": { - "properties": { - "goal_satisfied": { - "const": false - }, - "occupied_claims": { - "const": 0 - } - }, - "required": [ - "episode", - "role", - "round", - "open_unclaimed", - "occupied_claims", - "turn_limit", - "turns_used", - "goal_digest", - "goal_satisfied" - ] - } - } - } - }, - { - "if": { - "properties": { - "kind": { - "const": "test.attention.occupied" - } - }, - "required": [ - "kind" - ] - }, - "then": { - "properties": { - "causes": { - "maxItems": 0 - }, - "facts": { - "not": { - "anyOf": [ - { - "required": [ - "goal_digest" - ] - }, - { - "required": [ - "goal_satisfied" - ] - } - ] - }, - "required": [ - "episode", - "role", - "round", - "open_unclaimed", - "occupied_claims", - "turn_limit", - "turns_used" - ] - } - } - } - } - ] - } - ] - }, - { - "additionalProperties": false, - "allOf": [ - { - "if": { - "properties": { - "status": { - "const": "passed" - } - }, - "required": [ - "status" - ] - }, - "then": { - "properties": { - "gates": { - "contains": { - "properties": { - "status": { - "const": "pass" - } - }, - "required": [ - "status" - ] - }, - "items": { - "properties": { - "status": { - "enum": [ - "pass", - "not_applicable" - ] - } - } - }, - "minContains": 1 - } - } - } - }, - { - "if": { - "properties": { - "status": { - "const": "failed" - } - }, - "required": [ - "status" - ] - }, - "then": { - "properties": { - "gates": { - "contains": { - "properties": { - "status": { - "const": "fail" - } - }, - "required": [ - "status" - ] - }, - "minContains": 1 - } - } - } - } - ], - "properties": { - "finished_at": { - "$ref": "#/$defs/timestamp" - }, - "gates": { - "items": { - "$ref": "#/$defs/gate" - }, - "maxItems": 64, - "type": "array" - }, - "record": { - "const": "result" - }, - "record_count": { - "maximum": 100000, - "minimum": 0, - "type": "integer" - }, - "schema": { - "const": "mnemon.test.trace" - }, - "status": { - "enum": [ - "passed", - "failed", - "incomplete" - ] - }, - "trace_digest": { - "$ref": "#/$defs/digest" - }, - "version": { - "const": 2 - } - }, - "required": [ - "schema", - "version", - "record", - "status", - "finished_at", - "record_count", - "trace_digest", - "gates" - ], - "type": "object" - } - ], - "title": "Mnemon sanitized test trace record" -} diff --git a/harness/test/observer/trace_writer_validate.go b/harness/test/observer/trace_writer_validate.go index ea82bba7..ae10beb6 100644 --- a/harness/test/observer/trace_writer_validate.go +++ b/harness/test/observer/trace_writer_validate.go @@ -16,11 +16,17 @@ const ( ) var ( - tokenPattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$`) - digestPattern = regexp.MustCompile(`^sha256:[0-9a-f]{64}$`) - tracePattern = regexp.MustCompile(`^trace:[A-Za-z0-9][A-Za-z0-9._:-]{0,121}$`) - sourceClasses = []string{"runtime", "r7_authority", "transport", "r8_selector", "oracle", "runner"} - truthClasses = []string{"observation", "accepted_local_fact", "derived_projection", "local_preference", "assertion"} + tokenPattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$`) + digestPattern = regexp.MustCompile(`^sha256:[0-9a-f]{64}$`) + tracePattern = regexp.MustCompile(`^trace:[A-Za-z0-9][A-Za-z0-9._:-]{0,121}$`) + sourceClasses = []string{"runtime", "r7_authority", "transport", "r8_selector", "oracle", "runner"} + truthClasses = []string{"observation", "accepted_local_fact", "derived_projection", "local_preference", "assertion"} + intentDenialCodes = []string{ + "invalid_argument", "content_required", "content_too_large", "artifact_invalid", + "artifact_too_large", "authentication_failed", "context_required", "context_stale", + "asset_revision_mismatch", "action_not_allowed", "operation_mismatch", + "operation_pending", "mnemond_unavailable", "internal", + } ) func validateWriterRun(run Run) (string, error) { @@ -136,12 +142,7 @@ func operationCountSum(fields FactFields) int { func validIntentDenialEvidence(fact Fact) bool { return len(fact.Causes) == 0 && fact.Fields.Action == "submit" && fact.Fields.Count != nil && *fact.Fields.Count > 0 && - slices.Contains([]string{ - "invalid_argument", "content_required", "content_too_large", "artifact_invalid", - "artifact_too_large", "authentication_failed", "context_required", "context_stale", - "asset_revision_mismatch", "action_not_allowed", "operation_mismatch", - "operation_pending", "mnemond_unavailable", "internal", - }, fact.Fields.Code) + slices.Contains(intentDenialCodes, fact.Fields.Code) } func validateKindEvidence(fact Fact, sequence int) error { diff --git a/harness/test/r7/domainops/run_live_oracle.sh b/harness/test/r7/domainops/run_live_oracle.sh deleted file mode 100755 index 7f8d8b39..00000000 --- a/harness/test/r7/domainops/run_live_oracle.sh +++ /dev/null @@ -1,1563 +0,0 @@ -#!/usr/bin/env bash - -# Deterministic, zero-provider regression oracle for run_live.sh's Runtime -# boundary. It exercises the controlled role projection, stream filtering, -# terminal validation, pipeline error propagation, and complete local -# process-group timeout. - -set -euo pipefail -umask 077 - -oracle_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P) -# shellcheck source=run_live.sh -source "$oracle_dir/run_live.sh" - -test "$(grep -Fxc -- " \"$attention_exhausted_reason\";" \ - "$harness_root/internal/attach/assets/pi/mnemond.ts")" = 1 || { - printf 'runtime oracle: Host attention disposition drifted from the observer\n' >&2 - exit 1 -} -test "$(grep -Fxc -- "const CURRENT_FAILED_TEXT = \"$current_failed_reason\";" \ - "$harness_root/internal/attach/assets/pi/mnemond-current.ts")" = 1 || { - printf 'runtime oracle: native Current failure disposition drifted from the observer\n' >&2 - exit 1 -} -grep -Eq "^[[:space:]]*MonitorProbeLimit[[:space:]]*=[[:space:]]*$monitor_probe_limit$" \ - "$case_root/world/monitor.go" || { - printf 'runtime oracle: shell and monitor probe bounds diverged\n' >&2 - exit 1 -} -grep -Eq "^[[:space:]]*MonitorProbeChargeLimit[[:space:]]*=[[:space:]]*$monitor_probe_charge_limit$" \ - "$case_root/world/monitor.go" || { - printf 'runtime oracle: shell and monitor per-probe charge bounds diverged\n' >&2 - exit 1 -} -grep -Eq "^[[:space:]]*GatewayHistoryLimit[[:space:]]*=[[:space:]]*$gateway_history_limit$" \ - "$case_root/world/gateway.go" || { - printf 'runtime oracle: shell and gateway history bounds diverged\n' >&2 - exit 1 -} -grep -Fqx -- $'\tMaxRequestBodyBytes = '"$domain_request_max_kib"' << 10' \ - "$case_root/world/protocol.go" && - grep -Fqx -- $'\tMaxResponseBodyBytes = '"$domain_response_max_kib"' << 10' \ - "$case_root/world/protocol.go" && - grep -Fqx -- $'\tmaxActionRequestBytes = world.MaxRequestBodyBytes' \ - "$case_root/cmd/domainctl/main.go" && - grep -Fqx -- $'\tmaxControlResponseBytes = world.MaxResponseBodyBytes' \ - "$case_root/cmd/domainctl/main.go" || { - printf 'runtime oracle: world and domainctl body bounds diverged\n' >&2 - exit 1 -} -test "$synthetic_charge_limit" = $((monitor_probe_limit * monitor_probe_charge_limit)) || { - printf 'runtime oracle: synthetic charge envelope is not derived from probe bounds\n' >&2 - exit 1 -} -grep -Fqx -- $'\tmaxSyntheticProbes = world.MonitorProbeLimit' \ - "$harness_root/test/r7/domainops/trace/report_world.go" && - grep -Fqx -- $'\tmaxSyntheticChargesPerProbe = world.MonitorProbeChargeLimit' \ - "$harness_root/test/r7/domainops/trace/report_world.go" || { - printf 'runtime oracle: trace and world synthetic bounds diverged\n' >&2 - exit 1 -} -test "$scenario_customer_receipt_limit" = 32 && - test "$gateway_history_limit" -ge \ - $((monitor_probe_limit + scenario_customer_receipt_limit)) || { - printf 'runtime oracle: gateway history cannot retain scenario and probe receipts\n' >&2 - exit 1 -} - -write_trace_source=$(declare -f write_trace) -for required in '--consolidation-authority' '--boundary-authority'; do - printf '%s\n' "$write_trace_source" | grep -F -- "$required" >/dev/null || { - printf 'runtime oracle: trace adapter omits %s\n' "$required" >&2 - exit 1 - } -done -consolidation_source=$(declare -f capture_consolidation_start) -printf '%s\n' "$consolidation_source" | - grep -F -- 'chmod -R a-w "$staging"' >/dev/null || { - printf 'runtime oracle: consolidation does not freeze independent authority\n' >&2 - exit 1 -} -if printf '%s\n' "$consolidation_source" | - sed -n '/chmod -R a-w "$staging"/,$p' | - grep -F -- 'rm -rf' >/dev/null; then - printf 'runtime oracle: consolidation deletes its frozen authority\n' >&2 - exit 1 -fi -boundary_source=$(declare -f capture_evolution_boundary) -printf '%s\n' "$boundary_source" | - grep -F -- 'chmod -R a-w "$runtime_root/runtime-restart-state"' >/dev/null || { - printf 'runtime oracle: episode boundary is not frozen before restart\n' >&2 - exit 1 -} -restart_source=$(declare -f restart_agent_runtimes) -if printf '%s\n' "$restart_source" | - grep -E -- '(rm|mv|chmod|chown|docker cp)[^\n]*\$snapshot' >/dev/null; then - printf 'runtime oracle: restart mutates independent boundary authority\n' >&2 - exit 1 -fi -printf '%s\n' "$restart_source" | - grep -F -- 'cp -R "$snapshot/." "$restore"' >/dev/null && - printf '%s\n' "$restart_source" | - grep -F -- 'tar -C "$restore" -cf - . | docker exec -i "$container"' >/dev/null || { - printf 'runtime oracle: restart does not restore from a disposable unprivileged copy\n' >&2 - exit 1 -} - -scratch=$(mktemp -d /tmp/mnr7-runtime-oracle.XXXXXX) -cleanup_oracle() { - chmod -R u+w "$scratch" >/dev/null 2>&1 || true - rm -rf -- "$scratch" -} -trap cleanup_oracle EXIT - -projection_policy() { - local file=$1 - local protocol_pattern hidden_answer_pattern choreography_pattern - protocol_pattern='("(kind|consequence|successors|alias|subject_handling|correlation_handle|reply_target)"[[:space:]]*:|handling\.(create|advance|resolve)|reference\.(publish|supersede|retract)|review\.|contract-net\.|blackboard\.)' - hidden_answer_pattern='("route"[[:space:]]*:[[:space:]]*"east"[[:space:]]*}|--latency[[:space:]]+300ms|--timeout[[:space:]]+100ms|--stable-keys=false|incident-[ab0-9]|evaluation-[ab0-9]|stability-[ab0-9]|root[ -]?cause[[:space:]]+(is|=)|remediation[[:space:]]+(is|=)|fix[[:space:]]+by)' - choreography_pattern='(first[[:space:]]+(ask|contact|send)|then[[:space:]]+(ask|contact|send)|send[[:space:]].*[[:space:]]to[[:space:]]+(lead|edge|payment|platform|data))' - - ! grep -Ein -- "$protocol_pattern|$hidden_answer_pattern|$choreography_pattern" "$file" \ - >/dev/null -} - -assert_domain_projection_boundary() { - local pi_source role source projected mode - pi_source=$(declare -f pi_process) - if printf '%s\n' "$pi_source" | grep -F -- '--no-context-files' >/dev/null; then - printf 'runtime oracle: domainops Pi disables its role context projection\n' >&2 - exit 1 - fi - printf '%s\n' "$pi_source" | grep -F -- 'docker exec -w /workspace' >/dev/null || { - printf 'runtime oracle: domainops Pi does not start in the controlled workspace\n' >&2 - exit 1 - } - printf '%s\n' "$pi_source" | grep -F -- \ - '--extension /opt/mnemon/pi-delegate/delegate.ts' >/dev/null || { - printf 'runtime oracle: domainops Pi lacks the bounded delegate extension\n' >&2 - exit 1 - } - printf '%s\n' "$pi_source" | grep -F -- \ - '--tools bash,delegate,mnemond_current,mnemond_submit' >/dev/null || { - printf 'runtime oracle: domainops Pi does not expose the exact bounded exploration and settlement surface\n' >&2 - exit 1 - } - test "$(printf '%s\n' "$pi_source" | grep -Fc -- '--thinking "$thinking"')" = 1 && - test "$(printf '%s\n' "$pi_source" | - grep -Fc -- 'pi-turn-wrapper "$pid_file" "$pi_model" "$pi_thinking"')" = 1 || { - printf 'runtime oracle: Pi reasoning level is not passed through one closed argument\n' >&2 - exit 1 - } - - runtime_root="$scratch/projection-runtime" - mkdir -p "$runtime_root/workspaces" - for role in $roles; do - source="$case_root/domains/$role/AGENTS.md" - projection_policy "$source" || { - printf 'runtime oracle: %s role projection contains a task answer or Event choreography\n' \ - "$role" >&2 - exit 1 - } - prepare_workspace "$role" - projected="$runtime_root/workspaces/$role/AGENTS.md" - test -f "$projected" && test ! -L "$projected" && cmp -s "$source" "$projected" || { - printf 'runtime oracle: %s role projection is not the exact controlled input\n' "$role" >&2 - exit 1 - } - if mode=$(stat -c '%a' "$projected" 2>/dev/null); then :; else - mode=$(stat -f '%Lp' "$projected") - fi - test "$mode" = 444 || { - printf 'runtime oracle: %s role projection mode = %s, want 444\n' "$role" "$mode" >&2 - exit 1 - } - done - test "$(find "$runtime_root/workspaces" -type f -print | wc -l | tr -d '[:space:]')" = 5 || { - printf 'runtime oracle: controlled workspaces contain an unexpected projected file\n' >&2 - exit 1 - } - projection_policy "$mission_file" || { - printf 'runtime oracle: mission contains a task answer or Event choreography\n' >&2 - exit 1 - } - printf '%s\n' "$outcome_attention" >"$scratch/outcome-attention.md" - projection_policy "$scratch/outcome-attention.md" || { - printf 'runtime oracle: outcome attention contains a task answer or Event choreography\n' >&2 - exit 1 - } - - printf '%s\n' '{"kind":"repair.force","consequence":"handling.create","successors":[{"alias":"data"}]}' \ - >"$scratch/forbidden-projection.md" - if projection_policy "$scratch/forbidden-projection.md"; then - printf 'runtime oracle: projection policy accepted an Event choreography fixture\n' >&2 - exit 1 - fi - runtime_root= -} - -assert_domain_projection_boundary - -assert_generic_evolution_oracle() { - local role database reference_digest event_json - runtime_root="$scratch/evolution-runtime" - authority_captured=1 - mkdir -p "$runtime_root/authority" "$runtime_root/evolution-boundary" - reference_digest="sha256:$(printf 'a%.0s' {1..64})" - for role in $roles; do - mkdir -p "$runtime_root/authority/$role" - database="$runtime_root/authority/$role/agency.db" - sqlite3 "$database" 'CREATE TABLE events(origin_sequence INTEGER, canonical_json BLOB);' - jq -n --arg role "$role" \ - '{role:$role,consolidation_after_sequence:0,max_origin_sequence:0,active_heads:[]}' \ - >"$runtime_root/evolution-boundary/$role.json" - done - jq -n --arg digest "$reference_digest" ' - {role:"lead",consolidation_after_sequence:0,max_origin_sequence:1,active_heads:[ - {event_id:"event:fixture-reference",event_digest:$digest}]} - ' >"$runtime_root/evolution-boundary/lead.json" - event_json=$(jq -cn --arg digest "$reference_digest" ' - {machine:{event_id:"event:fixture-use"},evidence:{causation:[ - {id:"event:fixture-reference",digest:$digest}]}} - ') - sqlite3 "$runtime_root/authority/lead/agency.db" <&2 - exit 1 - } - - jq '.active_heads[0].event_digest = - "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"' \ - "$runtime_root/evolution-boundary/lead.json" \ - >"$runtime_root/evolution-boundary/lead-tampered.json" - mv "$runtime_root/evolution-boundary/lead-tampered.json" \ - "$runtime_root/evolution-boundary/lead.json" - assert_evolution - if test "$(cat "$runtime_root/evolution-effects.total")" != 0; then - printf 'runtime oracle: non-exact later Reference use was counted\n' >&2 - exit 1 - fi - runtime_root= - authority_captured=0 -} - -assert_generic_evolution_oracle - -assert_failure_world_boundary() { - local destination - runtime_root="$scratch/failure-world-runtime" - mkdir -p "$runtime_root" - cat >"$runtime_root/episode-1-incident-after.json" <<'JSON' -{"role":"data","result":{"charges":8,"active_charges":8,"voided_charges":0,"unique_businesses":4,"duplicate_businesses":4,"ignored":"not retained"}} -JSON - destination="$runtime_root/world.json" - collect_failure_world "$destination" - jq -e ' - . == [{episode:"episode-1",charges:8,active_charges:8,voided_charges:0, - unique_businesses:4,duplicate_businesses:4}] - ' "$destination" >/dev/null - if grep -F 'ignored' "$destination" >/dev/null; then - printf 'runtime oracle: bounded failure world retained an unapproved field\n' >&2 - exit 1 - fi - - cat >"$runtime_root/episode-2-incident-after.json" <<'JSON' -{"role":"data","result":{"charges":8,"active_charges":7,"voided_charges":0,"unique_businesses":4,"duplicate_businesses":1}} -JSON - if collect_failure_world "$runtime_root/invalid-world.json" >/dev/null 2>&1; then - printf 'runtime oracle: inconsistent failure world counts were accepted\n' >&2 - exit 1 - fi - runtime_root= -} - -assert_failure_world_boundary - -assert_exclusive_turn_window() { - runtime_root="$scratch/turn-window-runtime" - mkdir -p "$runtime_root/turn-locks" - claim_turn_window lead - if claim_turn_window lead >/dev/null 2>&1; then - printf 'runtime oracle: concurrent turns acquired the same node window\n' >&2 - exit 1 - fi - release_turn_window lead - claim_turn_window lead - release_turn_window lead - runtime_root= -} - -assert_exclusive_turn_window - -write_attention_snapshot() { - local output=$1 data_unclaimed=$2 platform_unclaimed=$3 occupied_role=${4:-} - local occupied_value=${5:-0} role unclaimed occupied - : >"$output.jsonl" - for role in $roles; do - unclaimed=0 - occupied=0 - test "$role" != data || unclaimed=$data_unclaimed - test "$role" != platform || unclaimed=$platform_unclaimed - test "$role" != "$occupied_role" || occupied=$occupied_value - jq -cn --arg role "$role" --argjson unclaimed "$unclaimed" \ - --argjson occupied "$occupied" \ - '{role:$role,open_unclaimed:$unclaimed,occupied_claims:$occupied}' >>"$output.jsonl" - done - jq -s '.' "$output.jsonl" >"$output" -} - -assert_open_attention_boundary() { - local snapshot counter counts unclaimed occupied query_source wave_source driver_source - local goal_source agents_source post_outcome_source - runtime_root="$scratch/attention-runtime" - mkdir -p "$runtime_root/turns" - - sqlite3 "$runtime_root/attention.db" ' - CREATE TABLE handlings(state TEXT NOT NULL, claim_fence INTEGER NOT NULL, - claim_attachment_id TEXT); - INSERT INTO handlings VALUES('\''open'\'',0,NULL); - INSERT INTO handlings VALUES('\''open'\'',7,NULL); - INSERT INTO handlings VALUES('\''open'\'',3,'\''attachment:occupied'\''); - INSERT INTO handlings VALUES('\''terminal'\'',9,NULL);' - counts=$(read_open_attention_counts "$runtime_root/attention.db") - IFS='|' read -r unclaimed occupied <&2 - exit 1 - } - query_source=$(declare -f read_open_attention_counts) - printf '%s\n' "$query_source" | grep -F -- \ - "state = '\\''open'\\'' AND claim_attachment_id IS NULL" >/dev/null || { - printf 'runtime oracle: open attention does not derive open-unclaimed work from authority occupancy\n' >&2 - exit 1 - } - if printf '%s\n' "$query_source" | grep -Ei -- \ - 'claim_fence|semantic|kind|payload|artifact|canonical_json|domainctl' >/dev/null; then - printf 'runtime oracle: open attention inspects non-occupancy semantics\n' >&2 - exit 1 - fi - wave_source=$(declare -f run_open_attention_wave) - printf '%s\n' "$wave_source" | grep -F -- '.open_unclaimed > 0' >/dev/null || { - printf 'runtime oracle: open attention wave does not use the open-unclaimed projection\n' >&2 - exit 1 - } - if printf '%s\n' "$wave_source" | grep -Ei -- \ - 'semantic|kind|payload|artifact|canonical_json|domainctl|gateway|ledger|payment' \ - >/dev/null; then - printf 'runtime oracle: open attention wave inspects scenario semantics\n' >&2 - exit 1 - fi - driver_source=$(declare -f drive_attention_until_outcome) - if printf '%s\n' "$driver_source" | grep -Ei -- \ - 'claim_fence|semantic|kind|payload|artifact|canonical_json|domainctl|gateway|ledger|payment' \ - >/dev/null; then - printf 'runtime oracle: bounded attention driver inspects scenario semantics\n' >&2 - exit 1 - fi - goal_source=$(declare -f observe_episode_goal) - printf '%s\n' "$goal_source" | grep -F -- \ - 'data-tool status "$incident_prefix"' >/dev/null || { - printf 'runtime oracle: episode goal does not observe historical ledger integrity\n' >&2 - exit 1 - } - printf '%s\n' "$goal_source" | grep -F -- 'lead-tool probe' >/dev/null || { - printf 'runtime oracle: episode goal omits its bounded real canary\n' >&2 - exit 1 - } - if printf '%s\n' "$goal_source" | grep -Ei -- \ - 'domainctl|[[:space:]]action[[:space:]]|handling|reference|event|repair|remediat|latency|timeout|config' \ - >/dev/null; then - printf 'runtime oracle: episode goal depends on Agent choreography or remediation\n' >&2 - exit 1 - fi - agents_source=$(declare -f run_agents) - test "$(printf '%s\n' "$agents_source" | grep -Fc -- 'run_turn lead')" = 1 && - ! printf '%s\n' "$agents_source" | grep -E -- 'for role|while .*round|run_open_attention_wave' \ - >/dev/null || { - printf 'runtime oracle: initial Agent entry still contains fixed all-node rounds\n' >&2 - exit 1 - } - post_outcome_source=$(declare -f run_post_outcome_attention) - test "$(printf '%s\n' "$post_outcome_source" | grep -Fc -- 'run_turn lead')" = 1 && - ! printf '%s\n' "$post_outcome_source" | grep -E -- 'for role|run_open_attention_wave' \ - >/dev/null || { - printf 'runtime oracle: post-outcome attention is not one lead opportunity\n' >&2 - exit 1 - } - - printf '0\n' >"$runtime_root/canary-calls" - compose() { - case " $* " in - *' data-tool status '*) cat "$runtime_root/history-source.json" ;; - *' lead-tool probe '*) - local calls - calls=$(cat "$runtime_root/canary-calls") - printf '%s\n' $((calls + 1)) >"$runtime_root/canary-calls" - cat "$runtime_root/canary-source.json" - ;; - *) return 1 ;; - esac - } - - # Historical failure is a complete false observation and never spends a - # real canary from the shared bounded service. - cat >"$runtime_root/history-source.json" <<'JSON' -{"role":"data","result":{"charges":8,"active_charges":8,"voided_charges":0,"unique_businesses":4,"duplicate_businesses":4}} -JSON - : >"$runtime_root/canary-source.json" - observe_episode_goal episode-goal 1 "$runtime_root/goal-history-false.json" incident-fixture - jq -e ' - (keys | sort) == ["canary","episode","observed","satisfied","schema","version"] and - .schema == "mnemon.r7.domain-ops.goal" and .version == 2 and - .episode == "episode-goal" and .satisfied == false and .canary == null and - .observed == {charges:8,active_charges:8,voided_charges:0, - unique_businesses:4,duplicate_businesses:4} - ' "$runtime_root/goal-history-false.json" >/dev/null - test "$(cat "$runtime_root/canary-calls")" = 0 - test ! -e "$runtime_root/episode-goal-incident-after.json" - - # A repaired history is insufficient while the bounded real canary still - # observes an unsafe customer path. - cat >"$runtime_root/history-source.json" <<'JSON' -{"role":"data","result":{"charges":8,"active_charges":4,"voided_charges":4,"unique_businesses":4,"duplicate_businesses":0}} -JSON - cat >"$runtime_root/canary-source.json" <<'JSON' -{"role":"lead","result":{"receipt":{"request_id":1,"business_id":"synthetic-001","capture_id":0,"route":"east","status":"failed"},"observed":{"charges":1,"active_charges":1,"voided_charges":0,"unique_businesses":1,"duplicate_businesses":0},"ledger":{"charges":1,"active_charges":0,"voided_charges":1,"unique_businesses":0,"duplicate_businesses":0}}} -JSON - observe_episode_goal episode-goal 2 "$runtime_root/goal-canary-false.json" incident-fixture - jq -e ' - .satisfied == false and .observed.active_charges == 4 and - .canary == {receipt_status:"failed",capture_id_present:false, - observed:{charges:1,active_charges:1,voided_charges:0, - unique_businesses:1,duplicate_businesses:0}, - settled:{charges:1,active_charges:0,voided_charges:1, - unique_businesses:0,duplicate_businesses:0}} - ' "$runtime_root/goal-canary-false.json" >/dev/null - test "$(cat "$runtime_root/canary-calls")" = 1 - - # Only repaired history plus one clean real checkout closes the mission goal. - cat >"$runtime_root/canary-source.json" <<'JSON' -{"role":"lead","result":{"receipt":{"request_id":2,"business_id":"synthetic-002","capture_id":9,"route":"west","status":"succeeded"},"observed":{"charges":1,"active_charges":1,"voided_charges":0,"unique_businesses":1,"duplicate_businesses":0},"ledger":{"charges":1,"active_charges":1,"voided_charges":0,"unique_businesses":1,"duplicate_businesses":0}}} -JSON - observe_episode_goal episode-goal 3 "$runtime_root/goal-true.json" incident-fixture - jq -e ' - .satisfied == true and .canary.receipt_status == "succeeded" and - .canary.capture_id_present == true and - .canary.observed == .canary.settled and - .canary.settled == {charges:1,active_charges:1,voided_charges:0, - unique_businesses:1,duplicate_businesses:0} - ' "$runtime_root/goal-true.json" >/dev/null - test "$(cat "$runtime_root/canary-calls")" = 2 - - # The driver and final adapter share a closed predicate: an asserted true - # value that contradicts its observation is rejected immediately. - jq '.satisfied = false' "$runtime_root/goal-true.json" \ - >"$runtime_root/goal-invalid-projection.json" - if validate_episode_goal episode-goal "$runtime_root/goal-invalid-projection.json" \ - >/dev/null 2>&1; then - printf 'runtime oracle: contradictory goal projection was accepted\n' >&2 - exit 1 - fi - cat >"$runtime_root/history-source.json" <<'JSON' -{"role":"data","result":{"charges":8,"active_charges":7,"voided_charges":0,"unique_businesses":4,"duplicate_businesses":1}} -JSON - printf '%s\n' '{"sealed":"existing-final-incident-evidence"}' \ - >"$runtime_root/episode-goal-incident-after.json" - cp "$runtime_root/episode-goal-incident-after.json" \ - "$runtime_root/episode-goal-incident-after.expected.json" - if observe_episode_goal episode-goal 4 "$runtime_root/goal-invalid.json" \ - incident-fixture >/dev/null 2>&1; then - printf 'runtime oracle: inconsistent historical counts were accepted\n' >&2 - exit 1 - fi - test ! -e "$runtime_root/goal-invalid.json" - cmp -s "$runtime_root/episode-goal-incident-after.expected.json" \ - "$runtime_root/episode-goal-incident-after.json" || { - printf 'runtime oracle: invalid goal observation overwrote final incident evidence\n' >&2 - exit 1 - } - test "$(find "$runtime_root" -maxdepth 1 -name '.episode-goal-goal-*' | wc -l | tr -d '[:space:]')" = 0 - - snapshot="$runtime_root/targeting.json" - write_attention_snapshot "$snapshot" 2 1 - - run_turn() { - local role=$1 prompt=$2 tag=$3 - test "$prompt" = "$neutral_attention" - : >"$runtime_root/turns/$tag" - } - wait_for_peer_delivery_quiescence() { - printf '%s\n' "$1" >>"$runtime_root/barriers" - } - run_open_attention_wave episode-test 1 "$snapshot" - test -f "$runtime_root/turns/episode-test-open-attention-1-data" - test -f "$runtime_root/turns/episode-test-open-attention-1-platform" - test "$(find "$runtime_root/turns" -type f | wc -l | tr -d '[:space:]')" = 2 - test "$(cat "$runtime_root/barriers")" = episode-test-open-attention-1 - - reset_attention_fixture() { - rm -rf -- "$runtime_root/turns" "$runtime_root/open-attention" - mkdir -p "$runtime_root/turns" "$runtime_root/open-attention" - : >"$runtime_root/barriers" - } - write_goal_result() { - local destination=$1 episode=$2 mode=$3 - case "$mode" in - satisfied) - jq -n --arg episode "$episode" ' - {schema:"mnemon.r7.domain-ops.goal",version:2,episode:$episode, - satisfied:true, - observed:{charges:8,active_charges:4,voided_charges:4, - unique_businesses:4,duplicate_businesses:0}, - canary:{receipt_status:"succeeded",capture_id_present:true, - observed:{charges:1,active_charges:1,voided_charges:0, - unique_businesses:1,duplicate_businesses:0}, - settled:{charges:1,active_charges:1,voided_charges:0, - unique_businesses:1,duplicate_businesses:0}}} - ' >"$destination" - ;; - historical_failure) - jq -n --arg episode "$episode" ' - {schema:"mnemon.r7.domain-ops.goal",version:2,episode:$episode, - satisfied:false, - observed:{charges:8,active_charges:8,voided_charges:0, - unique_businesses:4,duplicate_businesses:4},canary:null} - ' >"$destination" - ;; - canary_failure) - jq -n --arg episode "$episode" ' - {schema:"mnemon.r7.domain-ops.goal",version:2,episode:$episode, - satisfied:false, - observed:{charges:8,active_charges:4,voided_charges:4, - unique_businesses:4,duplicate_businesses:0}, - canary:{receipt_status:"failed",capture_id_present:false, - observed:{charges:1,active_charges:1,voided_charges:0, - unique_businesses:1,duplicate_businesses:0}, - settled:{charges:1,active_charges:0,voided_charges:1, - unique_businesses:0,duplicate_businesses:0}}} - ' >"$destination" - ;; - *) return 1 ;; - esac - } - - # A satisfied external goal ends attention immediately even when durable - # responsibilities remain open. - reset_attention_fixture - snapshot_open_attention() { - local output="$runtime_root/goal-first-$2.json" - write_attention_snapshot "$output" 2 1 - printf '%s\n' "$output" - } - goal_probe() { - write_goal_result "$3" "$1" satisfied - } - open_attention_turn_limit=16 - drive_attention_until_outcome episode-goal-first goal_probe - jq -e ' - .episode == "episode-goal-first" and .status == "outcome_observed" and - .turn_limit == 16 and .turns_used == 0 and (.waves | length) == 0 and - .goal.satisfied == true and - ([.final_nodes[] | select(.open_unclaimed > 0)] | length) == 2 and - all(.final_nodes[]; .occupied_claims == 0) - ' "$runtime_root/open-attention/episode-goal-first-settlement.json" >/dev/null - test "$(find "$runtime_root/turns" -type f | wc -l | tr -d '[:space:]')" = 0 - test ! -s "$runtime_root/barriers" - - # An unsatisfied goal receives one eligible wave. A later satisfied goal - # stops even if that collaboration produced more residual responsibilities. - reset_attention_fixture - counter="$runtime_root/goal-after-wave-counter" - printf '0\n' >"$counter" - snapshot_open_attention() { - local index output="$runtime_root/goal-after-wave-$2.json" - index=$(cat "$counter") - if test "$index" = 0; then write_attention_snapshot "$output" 1 0 - else write_attention_snapshot "$output" 2 1; fi - printf '%s\n' "$output" - } - goal_probe() { - local index - index=$(cat "$counter") - index=$((index + 1)) - printf '%s\n' "$index" >"$counter" - if test "$index" = 1; then write_goal_result "$3" "$1" historical_failure - else write_goal_result "$3" "$1" satisfied; fi - } - drive_attention_until_outcome episode-goal-after-wave goal_probe - jq -e ' - .status == "outcome_observed" and .turns_used == 1 and - [.waves[].wave] == [1] and .goal.satisfied == true and - ([.final_nodes[] | select(.open_unclaimed > 0)] | length) == 2 - ' "$runtime_root/open-attention/episode-goal-after-wave-settlement.json" >/dev/null - test -f "$runtime_root/turns/episode-goal-after-wave-open-attention-1-data" - test "$(cat "$runtime_root/barriers")" = episode-goal-after-wave-open-attention-1 - - # No eligible attention cannot be mistaken for a successful outcome. - reset_attention_fixture - snapshot_open_attention() { - local output="$runtime_root/no-eligible-$2.json" - write_attention_snapshot "$output" 0 0 - printf '%s\n' "$output" - } - goal_probe() { write_goal_result "$3" "$1" historical_failure; } - if drive_attention_until_outcome episode-no-eligible goal_probe >/dev/null 2>&1; then - printf 'runtime oracle: goal-free quiescence was accepted as success\n' >&2 - exit 1 - fi - test "$failure_stage" = scenario.episode-no-eligible.attention-quiescent-without-outcome - jq -e ' - .status == "quiescent_without_outcome" and .turns_used == 0 and - .goal.satisfied == false and all(.final_nodes[]; - .open_unclaimed == 0 and .occupied_claims == 0) - ' "$runtime_root/open-attention/episode-no-eligible-quiescent-without-outcome.json" \ - >/dev/null - - # A false goal remains false after one bounded turn, then the resource - # envelope fails closed before a second turn is issued. - reset_attention_fixture - open_attention_turn_limit=1 - snapshot_open_attention() { - local output="$runtime_root/exhausted-$2.json" - write_attention_snapshot "$output" 1 0 - printf '%s\n' "$output" - } - goal_probe() { write_goal_result "$3" "$1" historical_failure; } - if drive_attention_until_outcome episode-budget goal_probe >/dev/null 2>&1; then - printf 'runtime oracle: unbounded open attention was accepted\n' >&2 - exit 1 - fi - test "$failure_stage" = scenario.episode-budget.attention-budget-exhausted-before-outcome - jq -e ' - .episode == "episode-budget" and .status == "budget_exhausted_before_outcome" and - .turn_limit == 1 and .turns_used == 1 and [.waves[].wave] == [1] and - .goal.satisfied == false and - ([.final_nodes[] | select(.open_unclaimed > 0)] | length) == 1 and - all(.final_nodes[]; .occupied_claims == 0) - ' "$runtime_root/open-attention/episode-budget-budget-exhausted-before-outcome.json" \ - >/dev/null - test -f "$runtime_root/turns/episode-budget-open-attention-1-data" - test "$(cat "$runtime_root/barriers")" = episode-budget-open-attention-1 - - # Claim occupancy is a protocol safety failure and is recorded before any - # external goal I/O can hide it. - reset_attention_fixture - open_attention_turn_limit=16 - goal_calls=0 - snapshot_open_attention() { - local output="$runtime_root/occupied-$2.json" - write_attention_snapshot "$output" 0 0 data 1 - printf '%s\n' "$output" - } - goal_probe() { - goal_calls=$((goal_calls + 1)) - return 1 - } - if drive_attention_until_outcome episode-occupied goal_probe >/dev/null 2>&1; then - printf 'runtime oracle: an occupied claim was hidden by a satisfied goal\n' >&2 - exit 1 - fi - test "$failure_stage" = scenario.episode-occupied.attention-claim-occupied - jq -e ' - .episode == "episode-occupied" and .status == "claim_occupied" and - .turn_limit == 16 and .turns_used == 0 and (.waves | length) == 0 and - .goal == null and - ([.final_nodes[] | select(.occupied_claims > 0)] | map(.role)) == ["data"] and - all(.final_nodes[]; .open_unclaimed == 0) - ' "$runtime_root/open-attention/episode-occupied-claim-occupied.json" >/dev/null - test "$goal_calls" = 0 - test "$(find "$runtime_root/turns" -type f | wc -l | tr -d '[:space:]')" = 0 - test ! -s "$runtime_root/barriers" - runtime_root= -} - -(assert_open_attention_boundary) - -stream_mode=valid -pi_process() { - case "$stream_mode" in - valid) - printf '%s\n' \ - '{"type":"message_update","message":{"role":"assistant","content":"transient"}}' \ - '{"type":"tool_execution_update","toolCallId":"tool-1","progress":"transient"}' \ - '{"type":"message_end","message":{"role":"assistant","stopReason":"stop"}}' \ - '{"type":"agent_end"}' - ;; - malformed) printf '%s\n' '{not-json' ;; - upstream) - printf '%s\n' '{"type":"agent_end"}' - return 17 - ;; - *) return 18 ;; - esac -} - -stream_mode=valid -(bounded_pi_process unused oracle >"$scratch/filtered.jsonl") -test "$(wc -l <"$scratch/filtered.jsonl" | tr -d '[:space:]')" = 2 -jq -s -e ' - length == 2 and - .[0].type == "message_end" and .[0].message.stopReason == "stop" and - .[1].type == "agent_end" and - all(.[]; .type != "message_update" and .type != "tool_execution_update") -' "$scratch/filtered.jsonl" >/dev/null - -stream_mode=malformed -if (bounded_pi_process unused oracle >"$scratch/malformed.jsonl" 2>/dev/null); then - printf 'runtime oracle: malformed provider record was accepted\n' >&2 - exit 1 -fi -stream_mode=upstream -if (bounded_pi_process unused oracle >"$scratch/upstream.jsonl" 2>/dev/null); then - printf 'runtime oracle: upstream provider failure was hidden by jq\n' >&2 - exit 1 -fi - -write_sanitizer_stream() { - local reason=$1 destination=$2 - jq -nc '{type:"message_start",message:{role:"custom",customType:"mnemond"}}' \ - >"$destination" - jq -nc --arg reason "$reason" \ - '{type:"message_end",message:{role:"assistant",stopReason:$reason}}' \ - >>"$destination" - jq -nc '{type:"agent_end"}' >>"$destination" -} - -write_current_stream() { - local destination=$1 - shift - write_current_stream_command "$destination" \ - "mnemon-harness agent current --json" "$@" -} - -write_current_stream_command() { - local destination=$1 command=$2 - shift 2 - local index=0 view - jq -nc '{type:"message_start",message:{role:"custom",customType:"mnemond"}}' \ - >"$destination" - for view in "$@"; do - index=$((index + 1)) - jq -nc --arg id "current-$index" --arg command "$command" \ - '{type:"tool_execution_start",toolCallId:$id,toolName:"bash", - args:{command:$command}}' >>"$destination" - jq -nc --arg id "current-$index" --arg text "$view" \ - '{type:"tool_execution_end",toolCallId:$id,toolName:"bash",isError:false, - result:{content:[{type:"text",text:$text}],details:{output:$text}}}' \ - >>"$destination" - done - jq -nc '{type:"message_end",message:{role:"assistant",stopReason:"stop"}}' \ - >>"$destination" - jq -nc '{type:"agent_end"}' >>"$destination" -} - -write_native_current_stream() { - local destination=$1 - shift - local index=0 view - jq -nc '{type:"message_start",message:{role:"custom",customType:"mnemond"}}' \ - >"$destination" - for view in "$@"; do - index=$((index + 1)) - jq -nc --arg id "native-current-$index" \ - '{type:"tool_execution_start",toolCallId:$id,toolName:"mnemond_current",args:{}}' \ - >>"$destination" - jq -nc --arg id "native-current-$index" --arg text "$view" \ - '{type:"tool_execution_end",toolCallId:$id,toolName:"mnemond_current",isError:false, - result:{content:[{type:"text",text:$text}], - details:{schema:"mnemon.pi.current",version:1,status:"projected"}}}' \ - >>"$destination" - done - jq -nc '{type:"message_end",message:{role:"assistant",stopReason:"stop"}}' \ - >>"$destination" - jq -nc '{type:"agent_end"}' >>"$destination" -} - -assert_native_current_stream_rejected() { - local name=$1 source=$2 expected=$3 output partial - output="$scratch/$name.json" - if sanitize_turn lead "oracle-$name" "$source" "$output"; then - printf 'runtime oracle: malformed native Current stream %s was accepted\n' "$name" >&2 - exit 1 - fi - partial=$(summarize_partial_turn "$source") - jq -e --arg expected "$expected" ' - .current_boundary.native_protocol_valid == false and - any(.current_boundary.native_violations[]; - .class == $expected and .count >= 1) - ' <<<"$partial" >/dev/null || { - printf 'runtime oracle: malformed native Current stream %s lacked bounded diagnostics\n' \ - "$name" >&2 - exit 1 - } -} - -write_domain_observation_stream() { - local destination=$1 - jq -nc '{type:"message_start",message:{role:"custom",customType:"mnemond"}}' \ - >"$destination" - jq -nc '{type:"tool_execution_start",toolCallId:"domain-status",toolName:"bash", - args:{command:"domainctl --endpoint http://secret-endpoint-sentinel status"}}' >>"$destination" - jq -nc '{type:"tool_execution_end",toolCallId:"domain-status",toolName:"bash", - isError:false,result:{content:[{type:"text", - text:"{\"role\":\"lead\",\"result\":{\"secret-response-sentinel\":true}}"}]}}' \ - >>"$destination" - jq -nc '{type:"tool_execution_start",toolCallId:"domain-read",toolName:"bash", - args:{command:"domainctl read /secret-path-sentinel"}}' >>"$destination" - jq -nc '{type:"tool_execution_end",toolCallId:"domain-read",toolName:"bash", - isError:true,result:{content:[{type:"text",text:"secret-read-error-sentinel"}]}}' \ - >>"$destination" - jq -nc '{type:"tool_execution_start",toolCallId:"domain-probe",toolName:"bash", - args:{command:"domainctl probe"}}' >>"$destination" - jq -nc '{type:"tool_execution_end",toolCallId:"domain-probe",toolName:"bash", - isError:false,result:{content:[{type:"text", - text:"{\"role\":\"lead\",\"result\":{\"secret-probe-sentinel\":true}}"}]}}' \ - >>"$destination" - jq -nc '{type:"tool_execution_start",toolCallId:"domain-action",toolName:"bash", - args:{command:"domainctl --endpoint=http://secret-endpoint-sentinel action /secret-action-sentinel '\''{\"secret-payload-sentinel\":true}'\''"}}' \ - >>"$destination" - jq -nc '{type:"tool_execution_end",toolCallId:"domain-action",toolName:"bash", - isError:false,result:{content:[{type:"text", - text:"{\"role\":\"lead\",\"result\":{\"secret-action-result-sentinel\":true}}"}]}}' \ - >>"$destination" - jq -nc '{type:"tool_execution_start",toolCallId:"domain-ambiguous",toolName:"bash", - args:{command:"domainctl read /first; domainctl action /second '\''{}'\''"}}' \ - >>"$destination" - jq -nc '{type:"tool_execution_end",toolCallId:"domain-ambiguous",toolName:"bash", - isError:false,result:{content:[{type:"text", - text:"{\"role\":\"lead\",\"result\":{\"ambiguous-result-sentinel\":true}}"}]}}' \ - >>"$destination" - jq -nc '{type:"tool_execution_start",toolCallId:"domain-masked",toolName:"bash", - args:{command:"domainctl read /masked-failure-sentinel || true"}}' >>"$destination" - jq -nc '{type:"tool_execution_end",toolCallId:"domain-masked",toolName:"bash", - isError:false,result:{content:[{type:"text",text:""}]}}' \ - >>"$destination" - jq -nc '{type:"message_end",message:{role:"assistant",stopReason:"stop"}}' \ - >>"$destination" - jq -nc '{type:"agent_end"}' >>"$destination" -} - -write_repeated_probe_stream() { - local destination=$1 index - jq -nc '{type:"message_start",message:{role:"custom",customType:"mnemond"}}' \ - >"$destination" - for index in 1 2; do - jq -nc --arg id "domain-probe-$index" \ - '{type:"tool_execution_start",toolCallId:$id,toolName:"bash", - args:{command:"domainctl probe"}}' >>"$destination" - jq -nc --arg id "domain-probe-$index" \ - '{type:"tool_execution_end",toolCallId:$id,toolName:"bash",isError:false, - result:{content:[{type:"text",text: - "{\"role\":\"lead\",\"result\":{\"bounded\":true}}"}]}}' \ - >>"$destination" - done - jq -nc '{type:"message_end",message:{role:"assistant",stopReason:"stop"}}' \ - >>"$destination" - jq -nc '{type:"agent_end"}' >>"$destination" -} - -write_submit_stream() { - local destination=$1 - shift - local index=0 result is_error - jq -nc '{type:"message_start",message:{role:"custom",customType:"mnemond"}}' \ - >"$destination" - jq -nc '{type:"tool_execution_start",toolCallId:"submit-current",toolName:"bash", - args:{command:"mnemon-harness agent current --json"}}' >>"$destination" - jq -nc --arg text "$root_view" \ - '{type:"tool_execution_end",toolCallId:"submit-current",toolName:"bash",isError:false, - result:{content:[{type:"text",text:$text}],details:{output:$text}}}' \ - >>"$destination" - for result in "$@"; do - index=$((index + 1)) - is_error=false - case "$result" in - *'"status":"error"'*) - is_error=true - result="$result"$'\n\nCommand exited with code 3' - ;; - esac - jq -nc --arg id "submit-$index" \ - '{type:"tool_execution_start",toolCallId:$id,toolName:"bash", - args:{command:"mnemon-harness agent submit --json"}}' >>"$destination" - jq -nc --arg id "submit-$index" --arg text "$result" --argjson is_error "$is_error" \ - '{type:"tool_execution_end",toolCallId:$id,toolName:"bash",isError:$is_error, - result:{content:[{type:"text",text:$text}],details:{output:$text}}}' \ - >>"$destination" - done - jq -nc '{type:"message_end",message:{role:"assistant",stopReason:"stop"}}' \ - >>"$destination" - jq -nc '{type:"agent_end"}' >>"$destination" -} - -write_native_submit_stream() { - local destination=$1 result=$2 - jq -nc '{type:"message_start",message:{role:"custom",customType:"mnemond"}}' \ - >"$destination" - jq -nc '{type:"tool_execution_start",toolCallId:"native-submit-current", - toolName:"mnemond_current",args:{}}' >>"$destination" - jq -nc --arg text "$root_view" \ - '{type:"tool_execution_end",toolCallId:"native-submit-current", - toolName:"mnemond_current",isError:false, - result:{content:[{type:"text",text:$text}], - details:{schema:"mnemon.pi.current",version:1,status:"projected"}}}' \ - >>"$destination" - jq -nc '{type:"tool_execution_start",toolCallId:"native-submit",toolName:"mnemond_submit", - args:{intent:{kind:"opaque",payload:"bounded",consequence:"handling.advance"}}}' \ - >>"$destination" - jq -nc --arg text "$result" \ - '{type:"tool_execution_end",toolCallId:"native-submit",toolName:"mnemond_submit", - isError:false,result:{content:[{type:"text",text:$text}], - details:{schema:"mnemon.pi.effect",version:1,status:"settled"}}}' \ - >>"$destination" - jq -nc '{type:"message_end",message:{role:"assistant",stopReason:"stop"}}' \ - >>"$destination" - jq -nc '{type:"agent_end"}' >>"$destination" -} - -write_sequential_submit_stream() { - local destination=$1 count=$2 - shift 2 - local command= index result combined= is_error=false - for index in $(seq 1 "$count"); do - command="${command:+$command; }mnemon-harness agent submit --json" - done - for result in "$@"; do - combined="${combined:+$combined -}$result" - case "$result" in *'"status":"error"'*) is_error=true ;; esac - done - test "$is_error" = false || combined="$combined"$'\n\nCommand exited with code 3' - jq -nc '{type:"message_start",message:{role:"custom",customType:"mnemond"}}' \ - >"$destination" - jq -nc '{type:"tool_execution_start",toolCallId:"submit-current",toolName:"bash", - args:{command:"mnemon-harness agent current --json"}}' >>"$destination" - jq -nc --arg text "$root_view" \ - '{type:"tool_execution_end",toolCallId:"submit-current",toolName:"bash",isError:false, - result:{content:[{type:"text",text:$text}],details:{output:$text}}}' \ - >>"$destination" - jq -nc --arg command "$command" \ - '{type:"tool_execution_start",toolCallId:"submit-batch",toolName:"bash", - args:{command:$command}}' >>"$destination" - jq -nc --arg text "$combined" --argjson is_error "$is_error" \ - '{type:"tool_execution_end",toolCallId:"submit-batch",toolName:"bash", - isError:$is_error,result:{content:[{type:"text",text:$text}],details:{output:$text}}}' \ - >>"$destination" - jq -nc '{type:"message_end",message:{role:"assistant",stopReason:"stop"}}' \ - >>"$destination" - jq -nc '{type:"agent_end"}' >>"$destination" -} - -write_sanitizer_stream stop "$scratch/stop.jsonl" -sanitize_turn lead oracle "$scratch/stop.jsonl" "$scratch/stop.json" -test "$(jq '.delegate_calls' "$scratch/stop.json")" = 0 -root_view='{"schema":"mnemon.agent.view","version":7,"view":"view:root-secret","outstanding":{"open_total":0,"related_total":0,"related_projected":0,"truncated":false},"allowed_intents":[]}' -current_view='{"schema":"mnemon.agent.view","version":7,"view":"view:current-secret","current":{"facts":{"handle":"handling:secret","reply_to":"event:secret","reply_required":true,"reply_target":"peer-secret","reply_observation_pending":true},"semantic":{"kind":"secret.kind","payload":"secret payload"}},"related":[{"facts":{"event":"event:related-secret","relation":"correlation"},"semantic":{"kind":"secret.related","payload":"related secret"}}],"outstanding":{"open_total":3,"related_total":2,"related_projected":1,"truncated":true},"allowed_intents":[]}' -full_view=$(jq -nc --arg digest \ - 'sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' ' - { - schema:"mnemon.agent.view",version:7,view:"view:full-secret", - current:{ - facts:{handle:"handling:full-secret",reply_to:"event:full-secret", - reply_required:false,reply_observation_pending:false, - artifacts:[{digest:$digest,handle:"artifact:current-secret"}]}, - semantic:{kind:"secret.current",payload:"current secret"} - }, - related:[{ - facts:{event:"event:related-full-secret",relation:"terminal_reply",outcome:"completed", - artifacts:[{digest:$digest,handle:"artifact:related-secret"}]}, - semantic:{kind:"secret.related",payload:"related secret"} - }], - outstanding:{open_total:1,related_total:2,related_projected:1,truncated:true}, - references:[ - {facts:{key:"playbook-secret",head:"event:active-secret",state:"active", - artifact:{digest:$digest,handle:"artifact:reference-secret"}, - terminal_outcomes:{completed:1,declined:2,unresolved:3}}}, - {facts:{key:"retired-secret",head:"event:retracted-secret",state:"retracted", - terminal_outcomes:{completed:0,declined:0,unresolved:0}}} - ], - targets:["peer-secret"], - allowed_intents:[ - {artifacts:"zero_or_one",consequence:"handling.advance",subject:"current"}, - {artifacts:"exactly_one",consequence:"reference.supersede",subject:"none", - reference:"offered_head",successors:"none"} - ], - provenance_handles:["event:full-secret","event:related-full-secret"] - }') -write_current_stream "$scratch/root-view.jsonl" "$root_view" -sanitize_turn lead oracle-root-view "$scratch/root-view.jsonl" "$scratch/root-view.json" -jq -e ' - .current_reads == 1 and .view == { - has_current:false,open_total:0,related_total:0,related_projected:0,truncated:false - } -' "$scratch/root-view.json" >/dev/null -jq -s -c '.[0], .[2], .[1], .[3:][]' "$scratch/root-view.jsonl" \ - >"$scratch/shell-current-end-before-start.jsonl" -if sanitize_turn lead oracle-shell-current-end-before-start \ - "$scratch/shell-current-end-before-start.jsonl" \ - "$scratch/shell-current-end-before-start.json"; then - printf 'runtime oracle: a shell Current end preceding its start was accepted\n' >&2 - exit 1 -fi -write_current_stream "$scratch/full-view.jsonl" "$full_view" -sanitize_turn lead oracle-full-view "$scratch/full-view.jsonl" "$scratch/full-view.json" -jq -e ' - .current_reads == 1 and .view == { - has_current:true,reply_required:false,reply_observation_pending:false, - open_total:1,related_total:2, - related_projected:1,truncated:true - } -' "$scratch/full-view.json" >/dev/null -write_native_current_stream "$scratch/native-current-view.jsonl" "$full_view" "$full_view" -sanitize_turn lead oracle-native-current "$scratch/native-current-view.jsonl" \ - "$scratch/native-current-view.json" -jq -e ' - .bash_calls == 0 and .current_reads == 2 and .view == { - has_current:true,reply_required:false,reply_observation_pending:false, - open_total:1,related_total:2, - related_projected:1,truncated:true - } -' "$scratch/native-current-view.json" >/dev/null -native_partial=$(summarize_partial_turn "$scratch/native-current-view.jsonl") -jq -e ' - .current_attempts == 2 and .current_boundary.observed_starts == 0 and - .current_boundary.native_starts == 2 and .current_boundary.native_ends == 2 and - .current_boundary.mixed_surfaces == false and - .current_boundary.native_protocol_valid == true and - .current_boundary.native_unfinished == 0 and - .current_boundary.native_violations == [] and - .current_boundary.native_results == [ - {class:"projected",is_error:false},{class:"projected",is_error:false}] -' <<<"$native_partial" >/dev/null -write_native_current_stream "$scratch/native-current-one.jsonl" "$full_view" -command='mnemon-harness agent current --json >/dev/null; printf forged' -jq -c --arg command "$command" --arg text "$full_view" ' - if .type == "message_end" then - {type:"tool_execution_start",toolCallId:"mixed-shell-current",toolName:"bash", - args:{command:$command}}, - {type:"tool_execution_end",toolCallId:"mixed-shell-current",toolName:"bash", - isError:false,result:{content:[{type:"text",text:$text}],details:{output:$text}}}, - . - else . end -' "$scratch/native-current-one.jsonl" >"$scratch/mixed-current-surfaces.jsonl" -sanitize_turn lead oracle-mixed-current-surfaces \ - "$scratch/mixed-current-surfaces.jsonl" "$scratch/mixed-current-surfaces.json" -jq -e ' - .current_reads == 1 and .view == { - has_current:true,reply_required:false,reply_observation_pending:false, - open_total:1,related_total:2, - related_projected:1,truncated:true - } -' "$scratch/mixed-current-surfaces.json" >/dev/null -mixed_partial=$(summarize_partial_turn "$scratch/mixed-current-surfaces.jsonl") -jq -e ' - .current_boundary.mixed_surfaces == true and - .current_boundary.untrusted_shell_explorations == 1 and - .current_boundary.view_objects == 1 -' <<<"$mixed_partial" >/dev/null -jq -c 'if .type == "tool_execution_start" and .toolName == "mnemond_current" - then ., . else . end' "$scratch/native-current-one.jsonl" \ - >"$scratch/native-current-duplicate-start.jsonl" -assert_native_current_stream_rejected native-current-duplicate-start \ - "$scratch/native-current-duplicate-start.jsonl" duplicate_start -jq -s -c '.[0], .[2], .[1], .[3:][]' "$scratch/native-current-one.jsonl" \ - >"$scratch/native-current-end-before-start.jsonl" -assert_native_current_stream_rejected native-current-end-before-start \ - "$scratch/native-current-end-before-start.jsonl" orphan_or_early_end -jq -c --arg text "$full_view" ' - if .type == "message_end" then - {type:"tool_execution_end",toolCallId:"native-current-orphan", - toolName:"mnemond_current",isError:false, - result:{content:[{type:"text",text:$text}], - details:{schema:"mnemon.pi.current",version:1,status:"projected"}}}, . - else . end -' "$scratch/native-current-one.jsonl" >"$scratch/native-current-orphan-end.jsonl" -assert_native_current_stream_rejected native-current-orphan-end \ - "$scratch/native-current-orphan-end.jsonl" orphan_or_early_end -jq -c 'if .type == "tool_execution_start" and .toolName == "mnemond_current" - then .args = {unexpected:true} else . end' "$scratch/native-current-one.jsonl" \ - >"$scratch/native-current-nonempty-args.jsonl" -assert_native_current_stream_rejected native-current-nonempty-args \ - "$scratch/native-current-nonempty-args.jsonl" invalid_start_args -printf '%s\n' "$current_failed_reason" | jq -Rs '{type:"tool_execution_end", - toolCallId:"native-current-failed",toolName:"mnemond_current",isError:true, - result:{content:[{type:"text",text:(.[:-1])}], - details:{schema:"mnemon.pi.current",version:1,status:"failed"}}}' \ - >"$scratch/native-current-failed-end.json" -jq -nc '{type:"message_start",message:{role:"custom",customType:"mnemond"}}, - {type:"tool_execution_start",toolCallId:"native-current-failed", - toolName:"mnemond_current",args:{}}, - input,{type:"message_end",message:{role:"assistant",stopReason:"stop"}}, - {type:"agent_end"}' "$scratch/native-current-failed-end.json" \ - >"$scratch/native-current-failed.jsonl" -sanitize_turn lead oracle-native-current-failed "$scratch/native-current-failed.jsonl" \ - "$scratch/native-current-failed.json" -jq -e '.current_reads == 0 and (.view | not)' "$scratch/native-current-failed.json" >/dev/null -jq -c --arg text "$full_view" ' - if .type == "message_end" then - {type:"tool_execution_start",toolCallId:"failed-native-shell-current",toolName:"bash", - args:{command:"mnemon-harness agent current --json"}}, - {type:"tool_execution_end",toolCallId:"failed-native-shell-current",toolName:"bash", - isError:false,result:{content:[{type:"text",text:$text}],details:{output:$text}}}, - . - else . end -' "$scratch/native-current-failed.jsonl" >"$scratch/native-failed-shell-valid.jsonl" -sanitize_turn lead oracle-native-failed-shell-valid \ - "$scratch/native-failed-shell-valid.jsonl" "$scratch/native-failed-shell-valid.json" -jq -e '.current_reads == 0 and (.view | not)' \ - "$scratch/native-failed-shell-valid.json" >/dev/null -native_failed_mixed_partial=$(summarize_partial_turn \ - "$scratch/native-failed-shell-valid.jsonl") -jq -e ' - .current_boundary.mixed_surfaces == true and - .current_boundary.untrusted_shell_explorations == 1 and - .current_boundary.native_results == [{class:"current_error",is_error:true}] -' <<<"$native_failed_mixed_partial" >/dev/null -jq -c 'if .type == "tool_execution_end" then .result.details.status = "unknown" else . end' \ - "$scratch/native-current-view.jsonl" >"$scratch/native-current-invalid.jsonl" -if sanitize_turn lead oracle-native-current-invalid "$scratch/native-current-invalid.jsonl" \ - "$scratch/native-current-invalid.json"; then - printf 'runtime oracle: an unclassified native Current result was accepted\n' >&2 - exit 1 -fi -forged_command='mnemon-harness agent current --json >/dev/null; printf forged' -write_current_stream_command "$scratch/forged-view.jsonl" "$forged_command" "$root_view" -if sanitize_turn lead oracle-forged-view "$scratch/forged-view.jsonl" \ - "$scratch/forged-view.json"; then - printf 'runtime oracle: a non-exact current invocation was silently ignored\n' >&2 - exit 1 -fi -write_current_stream_command "$scratch/path-view.jsonl" \ - "/tmp/mnemon-harness agent current --json" "$root_view" -if sanitize_turn lead oracle-path-view "$scratch/path-view.jsonl" \ - "$scratch/path-view.json"; then - printf 'runtime oracle: an unfrozen current binary path was trusted\n' >&2 - exit 1 -fi -for wrapped_current in \ - 'command mnemon-harness agent current --json' \ - '(mnemon-harness agent current --json)'; do - write_current_stream_command "$scratch/wrapped-view.jsonl" \ - "$wrapped_current" "$root_view" - if sanitize_turn lead oracle-wrapped-view "$scratch/wrapped-view.jsonl" \ - "$scratch/wrapped-view.json"; then - printf 'runtime oracle: a wrapped current invocation was silently ignored\n' >&2 - exit 1 - fi -done -write_current_stream "$scratch/current-view.jsonl" "$current_view" "$current_view" -sanitize_turn lead oracle-current-view "$scratch/current-view.jsonl" \ - "$scratch/current-view.json" -jq -e ' - .current_reads == 2 and .view == { - has_current:true,reply_required:true,reply_observation_pending:true, - open_total:3,related_total:2, - related_projected:1,truncated:true - } -' "$scratch/current-view.json" >/dev/null -if grep -E 'secret|handling:|event:|peer-' "$scratch/current-view.json" >/dev/null; then - printf 'runtime oracle: sanitized Agent View retained semantic or authority content\n' >&2 - exit 1 -fi -inconsistent_view=$(printf '%s' "$current_view" | jq -c '.outstanding.open_total = 4') -write_current_stream "$scratch/inconsistent-view.jsonl" "$current_view" "$inconsistent_view" -if sanitize_turn lead oracle-inconsistent-view "$scratch/inconsistent-view.jsonl" \ - "$scratch/inconsistent-view.json"; then - printf 'runtime oracle: inconsistent Agent Views were silently combined\n' >&2 - exit 1 -fi -malformed_view=$(printf '%s' "$root_view" | jq -c '.private_authority = "secret"') -write_current_stream "$scratch/malformed-view.jsonl" "$malformed_view" -if sanitize_turn lead oracle-malformed-view "$scratch/malformed-view.jsonl" \ - "$scratch/malformed-view.json"; then - printf 'runtime oracle: a non-exact Agent View was accepted\n' >&2 - exit 1 -fi -write_domain_observation_stream "$scratch/domain-observations.jsonl" -sanitize_turn lead oracle-domain-observations "$scratch/domain-observations.jsonl" \ - "$scratch/domain-observations.json" -jq -e ' - .domain_operations == { - read:{attempts:4,successes:1,tool_errors:1,invalid_results:1, - batched_unattributed:1}, - probe:{attempts:1,successes:1,tool_errors:0,invalid_results:0, - batched_unattributed:0}, - mutation:{attempts:2,successes:1,tool_errors:0,invalid_results:0, - batched_unattributed:1} - } -' "$scratch/domain-observations.json" >/dev/null -write_repeated_probe_stream "$scratch/domain-repeated-probes.jsonl" -sanitize_turn lead oracle-domain-repeated-probes \ - "$scratch/domain-repeated-probes.jsonl" "$scratch/domain-repeated-probes.json" -jq -e '.domain_operations.probe == - {attempts:2,successes:2,tool_errors:0,invalid_results:0,batched_unattributed:0}' \ - "$scratch/domain-repeated-probes.json" >/dev/null || { - printf 'runtime oracle: repeated probes lost bounded outcome accounting\n' >&2 - exit 1 -} -printf '%s\n' '[{"id":"event:before","digest":"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}]' \ - >"$scratch/events-before.json" -printf '%s\n' '[{"id":"event:before","digest":"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},{"id":"event:new","digest":"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"}]' \ - >"$scratch/events-after.json" -printf '%s\n' '{"accepted_receipts":1}' >"$scratch/event-binding.json" -bind_turn_events "$scratch/events-before.json" "$scratch/events-after.json" \ - "$scratch/event-binding.json" -jq -e '.accepted_events == [{id:"event:new",digest:"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"}]' \ - "$scratch/event-binding.json" >/dev/null -printf '%s\n' '{"accepted_receipts":0}' >"$scratch/event-binding-mismatch.json" -bind_turn_events "$scratch/events-before.json" "$scratch/events-after.json" \ - "$scratch/event-binding-mismatch.json" -jq -e '.accepted_receipts == 0 and (.accepted_events | length) == 1' \ - "$scratch/event-binding-mismatch.json" >/dev/null -printf '%s\n' '{"accepted_receipts":1}' >"$scratch/event-replay.json" -bind_turn_events "$scratch/events-before.json" "$scratch/events-before.json" \ - "$scratch/event-replay.json" -jq -e '.accepted_receipts == 1 and .accepted_events == []' \ - "$scratch/event-replay.json" >/dev/null -printf '%s\n' '[{"id":"event:before","digest":"sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"},{"id":"event:new","digest":"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"}]' \ - >"$scratch/events-drifted.json" -if bind_turn_events "$scratch/events-before.json" "$scratch/events-drifted.json" \ - "$scratch/event-binding-mismatch.json"; then - printf 'runtime oracle: an accepted Event changed across a turn boundary\n' >&2 - exit 1 -fi -printf '%s\n' '[{"id":"event:before","digest":"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},{"id":"event:new","digest":"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"},{"id":"event:second","digest":"sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"}]' \ - >"$scratch/events-too-many.json" -if bind_turn_events "$scratch/events-before.json" "$scratch/events-too-many.json" \ - "$scratch/event-binding-mismatch.json"; then - printf 'runtime oracle: two accepted Events were attributed to one turn\n' >&2 - exit 1 -fi -if grep -E 'secret-|ambiguous-result|wrong-role' "$scratch/domain-observations.json" >/dev/null; then - printf 'runtime oracle: sanitized domain observation retained command or result content\n' >&2 - exit 1 -fi -partial=$(summarize_partial_turn "$scratch/stop.jsonl") -jq -e ' - .record_types.message_start == 1 and - .record_types.message_end == 1 and - .record_types.agent_end == 1 and - .message_boundaries == [ - {"type":"message_start","role":"custom","custom_type":"mnemond"}, - {"type":"message_end","role":"assistant","custom_type":""} - ] and - .assistant_stop_reasons == ["stop"] -' <<<"$partial" >/dev/null -printf '%s\n' \ - '{"type":"tool_execution_start","toolName":"bash","toolCallId":"read","args":{"command":"domainctl status"}}' \ - '{"type":"tool_execution_start","toolName":"bash","toolCallId":"probe","args":{"command":"domainctl probe"}}' \ - '{"type":"tool_execution_start","toolName":"bash","toolCallId":"mutation","args":{"command":"domainctl action /admin/config {}"}}' \ - >"$scratch/partial-domain-operations.jsonl" -domain_partial=$(summarize_partial_turn "$scratch/partial-domain-operations.jsonl") -jq -e '.domain_calls == 3 and - .domain_invocations == {read:1,probe:1,mutation:1}' \ - <<<"$domain_partial" >/dev/null || { - printf 'runtime oracle: partial diagnostics lost domain operation classes\n' >&2 - exit 1 -} -printf '%s' 'HTTP 503 provider unavailable' >"$scratch/provider.err" -provider_error=$(summarize_provider_stderr "$scratch/provider.err") -jq -e ' - .bytes == 29 and .unavailable == true and - (.auth or .rate_limited or .balance or .invalid_request or .network | not) -' <<<"$provider_error" >/dev/null -for terminal_reason in error aborted; do - write_sanitizer_stream "$terminal_reason" "$scratch/$terminal_reason.jsonl" - if sanitize_turn lead oracle "$scratch/$terminal_reason.jsonl" \ - "$scratch/$terminal_reason.json"; then - printf 'runtime oracle: terminal %s was accepted\n' "$terminal_reason" >&2 - exit 1 - fi -done - -write_delegate_stream() { - local destination=$1 status index=0 is_error - shift - jq -nc '{type:"message_start",message:{role:"custom",customType:"mnemond"}}' \ - >"$destination" - for status in "$@"; do - index=$((index + 1)) - is_error=true - test "$status" != completed || is_error=false - jq -nc --arg id "delegate-$index" \ - '{type:"tool_execution_start",toolCallId:$id,toolName:"delegate", - args:{task:"bounded independent analysis"}}' >>"$destination" - jq -nc --arg id "delegate-$index" --arg status "$status" \ - --argjson is_error "$is_error" \ - '{type:"tool_execution_end",toolCallId:$id,toolName:"delegate",isError:$is_error, - result:{content:[{type:"text",text:"bounded observation"}], - details:{schema:"mnemon.pi.delegate",version:1,status:$status}}}' \ - >>"$destination" - done - jq -nc '{type:"message_end",message:{role:"assistant",stopReason:"stop"}}' \ - >>"$destination" - jq -nc '{type:"agent_end"}' >>"$destination" -} - -write_host_delegate_disposition_stream() { - local destination=$1 reason=$2 - jq -nc '{type:"message_start",message:{role:"custom",customType:"mnemond"}}' \ - >"$destination" - jq -nc '{type:"tool_execution_start",toolCallId:"delegate-host",toolName:"delegate", - args:{task:"bounded independent analysis"}}' >>"$destination" - jq -nc --arg reason "$reason" \ - '{type:"tool_execution_end",toolCallId:"delegate-host",toolName:"delegate", - isError:true,result:{content:[{type:"text",text:$reason}],details:{}}}' \ - >>"$destination" - jq -nc '{type:"message_end",message:{role:"assistant",stopReason:"stop"}}' \ - >>"$destination" - jq -nc '{type:"agent_end"}' >>"$destination" -} - -write_delegate_stream "$scratch/delegate.jsonl" completed -sanitize_turn lead oracle-delegate "$scratch/delegate.jsonl" "$scratch/delegate.json" -test "$(jq '.delegate_calls' "$scratch/delegate.json")" = 1 -write_delegate_stream "$scratch/contained-delegate.jsonl" completed slot_used -sanitize_turn lead oracle-contained-delegate "$scratch/contained-delegate.jsonl" \ - "$scratch/contained-delegate.json" -test "$(jq '.delegate_calls' "$scratch/contained-delegate.json")" = 1 -write_host_delegate_disposition_stream "$scratch/host-delegate.jsonl" \ - "$attention_exhausted_reason" -sanitize_turn lead oracle-host-delegate "$scratch/host-delegate.jsonl" \ - "$scratch/host-delegate.json" -test "$(jq '.delegate_calls' "$scratch/host-delegate.json")" = 0 -host_delegate_partial=$(summarize_partial_turn "$scratch/host-delegate.jsonl") -jq -e ' - .delegate_attempts == 1 and .delegate_effects == 0 and - .delegate_results == [{class:"host_attention_disposition",is_error:true}] -' <<<"$host_delegate_partial" >/dev/null -write_host_delegate_disposition_stream "$scratch/unclassified-delegate.jsonl" \ - 'unclassified delegate failure' -if sanitize_turn lead oracle-unclassified-delegate \ - "$scratch/unclassified-delegate.jsonl" "$scratch/unclassified-delegate.json"; then - printf 'runtime oracle: an unclassified delegate error was accepted\n' >&2 - exit 1 -fi -jq -c 'if .type == "tool_execution_end" then .result.extra = true else . end' \ - "$scratch/host-delegate.jsonl" >"$scratch/malformed-host-delegate.jsonl" -if sanitize_turn lead oracle-malformed-host-delegate \ - "$scratch/malformed-host-delegate.jsonl" "$scratch/malformed-host-delegate.json"; then - printf 'runtime oracle: a malformed Host attention disposition was accepted\n' >&2 - exit 1 -fi -write_delegate_stream "$scratch/two-delegates.jsonl" completed completed -if sanitize_turn lead oracle-two-delegates "$scratch/two-delegates.jsonl" \ - "$scratch/two-delegates.json"; then - printf 'runtime oracle: two delegate calls in one turn were accepted\n' >&2 - exit 1 -fi - -accepted_receipt='{"schema":"mnemon.agent.receipt","version":1,"outcome":"accepted","replayed":false}' -rejected_receipt='{"schema":"mnemon.agent.receipt","version":1,"outcome":"rejected","replayed":false,"diagnostic":"bounded correction required"}' -closed_denial='{"code":"context_required","message":"a bounded View is required","operation_id":null,"replayed":false,"retryable":false,"schema_version":1,"status":"error"}' -write_native_submit_stream "$scratch/native-submit.jsonl" "$accepted_receipt" -sanitize_turn lead oracle-native-submit "$scratch/native-submit.jsonl" \ - "$scratch/native-submit.json" -jq -e ' - .bash_calls == 0 and .submit_attempts == 1 and .intent_submits == 1 and - .accepted_receipts == 1 and .rejected_receipts == 0 -' "$scratch/native-submit.json" >/dev/null -native_partial=$(summarize_partial_turn "$scratch/native-submit.jsonl") -jq -e ' - .submit_command_occurrences == 1 and .submit_ends == 1 and - .submit_command_cardinality == {"1":1} and .accepted_receipts == 1 -' <<<"$native_partial" >/dev/null -jq -s -c '.[0], .[1], .[2], .[4], .[3], .[5:][]' \ - "$scratch/native-submit.jsonl" >"$scratch/native-receipt-before-submit.jsonl" -if sanitize_turn lead oracle-native-receipt-before-submit \ - "$scratch/native-receipt-before-submit.jsonl" \ - "$scratch/native-receipt-before-submit.json"; then - printf 'runtime oracle: a receipt preceding its submit start was accepted\n' >&2 - exit 1 -fi -jq -c 'if .type == "tool_execution_end" and .toolCallId == "native-submit" then - .toolName = "bash" else . end' "$scratch/native-submit.jsonl" \ - >"$scratch/native-submit-mismatched-surface.jsonl" -if sanitize_turn lead oracle-native-submit-mismatched-surface \ - "$scratch/native-submit-mismatched-surface.jsonl" \ - "$scratch/native-submit-mismatched-surface.json"; then - printf 'runtime oracle: a submit end from another tool surface was accepted\n' >&2 - exit 1 -fi -jq -c --arg text "$root_view" ' - if .type == "tool_execution_start" and .toolCallId == "native-submit" then - {type:"tool_execution_start",toolCallId:"native-then-shell-current",toolName:"bash", - args:{command:"mnemon-harness agent current --json >/dev/null; printf ignored"}}, - {type:"tool_execution_end",toolCallId:"native-then-shell-current",toolName:"bash", - isError:false,result:{content:[{type:"text",text:$text}],details:{output:$text}}}, - . - else . end -' "$scratch/native-submit.jsonl" >"$scratch/native-then-shell-submit.jsonl" -if sanitize_turn lead oracle-native-then-shell-submit \ - "$scratch/native-then-shell-submit.jsonl" \ - "$scratch/native-then-shell-submit.json"; then - printf 'runtime oracle: an Effect used a stale native View after shell Current\n' >&2 - exit 1 -fi -jq -c --arg reason "$current_failed_reason" ' - if .type == "tool_execution_start" and .toolCallId == "native-submit" then - {type:"tool_execution_start",toolCallId:"native-current-after-view", - toolName:"mnemond_current",args:{}}, - {type:"tool_execution_end",toolCallId:"native-current-after-view", - toolName:"mnemond_current",isError:true, - result:{content:[{type:"text",text:$reason}], - details:{schema:"mnemon.pi.current",version:1,status:"failed"}}}, - . - else . end -' "$scratch/native-submit.jsonl" >"$scratch/native-failed-before-submit.jsonl" -if sanitize_turn lead oracle-native-failed-before-submit \ - "$scratch/native-failed-before-submit.jsonl" \ - "$scratch/native-failed-before-submit.json"; then - printf 'runtime oracle: an Effect used a View preceding a failed Current\n' >&2 - exit 1 -fi -jq -s -c '.[0], .[3], .[4], .[1], .[2], .[5:][]' \ - "$scratch/native-submit.jsonl" >"$scratch/native-submit-before-current.jsonl" -if sanitize_turn lead oracle-native-submit-before-current \ - "$scratch/native-submit-before-current.jsonl" \ - "$scratch/native-submit-before-current.json"; then - printf 'runtime oracle: an accepted Effect preceding its trusted View was accepted\n' >&2 - exit 1 -fi -write_submit_stream "$scratch/accounted.jsonl" "$accepted_receipt" "$closed_denial" -sanitize_turn lead oracle-accounted "$scratch/accounted.jsonl" "$scratch/accounted.json" -jq -e ' - .submit_attempts == 2 and .intent_submits == 1 and - .accepted_receipts == 1 and .rejected_receipts == 0 and - .submit_denials == 1 and .post_accept_denials == 1 and - .submit_control_denials == [{code:"context_required",count:1}] -' "$scratch/accounted.json" >/dev/null -jq -s -c '.[0], .[3], .[4], .[1], .[2], .[5:][]' \ - "$scratch/accounted.jsonl" >"$scratch/shell-submit-before-current.jsonl" -if sanitize_turn lead oracle-shell-submit-before-current \ - "$scratch/shell-submit-before-current.jsonl" \ - "$scratch/shell-submit-before-current.json"; then - printf 'runtime oracle: a shell Effect preceding its trusted View was accepted\n' >&2 - exit 1 -fi -write_submit_stream "$scratch/multiline-submit.jsonl" "$accepted_receipt" -jq -c 'if .type == "tool_execution_start" and .toolCallId == "submit-1" then - .args.command = "mnemon-harness artifact capture --json \u003c evidence.json\nmnemon-harness agent submit --json" - else . end' "$scratch/multiline-submit.jsonl" >"$scratch/multiline-submit.tmp" -mv "$scratch/multiline-submit.tmp" "$scratch/multiline-submit.jsonl" -sanitize_turn lead oracle-multiline-submit "$scratch/multiline-submit.jsonl" \ - "$scratch/multiline-submit.json" -jq -e '.submit_attempts == 1 and .accepted_receipts == 1' \ - "$scratch/multiline-submit.json" >/dev/null -if grep -F 'a bounded View is required' "$scratch/accounted.json" >/dev/null; then - printf 'runtime oracle: sanitized CLI denial retained diagnostic text\n' >&2 - exit 1 -fi - -write_submit_stream "$scratch/duplicate-rendering.jsonl" \ - "$closed_denial"$'\n'"$closed_denial" -sanitize_turn lead oracle-duplicate-rendering "$scratch/duplicate-rendering.jsonl" \ - "$scratch/duplicate-rendering.json" -jq -e ' - .submit_attempts == 1 and .intent_submits == 0 and .submit_denials == 1 and - .submit_control_denials == [{code:"context_required",count:1}] -' "$scratch/duplicate-rendering.json" >/dev/null - -write_sequential_submit_stream "$scratch/sequential-denials.jsonl" 3 \ - "$closed_denial" "$closed_denial" "$closed_denial" -sanitize_turn lead oracle-sequential-denials "$scratch/sequential-denials.jsonl" \ - "$scratch/sequential-denials.json" -jq -e ' - .bash_calls == 2 and .submit_attempts == 1 and .intent_submits == 0 and - .submit_denials == 1 and .submit_invocation_failures == 0 -' "$scratch/sequential-denials.json" >/dev/null - -write_sequential_submit_stream "$scratch/sequential-rejections.jsonl" 3 \ - "$rejected_receipt" "$rejected_receipt" "$rejected_receipt" -sanitize_turn lead oracle-sequential-rejections "$scratch/sequential-rejections.jsonl" \ - "$scratch/sequential-rejections.json" -jq -e ' - .bash_calls == 2 and .submit_attempts == 1 and .intent_submits == 1 and - .rejected_receipts == 1 and .submit_denials == 0 and - .submit_invocation_failures == 0 -' "$scratch/sequential-rejections.json" >/dev/null - -write_sequential_submit_stream "$scratch/sequential-repair.jsonl" 3 \ - "$closed_denial" "$closed_denial" "$accepted_receipt" -sanitize_turn lead oracle-sequential-repair "$scratch/sequential-repair.jsonl" \ - "$scratch/sequential-repair.json" -jq -e ' - .bash_calls == 2 and .submit_attempts == 1 and .intent_submits == 1 and - .accepted_receipts == 1 and .submit_denials == 0 and - .submit_invocation_failures == 0 -' "$scratch/sequential-repair.json" >/dev/null - -write_sequential_submit_stream "$scratch/duplicate-accepted.jsonl" 2 \ - "$accepted_receipt" "$accepted_receipt" -if sanitize_turn lead oracle-duplicate-accepted "$scratch/duplicate-accepted.jsonl" \ - "$scratch/duplicate-accepted.json"; then - printf 'runtime oracle: two accepted Receipts in one tool result were accepted\n' >&2 - exit 1 -fi - -write_submit_stream "$scratch/repaired-operation.jsonl" \ - "$closed_denial"$'\n'"$accepted_receipt" -sanitize_turn lead oracle-repaired-operation "$scratch/repaired-operation.jsonl" \ - "$scratch/repaired-operation.json" -jq -e ' - .submit_attempts == 1 and .intent_submits == 1 and - .accepted_receipts == 1 and .rejected_receipts == 0 and .submit_denials == 0 -' "$scratch/repaired-operation.json" >/dev/null - -contained=("$accepted_receipt") -for _ in $(seq 1 13); do contained+=("$closed_denial"); done -write_submit_stream "$scratch/contained.jsonl" "${contained[@]}" -sanitize_turn lead oracle-contained "$scratch/contained.jsonl" "$scratch/contained.json" -jq -e ' - .submit_attempts == 14 and .intent_submits == 1 and - .accepted_receipts == 1 and .rejected_receipts == 0 and - .submit_denials == 13 and .post_accept_denials == 13 and - .submit_control_denials == [{code:"context_required",count:13}] -' "$scratch/contained.json" >/dev/null - -write_submit_stream "$scratch/unaccounted.jsonl" 'not a closed protocol result' -if sanitize_turn lead oracle-unaccounted "$scratch/unaccounted.jsonl" \ - "$scratch/unaccounted.json"; then - printf 'runtime oracle: unaccounted submit attempt was accepted\n' >&2 - exit 1 -fi - -write_submit_stream "$scratch/local-failure.jsonl" '{"status":"error"}' -sanitize_turn lead oracle-local-failure "$scratch/local-failure.jsonl" \ - "$scratch/local-failure.json" -jq -e ' - .submit_attempts == 1 and .intent_submits == 0 and - .submit_denials == 0 and .submit_invocation_failures == 1 -' "$scratch/local-failure.json" >/dev/null - -write_submit_stream "$scratch/two-effects.jsonl" "$accepted_receipt" "$accepted_receipt" -if sanitize_turn lead oracle-two-effects "$scratch/two-effects.jsonl" \ - "$scratch/two-effects.json"; then - printf 'runtime oracle: two accepted Effects in one turn were accepted\n' >&2 - exit 1 -fi - -late_pipeline() { - sh -c 'sleep 3; printf '\''{"type":"agent_end"}\n'\''; : >"$1"' late "$scratch/late" | - jq -c . -} -if with_deadline 1 "$scratch/timeout" late_pipeline >"$scratch/timeout.jsonl" 2>/dev/null; then - printf 'runtime oracle: timed pipeline returned success\n' >&2 - exit 1 -else - deadline_status=$? -fi -test "$deadline_status" = 124 -test -f "$scratch/timeout" -sleep 3 -test ! -e "$scratch/late" - -printf 'r7 domain ops Runtime boundary oracle: PASS\n' diff --git a/harness/test/r7/domainops/run_world.sh b/harness/test/r7/domainops/run_world.sh index 87ff4b11..e7ca0d59 100755 --- a/harness/test/r7/domainops/run_world.sh +++ b/harness/test/r7/domainops/run_world.sh @@ -95,7 +95,9 @@ for report in edge payment platform data lead; do done jq -e --slurpfile incident "$runtime_dir/incident-a.json" ' - .role == "edge" and .result.limit == 32 and + .role == "edge" and + (.result.limit | type == "number") and + .result.limit >= (.result.entries | length) and .result.limit <= 256 and (.result.entries | length) == 4 and all(.result.entries[]; .route == "east" and .status == "succeeded" and .capture_id > 0) and diff --git a/harness/test/r7/runner/run_case_data_only.sh b/harness/test/r7/runner/run_case_data_only.sh deleted file mode 100755 index 46b20f33..00000000 --- a/harness/test/r7/runner/run_case_data_only.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -RUNNER_DIR=$(cd "$(dirname "$0")" && pwd -P) -# shellcheck source=static_lib.sh -source "$RUNNER_DIR/static_lib.sh" - -cases_root="$R7_STATIC_HARNESS_ROOT/testdata/r7/cases" -examples_root="$R7_STATIC_HARNESS_ROOT/testdata/r7/examples" -pattern=$(r7_static_forbidden_case_pattern) - -for name in review contract-net blackboard; do - directory="$cases_root/$name" - test -d "$directory" || r7_static_fail "case directory is missing: $name" - test -s "$directory/nodes.txt" || r7_static_fail "$name/nodes.txt is absent or empty" - test -s "$directory/playbook.md" || r7_static_fail "$name/playbook.md is absent or empty" - test -x "$directory/oracle.sh" || r7_static_fail "$name/oracle.sh is not executable" -done - -if find "$examples_root" -type f -perm -111 -print | grep -q .; then - r7_static_fail 'an R7 example is executable' -fi -example_files=$(find "$examples_root" -type f -print) -if r7_static_search_files_i "$pattern" "$example_files" | grep -q .; then - r7_static_fail 'an R7 example contains case behavior' -fi -runner_files=$(printf '%s\n' "$RUNNER_DIR/lib.sh" "$RUNNER_DIR/run_cases.sh") -if r7_static_search_files_i 'testdata/r7/examples|examples/' "$runner_files" | grep -q .; then - r7_static_fail 'the case runner reads non-authoritative examples' -fi -if r7_static_search_files_i '(review|contract-net|blackboard)' "$runner_files" | grep -q .; then - r7_static_fail 'the generic case runner contains case-specific behavior' -fi - -while IFS= read -r executable; do - case "$executable" in - "$cases_root"/*) ;; - *) r7_static_fail "executable R7 fixture is outside cases/: $executable" ;; - esac -done < <(find "$R7_STATIC_HARNESS_ROOT/testdata/r7" -type f -perm -111 -print) - -printf 'r7 static oracle passed: case behavior is data-only\n' diff --git a/harness/test/r7/runner/run_no_case_kind.sh b/harness/test/r7/runner/run_no_case_kind.sh deleted file mode 100755 index 99870533..00000000 --- a/harness/test/r7/runner/run_no_case_kind.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -RUNNER_DIR=$(cd "$(dirname "$0")" && pwd -P) -# shellcheck source=static_lib.sh -source "$RUNNER_DIR/static_lib.sh" - -pattern=$(r7_static_forbidden_case_pattern) -sources=$(r7_static_production_sources "$R7_STATIC_HARNESS_ROOT") -test -n "$sources" || r7_static_fail 'R7 production source set is empty' - -if ( - cd "$R7_STATIC_HARNESS_ROOT" - # The closed gate forbids case-specific semantic kind literals. Ordinary - # prose is not a dispatch table, so this scans the literal namespaces rather - # than banning English words from comments and diagnostics. - r7_static_search_files_i "$pattern" "$sources" | grep -q . -); then - r7_static_fail 'case-specific semantic kind appears in production R7 Go' -fi - -printf 'r7 static oracle passed: no production case-specific kind\n' diff --git a/harness/test/r7/runner/run_no_managed_wake.sh b/harness/test/r7/runner/run_no_managed_wake.sh deleted file mode 100755 index 4dc5dbdd..00000000 --- a/harness/test/r7/runner/run_no_managed_wake.sh +++ /dev/null @@ -1,96 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -RUNNER_DIR=$(cd "$(dirname "$0")" && pwd -P) -# shellcheck source=static_lib.sh -source "$RUNNER_DIR/static_lib.sh" - -count_lines() { - if test -z "$1"; then - printf '0\n' - return - fi - printf '%s\n' "$1" | wc -l | tr -d ' ' -} - -require_one() { - local matches=$1 description=$2 - if test "$(count_lines "$matches")" != 1; then - r7_static_fail "$description is not the one frozen interactive surface" - fi -} - -root=$R7_STATIC_HARNESS_ROOT -sources=$(r7_static_production_sources "$root") -test -n "$sources" || r7_static_fail 'R7 production source set is empty' - -# Attachment authority has one issuer, and the only production caller is the -# local daemon's interactive attachment service. This checks the issuance API -# rather than banning generic words such as "wake" or "managed" from prose. -issuer_declarations=$( - cd "$root" - files=$(find internal/authority -type f -name '*.go' ! -name '*_test.go' -print) - r7_static_search_files '^func \(s \*Store\) Issue[A-Za-z0-9_]*Attachment\(' "$files" -) -require_one "$issuer_declarations" 'Attachment issuer declaration' -case "$issuer_declarations" in - internal/authority/attachment_begin.go:*'IssueInteractiveAttachment('*) ;; - *) r7_static_fail 'Attachment issuer is not IssueInteractiveAttachment' ;; -esac - -issuer_calls=$( - cd "$root" - r7_static_search_files '\.Issue[A-Za-z0-9_]*Attachment\(' "$sources" -) -require_one "$issuer_calls" 'Attachment issuer call' -case "$issuer_calls" in - internal/daemon/service.go:*'service.authority.IssueInteractiveAttachment(ctx, service.principal, boundary)'*) ;; - *) r7_static_fail 'Attachment issuance escaped the interactive daemon service' ;; -esac - -# The CLI reaches that service through one exact hook command. Additional -# command aliases or another call to runAttach/client.Attach fail this oracle. -hook_case=$(cd "$root" && r7_static_search_files '^[[:space:]]case "hook\\x00attach":$' internal/cli/app.go) -require_one "$hook_case" 'hook attach command mapping' -attach_returns=$(cd "$root" && r7_static_search_files '^[[:space:]]+return commandAttach$' internal/cli/app.go) -require_one "$attach_returns" 'hook attach command result' -run_attach_calls=$(cd "$root" && files=$(find internal/cli -type f -name '*.go' ! -name '*_test.go' -print) && \ - r7_static_search_files 'return app\.runAttach\(' "$files") -require_one "$run_attach_calls" 'CLI runAttach dispatch' -client_attach_calls=$(cd "$root" && files=$(find internal/cli -type f -name '*.go' ! -name '*_test.go' -print) && \ - r7_static_search_files 'client\.Attach\(ctx, boundary\)' "$files") -require_one "$client_attach_calls" 'CLI attachment request' - -# The daemon exposes one attachment route, and that route calls only the -# boundary-digest-bound interactive service. A second route alias or background caller is -# therefore a visible protocol change rather than an implicit wake path. -attach_handlers=$(cd "$root" && files=$(find internal/daemon -type f -name '*.go' ! -name '*_test.go' -print) && \ - r7_static_search_files 'mux\.HandleFunc\([^,]+, server\.handleAttach\)' "$files") -require_one "$attach_handlers" 'daemon attachment route' -case "$attach_handlers" in - internal/daemon/control.go:*'mux.HandleFunc(routeAttachments, server.handleAttach)'*) ;; - *) r7_static_fail 'daemon attachment handler is not bound to the frozen route' ;; -esac -service_attach_calls=$(cd "$root" && files=$(find internal/daemon -type f -name '*.go' ! -name '*_test.go' -print) && \ - r7_static_search_files 'server\.service\.attach\(request\.Context\(\), boundary\)' "$files") -require_one "$service_attach_calls" 'daemon interactive attachment call' - -attachment_fields() { - awk ' - /^type attachmentWire struct \{$/ { inside = 1; next } - inside && /^}$/ { exit } - inside && match($0, /json:"[^"]+"/) { - print substr($0, RSTART + 6, RLENGTH - 7) - } - ' "$1" -} - -expected_fields=$(printf '%s\n' attachment credential expires_at schema version) -for wire in "$root/internal/daemon/control_wire.go" "$root/internal/cli/control_client.go"; do - if test "$(attachment_fields "$wire")" != "$expected_fields"; then - r7_static_fail "attachment schema changed outside the frozen interactive surface: $wire" - fi -done - -printf 'r7 static oracle passed: no managed attachment issuance surface\n' diff --git a/harness/test/r7/runner/run_pattern_free.sh b/harness/test/r7/runner/run_pattern_free.sh deleted file mode 100755 index b237017d..00000000 --- a/harness/test/r7/runner/run_pattern_free.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -RUNNER_DIR=$(cd "$(dirname "$0")" && pwd -P) -# shellcheck source=static_lib.sh -source "$RUNNER_DIR/static_lib.sh" -trap r7_static_cleanup EXIT INT TERM - -r7_static_candidate_copy -candidate="$R7_STATIC_TMP/harness" -rm -rf -- "$candidate/testdata/r7/examples" "$candidate/testdata/r7/cases" \ - "$candidate/testdata/r7/domain-ops" "$candidate/test/r7/domainops" -r7_static_core_tests "$candidate" - -printf 'r7 static oracle passed: Core is pattern-free after deleting examples and cases\n' diff --git a/harness/test/r7/runner/run_static.sh b/harness/test/r7/runner/run_static.sh deleted file mode 100755 index fd69033e..00000000 --- a/harness/test/r7/runner/run_static.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -RUNNER_DIR=$(cd "$(dirname "$0")" && pwd -P) - -"$RUNNER_DIR/run_no_case_kind.sh" -"$RUNNER_DIR/run_no_managed_wake.sh" -"$RUNNER_DIR/run_case_data_only.sh" -"$RUNNER_DIR/run_pattern_free.sh" -"$RUNNER_DIR/run_without_selector.sh" - -printf 'r7 static oracles passed\n' diff --git a/harness/test/r7/runner/run_without_selector.sh b/harness/test/r7/runner/run_without_selector.sh deleted file mode 100755 index b9f3eba5..00000000 --- a/harness/test/r7/runner/run_without_selector.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -RUNNER_DIR=$(cd "$(dirname "$0")" && pwd -P) -# shellcheck source=static_lib.sh -source "$RUNNER_DIR/static_lib.sh" -trap r7_static_cleanup EXIT INT TERM - -r7_static_repository_copy -candidate="$R7_STATIC_TMP/repository" -test -d "$candidate/harness/internal/selector" || r7_static_fail 'selector is already absent' -rm -rf -- "$candidate/harness/internal/selector" -( - cd "$candidate/harness" - go test -count=1 ./... - go build ./cmd/mnemon-harness ./cmd/mnemond -) - -printf 'r7 static oracle passed: deleting selector leaves all R7 Go conformance operational\n' diff --git a/harness/test/r7/runner/static_lib.sh b/harness/test/r7/runner/static_lib.sh deleted file mode 100755 index c2c1b071..00000000 --- a/harness/test/r7/runner/static_lib.sh +++ /dev/null @@ -1,119 +0,0 @@ -#!/usr/bin/env bash - -# Shared mechanics for R7 structural oracles. These helpers know package and -# filesystem boundaries only; collaboration case semantics stay in testdata. - -set -euo pipefail - -R7_STATIC_RUNNER_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P) -R7_STATIC_HARNESS_ROOT=$(cd "$R7_STATIC_RUNNER_DIR/../../.." && pwd -P) -R7_STATIC_REPOSITORY_ROOT=$(cd "$R7_STATIC_HARNESS_ROOT/.." && pwd -P) -R7_STATIC_TMP= - -r7_static_fail() { - printf 'r7 static oracle: %s\n' "$*" >&2 - return 1 -} - -r7_static_cleanup() { - if test -n "${R7_STATIC_TMP:-}" && test -d "$R7_STATIC_TMP"; then - rm -rf -- "$R7_STATIC_TMP" - fi - R7_STATIC_TMP= -} - -r7_static_candidate_copy() { - r7_static_cleanup - R7_STATIC_TMP=$(mktemp -d "${TMPDIR:-/tmp}/mnemon-r7-static.XXXXXX") - mkdir "$R7_STATIC_TMP/harness" - cp -R "$R7_STATIC_HARNESS_ROOT/." "$R7_STATIC_TMP/harness" -} - -# Copy the minimum repository surface needed by every Go conformance package. -# The private Git copy lets history-bound contract tests inspect the exact -# candidate without observing or mutating the caller's repository metadata. -r7_static_repository_copy() { - local entry - - r7_static_cleanup - R7_STATIC_TMP=$(mktemp -d "${TMPDIR:-/tmp}/mnemon-r7-static.XXXXXX") - mkdir "$R7_STATIC_TMP/repository" - for entry in .git .gitignore go.mod go.sum main.go cmd internal docs harness; do - test -e "$R7_STATIC_REPOSITORY_ROOT/$entry" || \ - r7_static_fail "repository candidate input is missing: $entry" - cp -R "$R7_STATIC_REPOSITORY_ROOT/$entry" "$R7_STATIC_TMP/repository/$entry" - done -} - -r7_static_cli_package() { - local root=$1 - test -d "$root/internal/cli" || r7_static_fail 'R7 Agent terminal package is missing' - printf '%s\n' './internal/cli' -} - -r7_static_core_tests() { - local root=$1 cli - cli=$(r7_static_cli_package "$root") - ( - cd "$root" - go test -count=1 \ - ./internal/agency \ - ./internal/authority \ - ./internal/cas \ - ./internal/peerlink \ - ./internal/daemon \ - "$cli" \ - ./internal/attach \ - ./cmd/mnemon-harness \ - ./cmd/mnemond \ - ./test/r7/process - go test -count=1 ./test/contracts -run '^TestR7InternalPackageSetAllowsSelectorDeletion$' - go build ./cmd/mnemon-harness ./cmd/mnemond - ) -} - -r7_static_production_sources() { - local root=$1 cli=internal/cli - local -a roots=( - internal/agency - internal/authority - internal/cas - internal/peerlink - internal/daemon - "$cli" - internal/attach - cmd/mnemon-harness - cmd/mnemond - ) - if test -d "$root/internal/selector"; then - roots+=(internal/selector) - fi - ( - cd "$root" - find "${roots[@]}" \ - -type f -name '*.go' ! -name '*_test.go' ! -path '*/testdata/*' -print - ) -} - -# Search an explicit newline-delimited file set with the ubiquitous grep -# utility. Structural oracles must not turn a missing optional developer tool -# into a misleading semantic failure. -r7_static_search_files() { - local pattern=$1 files=$2 - test -n "$files" || return 1 - printf '%s\n' "$files" | while IFS= read -r file; do - grep -EnH -- "$pattern" "$file" || true - done -} - -r7_static_search_files_i() { - local pattern=$1 files=$2 - test -n "$files" || return 1 - printf '%s\n' "$files" | while IFS= read -r file; do - grep -EinH -- "$pattern" "$file" || true - done -} - -r7_static_forbidden_case_pattern() { - printf '%s\n' '(review\.|contract-net\.|blackboard\.|memory\.wiki\.|teamwork\.|channel\.)' -} diff --git a/harness/tools/corecontract/cmd/core-gate/main.go b/harness/tools/corecontract/cmd/core-gate/main.go deleted file mode 100644 index 29b636d8..00000000 --- a/harness/tools/corecontract/cmd/core-gate/main.go +++ /dev/null @@ -1,40 +0,0 @@ -package main - -import ( - "context" - "fmt" - "io" - "os" - "os/signal" - "syscall" - - "github.com/mnemon-dev/mnemon/harness/tools/corecontract" -) - -const usage = "usage: core-gate --root \n" - -type runGatesFunc func(context.Context, string) (string, error) - -func main() { - ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) - defer stop() - if code := run(ctx, os.Args[1:], os.Stdout, os.Stderr, corecontract.RunGates); code != 0 { - os.Exit(code) - } -} - -func run(ctx context.Context, arguments []string, stdout, stderr io.Writer, runGates runGatesFunc) int { - if len(arguments) != 2 || arguments[0] != "--root" || arguments[1] == "" { - _, _ = io.WriteString(stderr, usage) - return 2 - } - path, err := runGates(ctx, arguments[1]) - if err != nil { - fmt.Fprintf(stderr, "core-gate: %v\n", err) - return 1 - } - if _, err := fmt.Fprintln(stdout, path); err != nil { - return 1 - } - return 0 -} diff --git a/harness/tools/corecontract/cmd/core-gate/main_test.go b/harness/tools/corecontract/cmd/core-gate/main_test.go deleted file mode 100644 index 1ae3eb21..00000000 --- a/harness/tools/corecontract/cmd/core-gate/main_test.go +++ /dev/null @@ -1,44 +0,0 @@ -package main - -import ( - "bytes" - "context" - "errors" - "testing" -) - -func TestCLIHasOneRootOnlyInvocationAndPrintsOnlyReportPath(t *testing.T) { - var stdout, stderr bytes.Buffer - code := run(context.Background(), []string{"--root", ".."}, &stdout, &stderr, - func(_ context.Context, root string) (string, error) { - if root != ".." { - t.Fatalf("root = %q", root) - } - return ".testdata/r7/core-gates/run/gate-report.json", nil - }) - if code != 0 || stderr.Len() != 0 || - stdout.String() != ".testdata/r7/core-gates/run/gate-report.json\n" { - t.Fatalf("run = code %d stdout %q stderr %q", code, stdout.String(), stderr.String()) - } - for _, arguments := range [][]string{nil, {"run"}, {"--root"}, {"--root", "..", "merge"}} { - stdout.Reset() - stderr.Reset() - code = run(context.Background(), arguments, &stdout, &stderr, - func(context.Context, string) (string, error) { - t.Fatal("invalid invocation ran gates") - return "", nil - }) - if code != 2 || stdout.Len() != 0 { - t.Fatalf("invalid %v = code %d stdout %q", arguments, code, stdout.String()) - } - } -} - -func TestCLIFailureDoesNotPrintReportPath(t *testing.T) { - var stdout, stderr bytes.Buffer - code := run(context.Background(), []string{"--root", ".."}, &stdout, &stderr, - func(context.Context, string) (string, error) { return "", errors.New("failed") }) - if code != 1 || stdout.Len() != 0 || stderr.Len() == 0 { - t.Fatalf("failure = code %d stdout %q stderr %q", code, stdout.String(), stderr.String()) - } -} diff --git a/harness/tools/corecontract/contract.go b/harness/tools/corecontract/contract.go deleted file mode 100644 index a4c126dc..00000000 --- a/harness/tools/corecontract/contract.go +++ /dev/null @@ -1,165 +0,0 @@ -// Package corecontract validates and executes the tracked R7 evidence ledger. -package corecontract - -import ( - "bufio" - "bytes" - "fmt" - "os" - "path/filepath" - "regexp" - "slices" - "strings" -) - -const ( - DocumentPath = "docs/harness/r7-core-contract.md" - RegistryPath = "harness/test/contracts/r7-requirements.json" - RegistrySchemaVersion = 1 - ReportSchemaVersion = 1 - InvariantCount = 10 - GateCount = 10 -) - -type Lifecycle string - -const ( - LifecycleProposed Lifecycle = "PROPOSED" - LifecycleActive Lifecycle = "ACTIVE" - LifecycleRetired Lifecycle = "RETIRED" -) - -type Contract struct { - Lifecycle Lifecycle - Invariants []string - Gates []string -} - -var ( - statusPattern = regexp.MustCompile(`^Status: \*\*(PROPOSED|ACTIVE|RETIRED)\*\*\.`) - invariantPattern = regexp.MustCompile(`^\*\*(P-[0-9]{2})\s`) - gatePattern = regexp.MustCompile(`^\| \x60(G-R7-[A-Z-]+)\x60 \|`) -) - -var expectedInvariants = []string{ - "P-01", "P-02", "P-03", "P-04", "P-05", - "P-06", "P-07", "P-08", "P-09", "P-10", -} - -var expectedGates = []string{ - "G-R7-AUTHORITY-CUTOVER", - "G-R7-CASE-DATA-ONLY", - "G-R7-CASES", - "G-R7-CONTINUITY", - "G-R7-CORE", - "G-R7-FEDERATION", - "G-R7-NO-CASE-KIND", - "G-R7-ONE-PATH", - "G-R7-PATTERN-FREE", - "G-R7-ROOT-ISOLATION", -} - -func Load(root string) (Contract, error) { - data, err := os.ReadFile(filepath.Join(root, filepath.FromSlash(DocumentPath))) - if err != nil { - return Contract{}, fmt.Errorf("read R7 Core contract: %w", err) - } - return Parse(data) -} - -func Parse(data []byte) (Contract, error) { - var contract Contract - invariants := make(map[string]struct{}, InvariantCount) - gates := make(map[string]struct{}, GateCount) - scanner := bufio.NewScanner(bytes.NewReader(data)) - for scanner.Scan() { - line := strings.TrimSpace(strings.TrimSuffix(scanner.Text(), "\r")) - if match := statusPattern.FindStringSubmatch(line); match != nil { - if contract.Lifecycle != "" { - return Contract{}, fmt.Errorf("contract repeats lifecycle status") - } - contract.Lifecycle = Lifecycle(match[1]) - } - if match := invariantPattern.FindStringSubmatch(line); match != nil { - if _, exists := invariants[match[1]]; exists { - return Contract{}, fmt.Errorf("contract repeats invariant %s", match[1]) - } - invariants[match[1]] = struct{}{} - contract.Invariants = append(contract.Invariants, match[1]) - } - if match := gatePattern.FindStringSubmatch(line); match != nil { - if _, exists := gates[match[1]]; exists { - return Contract{}, fmt.Errorf("contract repeats gate %s", match[1]) - } - gates[match[1]] = struct{}{} - contract.Gates = append(contract.Gates, match[1]) - } - } - if err := scanner.Err(); err != nil { - return Contract{}, fmt.Errorf("scan R7 Core contract: %w", err) - } - if contract.Lifecycle == "" { - return Contract{}, fmt.Errorf("contract has no canonical lifecycle status") - } - slices.Sort(contract.Invariants) - slices.Sort(contract.Gates) - if !slices.Equal(contract.Invariants, expectedInvariants) { - return Contract{}, fmt.Errorf("contract invariants = %v, want %v", - contract.Invariants, expectedInvariants) - } - if !slices.Equal(contract.Gates, expectedGates) { - return Contract{}, fmt.Errorf("contract gates = %v, want %v", - contract.Gates, expectedGates) - } - return contract, nil -} - -func ValidateAuthorityCutover(root string) error { - files, err := filepath.Glob(filepath.Join(root, "docs", "harness", "*core-contract.md")) - if err != nil { - return fmt.Errorf("enumerate Core contracts: %w", err) - } - active := 0 - for _, file := range files { - data, err := os.ReadFile(file) - if err != nil { - return fmt.Errorf("read %s: %w", filepath.Base(file), err) - } - lifecycle, err := parseLifecycle(data) - if err != nil { - return fmt.Errorf("%s: %w", filepath.Base(file), err) - } - if lifecycle == LifecycleActive { - active++ - } - } - if active != 1 { - return fmt.Errorf("tracked Core contracts have %d ACTIVE markers, want exactly one", active) - } - contract, err := Load(root) - if err != nil { - return err - } - if contract.Lifecycle != LifecycleActive { - return fmt.Errorf("R7 Core lifecycle = %s, want ACTIVE", contract.Lifecycle) - } - return nil -} - -func parseLifecycle(data []byte) (Lifecycle, error) { - var lifecycle Lifecycle - for _, raw := range bytes.Split(data, []byte("\n")) { - match := statusPattern.FindStringSubmatch(strings.TrimSpace(string(raw))) - if match == nil { - continue - } - if lifecycle != "" { - return "", fmt.Errorf("repeats lifecycle status") - } - lifecycle = Lifecycle(match[1]) - } - if lifecycle == "" { - return "", fmt.Errorf("missing lifecycle status") - } - return lifecycle, nil -} diff --git a/harness/tools/corecontract/contract_test.go b/harness/tools/corecontract/contract_test.go deleted file mode 100644 index 091e9d50..00000000 --- a/harness/tools/corecontract/contract_test.go +++ /dev/null @@ -1,45 +0,0 @@ -package corecontract - -import ( - "os" - "path/filepath" - "strings" - "testing" -) - -func TestTrackedR7ContractHasClosedLifecycleAndIDs(t *testing.T) { - root := filepath.Clean("../../..") - contract, err := Load(root) - if err != nil { - t.Fatal(err) - } - if contract.Lifecycle != LifecycleActive || len(contract.Invariants) != InvariantCount || - len(contract.Gates) != GateCount { - t.Fatalf("contract = lifecycle %s invariants %v gates %v", - contract.Lifecycle, contract.Invariants, contract.Gates) - } -} - -func TestContractParserAcceptsOnlyOneKnownLifecycleAndClosedLists(t *testing.T) { - data, err := os.ReadFile(filepath.Join("../../..", filepath.FromSlash(DocumentPath))) - if err != nil { - t.Fatal(err) - } - for _, lifecycle := range []Lifecycle{LifecycleProposed, LifecycleActive, LifecycleRetired} { - changed := strings.Replace(string(data), "Status: **ACTIVE**.", - "Status: **"+string(lifecycle)+"**.", 1) - contract, err := Parse([]byte(changed)) - if err != nil || contract.Lifecycle != lifecycle { - t.Fatalf("parse %s = %+v, %v", lifecycle, contract, err) - } - } - duplicate := strings.Replace(string(data), "**P-01 Admission owns facts.**", - "**P-01 Admission owns facts.**\n**P-01 Duplicate.**", 1) - if _, err := Parse([]byte(duplicate)); err == nil || !strings.Contains(err.Error(), "repeats invariant") { - t.Fatalf("duplicate invariant error = %v", err) - } - missing := strings.Replace(string(data), "Status: **ACTIVE**.", "Status: active.", 1) - if _, err := Parse([]byte(missing)); err == nil || !strings.Contains(err.Error(), "no canonical") { - t.Fatalf("missing status error = %v", err) - } -} diff --git a/harness/tools/corecontract/gate.go b/harness/tools/corecontract/gate.go deleted file mode 100644 index dce015b4..00000000 --- a/harness/tools/corecontract/gate.go +++ /dev/null @@ -1,179 +0,0 @@ -package corecontract - -import ( - "context" - "fmt" - "os" - "path/filepath" - "time" -) - -type preparedRun struct { - root string - base string - contract Contract - registry Registry - report GateReport -} - -func RunGates(ctx context.Context, root string) (string, error) { - run, err := prepareRun(root) - if err != nil { - return "", err - } - if err := run.execute(ctx); err != nil { - return "", err - } - return run.finish() -} - -func prepareRun(root string) (preparedRun, error) { - root, err := canonicalRepositoryRoot(root) - if err != nil { - return preparedRun{}, err - } - if err := requireClean(root, "R7 gate requires a clean worktree"); err != nil { - return preparedRun{}, err - } - contract, registry, err := loadGateInputs(root) - if err != nil { - return preparedRun{}, err - } - report, base, err := newReport(root) - if err != nil { - return preparedRun{}, err - } - return preparedRun{root: root, base: base, contract: contract, - registry: registry, report: report}, nil -} - -func loadGateInputs(root string) (Contract, Registry, error) { - contract, err := Load(root) - if err != nil { - return Contract{}, Registry{}, err - } - registry, err := LoadRegistry(root) - if err != nil { - return Contract{}, Registry{}, err - } - if err := ValidateBindings(root, contract, registry); err != nil { - return Contract{}, Registry{}, err - } - if err := ValidateAuthorityCutover(root); err != nil { - return Contract{}, Registry{}, err - } - return contract, registry, nil -} - -func newReport(root string) (GateReport, string, error) { - commit, err := gitValue(root, "rev-parse", "HEAD") - if err != nil { - return GateReport{}, "", err - } - tree, err := gitValue(root, "rev-parse", "HEAD^{tree}") - if err != nil { - return GateReport{}, "", err - } - started := time.Now().UTC() - runID := started.Format("20060102T150405.000000000Z") + "-" + tree[:12] - base := filepath.ToSlash(filepath.Join(".testdata", "r7", "core-gates", runID)) - if err := prepareReportDirectory(root, base); err != nil { - return GateReport{}, "", err - } - report := GateReport{SchemaVersion: ReportSchemaVersion, RunID: runID, - StartedAt: started.Format(time.RFC3339Nano), - Source: ReportSource{Commit: commit, Tree: tree, CleanAtStart: true}, - Steps: []StepResult{}, Gates: []GateResult{}} - if report.Inputs.ContractSHA256, err = fileSHA256( - filepath.Join(root, filepath.FromSlash(DocumentPath))); err != nil { - return GateReport{}, "", err - } - if report.Inputs.RegistrySHA256, err = fileSHA256( - filepath.Join(root, filepath.FromSlash(RegistryPath))); err != nil { - return GateReport{}, "", err - } - return report, base, nil -} - -func prepareReportDirectory(root, base string) error { - if err := ensureIgnored(root, base); err != nil { - return err - } - if err := os.MkdirAll(filepath.Join(root, filepath.FromSlash(base)), 0o700); err != nil { - return fmt.Errorf("create report directory: %w", err) - } - return nil -} - -func (run *preparedRun) execute(ctx context.Context) error { - for _, step := range uniqueSteps(run.registry) { - result, err := runStep(ctx, run.root, run.base, step) - if err != nil { - return err - } - run.report.Steps = append(run.report.Steps, result) - } - if err := requireInvariantPasses(run.registry, run.report.Steps); err != nil { - return err - } - run.report.Gates = closedGateResults(run.registry) - return nil -} - -func requireInvariantPasses(registry Registry, steps []StepResult) error { - passed := make(map[string]struct{}) - for _, step := range steps { - for _, test := range step.PassedTests { - passed[test] = struct{}{} - } - } - for _, test := range invariantTests(registry) { - if _, found := passed[test]; !found { - return fmt.Errorf("bound invariant test %s was not observed passing", test) - } - } - return nil -} - -func closedGateResults(registry Registry) []GateResult { - results := make([]GateResult, 0, len(registry.Gates)) - for _, gate := range registry.Gates { - ids := make([]string, len(gate.Steps)) - for index, step := range gate.Steps { - ids[index] = step.ID - } - results = append(results, GateResult{ID: gate.ID, StepIDs: ids, Passed: true}) - } - return results -} - -func (run *preparedRun) finish() (string, error) { - if err := requireClean(run.root, "worktree changed while R7 gates ran"); err != nil { - return "", err - } - run.report.Source.CleanAtFinish = true - run.report.FinishedAt = time.Now().UTC().Format(time.RFC3339Nano) - if err := ValidateReport(run.root, run.contract, run.registry, run.report); err != nil { - return "", fmt.Errorf("validate generated report: %w", err) - } - relative := filepath.ToSlash(filepath.Join(run.base, "gate-report.json")) - data, err := canonicalJSON(run.report) - if err != nil { - return "", err - } - if err := os.WriteFile(filepath.Join(run.root, filepath.FromSlash(relative)), data, 0o600); err != nil { - return "", fmt.Errorf("write gate report: %w", err) - } - return relative, nil -} - -func requireClean(root, message string) error { - clean, err := worktreeClean(root) - if err != nil { - return err - } - if !clean { - return fmt.Errorf("%s", message) - } - return nil -} diff --git a/harness/tools/corecontract/registry.go b/harness/tools/corecontract/registry.go deleted file mode 100644 index 90e3bf77..00000000 --- a/harness/tools/corecontract/registry.go +++ /dev/null @@ -1,301 +0,0 @@ -package corecontract - -import ( - "bytes" - "encoding/json" - "fmt" - "go/ast" - "go/parser" - "go/token" - "io" - "os" - "path/filepath" - "regexp" - "slices" - "strings" -) - -type Registry struct { - SchemaVersion int `json:"schema_version"` - Invariants []InvariantBinding `json:"invariants"` - Gates []GateBinding `json:"gates"` -} - -type InvariantBinding struct { - ID string `json:"id"` - Oracles []InvariantOracle `json:"oracles"` -} - -type InvariantOracle struct { - ID string `json:"id"` - Test string `json:"test"` -} - -type GateBinding struct { - ID string `json:"id"` - Steps []GateStep `json:"steps"` -} - -type GateStep struct { - ID string `json:"id"` - Kind string `json:"kind"` - Argv []string `json:"argv"` - Oracles []string `json:"oracles"` -} - -var ( - oracleIDPattern = regexp.MustCompile(`^[a-z0-9][a-z0-9-]{0,95}$`) - stepIDPattern = regexp.MustCompile(`^[a-z0-9][a-z0-9-]{0,63}$`) - testNamePattern = regexp.MustCompile(`^Test[A-Za-z0-9_]+$`) -) - -func LoadRegistry(root string) (Registry, error) { - path := filepath.Join(root, filepath.FromSlash(RegistryPath)) - data, err := os.ReadFile(path) - if err != nil { - return Registry{}, fmt.Errorf("read R7 requirements registry: %w", err) - } - registry, err := DecodeRegistry(data) - if err != nil { - return Registry{}, fmt.Errorf("decode R7 requirements registry: %w", err) - } - canonical, err := canonicalJSON(registry) - if err != nil { - return Registry{}, err - } - if !bytes.Equal(data, canonical) { - return Registry{}, fmt.Errorf("R7 requirements registry is not canonical JSON") - } - return registry, nil -} - -func DecodeRegistry(data []byte) (Registry, error) { - decoder := json.NewDecoder(bytes.NewReader(data)) - decoder.DisallowUnknownFields() - var registry Registry - if err := decoder.Decode(®istry); err != nil { - return Registry{}, err - } - var extra any - if err := decoder.Decode(&extra); err != io.EOF { - if err == nil { - return Registry{}, fmt.Errorf("multiple JSON values") - } - return Registry{}, err - } - return registry, nil -} - -func ValidateBindings(root string, contract Contract, registry Registry) error { - if registry.SchemaVersion != RegistrySchemaVersion { - return fmt.Errorf("registry schema_version = %d, want %d", - registry.SchemaVersion, RegistrySchemaVersion) - } - if registry.Invariants == nil || registry.Gates == nil { - return fmt.Errorf("registry closed lists must be non-null") - } - if len(registry.Invariants) != InvariantCount || len(registry.Gates) != GateCount { - return fmt.Errorf("registry has %d invariants and %d gates, want %d and %d", - len(registry.Invariants), len(registry.Gates), InvariantCount, GateCount) - } - if err := validateInvariantBindings(root, contract, registry.Invariants); err != nil { - return err - } - if err := validateGateBindings(contract, registry.Gates); err != nil { - return err - } - return validateInvariantExecutionCoverage(registry) -} - -func validateInvariantExecutionCoverage(registry Registry) error { - bound := make(map[string]struct{}) - for _, gate := range registry.Gates { - for _, step := range gate.Steps { - if step.Kind != "go-test" { - continue - } - for _, oracle := range step.Oracles { - bound[strings.TrimPrefix(oracle, "test:")] = struct{}{} - } - } - } - for _, test := range invariantTests(registry) { - if _, found := bound[test]; !found { - return fmt.Errorf("invariant test %s is not bound to any gate step", test) - } - } - return nil -} - -func validateInvariantBindings(root string, contract Contract, bindings []InvariantBinding) error { - ids := make([]string, len(bindings)) - for index, binding := range bindings { - ids[index] = binding.ID - if binding.Oracles == nil || len(binding.Oracles) == 0 { - return fmt.Errorf("invariant %s has no non-null oracle binding", binding.ID) - } - previous := "" - for _, oracle := range binding.Oracles { - if !oracleIDPattern.MatchString(oracle.ID) { - return fmt.Errorf("invariant %s has invalid oracle ID %q", binding.ID, oracle.ID) - } - if previous != "" && oracle.ID <= previous { - return fmt.Errorf("invariant %s oracles are not sorted and unique", binding.ID) - } - previous = oracle.ID - if err := validateTestReference(root, oracle.Test); err != nil { - return fmt.Errorf("invariant %s oracle %s: %w", binding.ID, oracle.ID, err) - } - } - } - if !slices.Equal(ids, contract.Invariants) { - return fmt.Errorf("registry invariant IDs = %v, want exact contract list %v", - ids, contract.Invariants) - } - return nil -} - -func validateGateBindings(contract Contract, bindings []GateBinding) error { - ids := make([]string, len(bindings)) - steps := make(map[string]GateStep) - for index, binding := range bindings { - ids[index] = binding.ID - if binding.Steps == nil || len(binding.Steps) == 0 { - return fmt.Errorf("gate %s has no non-null steps", binding.ID) - } - previous := "" - for _, step := range binding.Steps { - if !stepIDPattern.MatchString(step.ID) || (previous != "" && step.ID <= previous) { - return fmt.Errorf("gate %s step IDs are invalid or not sorted and unique", binding.ID) - } - previous = step.ID - if err := validateStep(step); err != nil { - return fmt.Errorf("gate %s step %s: %w", binding.ID, step.ID, err) - } - if existing, found := steps[step.ID]; found && !stepsEqual(existing, step) { - return fmt.Errorf("shared step %s differs between gates", step.ID) - } - steps[step.ID] = step - } - } - if !slices.Equal(ids, contract.Gates) { - return fmt.Errorf("registry gate IDs = %v, want exact contract list %v", ids, contract.Gates) - } - return nil -} - -func validateStep(step GateStep) error { - if step.Kind != "go-test" && step.Kind != "shell" { - return fmt.Errorf("unknown kind %q", step.Kind) - } - if step.Argv == nil || len(step.Argv) == 0 || step.Oracles == nil || len(step.Oracles) == 0 { - return fmt.Errorf("argv and oracles must be non-null and non-empty") - } - for _, argument := range step.Argv { - if argument == "" || strings.ContainsRune(argument, '\x00') { - return fmt.Errorf("argv contains an empty or NUL argument") - } - } - previous := "" - for _, oracle := range step.Oracles { - if oracle == "" || strings.ContainsAny(oracle, "\r\n") || - (previous != "" && oracle <= previous) { - return fmt.Errorf("oracles are invalid or not sorted and unique") - } - prefix := "stdout:" - if step.Kind == "go-test" { - prefix = "test:" - } - if !strings.HasPrefix(oracle, prefix) || len(oracle) == len(prefix) { - return fmt.Errorf("oracle %q does not match %s step", oracle, step.Kind) - } - previous = oracle - } - if step.Kind == "go-test" && !slices.Contains(step.Argv, "-json") { - return fmt.Errorf("go-test argv does not contain -json") - } - return nil -} - -func stepsEqual(left, right GateStep) bool { - return left.ID == right.ID && left.Kind == right.Kind && - slices.Equal(left.Argv, right.Argv) && slices.Equal(left.Oracles, right.Oracles) -} - -func invariantTests(registry Registry) []string { - set := make(map[string]struct{}) - for _, invariant := range registry.Invariants { - for _, oracle := range invariant.Oracles { - set[oracle.Test] = struct{}{} - } - } - tests := make([]string, 0, len(set)) - for test := range set { - tests = append(tests, test) - } - slices.Sort(tests) - return tests -} - -func uniqueSteps(registry Registry) []GateStep { - byID := make(map[string]GateStep) - for _, gate := range registry.Gates { - for _, step := range gate.Steps { - byID[step.ID] = step - } - } - steps := make([]GateStep, 0, len(byID)) - for _, step := range byID { - steps = append(steps, step) - } - slices.SortFunc(steps, func(left, right GateStep) int { - return strings.Compare(left.ID, right.ID) - }) - return steps -} - -func validateTestReference(root, reference string) error { - packagePath, symbol, ok := strings.Cut(reference, "::") - if !ok || strings.Contains(symbol, "::") || !strings.HasPrefix(packagePath, "./") || - !testNamePattern.MatchString(symbol) { - return fmt.Errorf("invalid test reference %q", reference) - } - directory := filepath.Join(root, "harness", filepath.FromSlash(strings.TrimPrefix(packagePath, "./"))) - entries, err := os.ReadDir(directory) - if err != nil { - return fmt.Errorf("read test package %s: %w", packagePath, err) - } - found := 0 - for _, entry := range entries { - if entry.IsDir() || !strings.HasSuffix(entry.Name(), "_test.go") { - continue - } - file, err := parser.ParseFile(token.NewFileSet(), filepath.Join(directory, entry.Name()), nil, - parser.SkipObjectResolution) - if err != nil { - return fmt.Errorf("parse %s/%s: %w", packagePath, entry.Name(), err) - } - for _, declaration := range file.Decls { - function, isFunction := declaration.(*ast.FuncDecl) - if !isFunction || function.Recv != nil || function.Name.Name != symbol { - continue - } - if function.Body == nil || len(function.Body.List) == 0 { - return fmt.Errorf("test %s exists but has an empty body", reference) - } - found++ - } - } - if found != 1 { - return fmt.Errorf("test %s has %d declarations, want exactly one", reference, found) - } - return nil -} - -func canonicalJSON(value any) ([]byte, error) { - data, err := json.MarshalIndent(value, "", " ") - if err != nil { - return nil, fmt.Errorf("marshal canonical JSON: %w", err) - } - return append(data, '\n'), nil -} diff --git a/harness/tools/corecontract/registry_test.go b/harness/tools/corecontract/registry_test.go deleted file mode 100644 index b944b199..00000000 --- a/harness/tools/corecontract/registry_test.go +++ /dev/null @@ -1,81 +0,0 @@ -package corecontract - -import ( - "path/filepath" - "strings" - "testing" -) - -func TestTrackedR7RegistryIsCanonicalCompleteAndGrounded(t *testing.T) { - root := filepath.Clean("../../..") - contract, err := Load(root) - if err != nil { - t.Fatal(err) - } - registry, err := LoadRegistry(root) - if err != nil { - t.Fatal(err) - } - if err := ValidateBindings(root, contract, registry); err != nil { - t.Fatal(err) - } -} - -func TestRegistryRejectsNullListsUnknownKindsAndDivergentSharedSteps(t *testing.T) { - root := filepath.Clean("../../..") - contract, err := Load(root) - if err != nil { - t.Fatal(err) - } - registry, err := LoadRegistry(root) - if err != nil { - t.Fatal(err) - } - registry.Invariants = nil - if err := ValidateBindings(root, contract, registry); err == nil || !strings.Contains(err.Error(), "non-null") { - t.Fatalf("null list error = %v", err) - } - registry, _ = LoadRegistry(root) - registry.Gates[0].Steps[0].Kind = "manual" - if err := ValidateBindings(root, contract, registry); err == nil || !strings.Contains(err.Error(), "unknown kind") { - t.Fatalf("unknown kind error = %v", err) - } - registry, _ = LoadRegistry(root) - for index := range registry.Gates { - if registry.Gates[index].ID == "G-R7-ROOT-ISOLATION" { - registry.Gates[index].Steps[0].Oracles = []string{"test:./test/contracts::TestReleaseBoundary"} - } - } - if err := ValidateBindings(root, contract, registry); err == nil || !strings.Contains(err.Error(), "shared step") { - t.Fatalf("shared step error = %v", err) - } -} - -func TestRegistryRejectsInvariantWithoutExecutableGateOracle(t *testing.T) { - root := filepath.Clean("../../..") - contract, err := Load(root) - if err != nil { - t.Fatal(err) - } - registry, err := LoadRegistry(root) - if err != nil { - t.Fatal(err) - } - missing := registry.Invariants[0].Oracles[0].Test - for gateIndex := range registry.Gates { - for stepIndex := range registry.Gates[gateIndex].Steps { - step := ®istry.Gates[gateIndex].Steps[stepIndex] - kept := step.Oracles[:0] - for _, oracle := range step.Oracles { - if oracle != "test:"+missing { - kept = append(kept, oracle) - } - } - step.Oracles = kept - } - } - if err := ValidateBindings(root, contract, registry); err == nil || - !strings.Contains(err.Error(), "not bound to any gate step") { - t.Fatalf("missing executable oracle error = %v", err) - } -} diff --git a/harness/tools/corecontract/report.go b/harness/tools/corecontract/report.go deleted file mode 100644 index abe5a627..00000000 --- a/harness/tools/corecontract/report.go +++ /dev/null @@ -1,199 +0,0 @@ -package corecontract - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "os" - "path/filepath" - "regexp" - "slices" - "strings" - "time" -) - -var reportRunIDPattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$`) - -func LoadReport(path string) (GateReport, error) { - data, err := os.ReadFile(path) - if err != nil { - return GateReport{}, fmt.Errorf("read R7 gate report: %w", err) - } - decoder := json.NewDecoder(bytes.NewReader(data)) - decoder.DisallowUnknownFields() - var report GateReport - if err := decoder.Decode(&report); err != nil { - return GateReport{}, fmt.Errorf("decode R7 gate report: %w", err) - } - var extra any - if err := decoder.Decode(&extra); err != io.EOF { - return GateReport{}, fmt.Errorf("R7 gate report contains trailing JSON") - } - canonical, err := canonicalJSON(report) - if err != nil { - return GateReport{}, err - } - if !bytes.Equal(data, canonical) { - return GateReport{}, fmt.Errorf("R7 gate report is not canonical JSON") - } - return report, nil -} - -func ValidateReport(root string, contract Contract, registry Registry, report GateReport) error { - validators := []func() error{ - func() error { return validateReportIdentity(root, report) }, - func() error { return validateReportInputs(root, report.Inputs) }, - func() error { return validateReportSteps(root, registry, report) }, - func() error { return validateReportGates(registry, report.Gates) }, - func() error { return validateReportContract(contract) }, - } - for _, validate := range validators { - if err := validate(); err != nil { - return err - } - } - return nil -} - -func validateReportIdentity(root string, report GateReport) error { - if report.SchemaVersion != ReportSchemaVersion || !reportRunIDPattern.MatchString(report.RunID) || - !report.Source.CleanAtStart || !report.Source.CleanAtFinish { - return fmt.Errorf("report identity or clean-worktree binding is invalid") - } - started, err := time.Parse(time.RFC3339Nano, report.StartedAt) - if err != nil { - return fmt.Errorf("invalid report started_at: %w", err) - } - finished, err := time.Parse(time.RFC3339Nano, report.FinishedAt) - if err != nil { - return fmt.Errorf("invalid report finished_at: %w", err) - } - if finished.Before(started) { - return fmt.Errorf("report finished_at precedes started_at") - } - if err := requireClean(root, "report evaluated against a dirty worktree"); err != nil { - return err - } - commit, err := gitValue(root, "rev-parse", "HEAD") - if err != nil { - return err - } - tree, err := gitValue(root, "rev-parse", "HEAD^{tree}") - if err != nil { - return err - } - if report.Source.Commit != commit || report.Source.Tree != tree { - return fmt.Errorf("report source does not bind current HEAD commit and tree") - } - return nil -} - -func validateReportInputs(root string, inputs ReportInputs) error { - contractDigest, err := fileSHA256(filepath.Join(root, filepath.FromSlash(DocumentPath))) - if err != nil { - return err - } - registryDigest, err := fileSHA256(filepath.Join(root, filepath.FromSlash(RegistryPath))) - if err != nil { - return err - } - if inputs.ContractSHA256 != contractDigest || inputs.RegistrySHA256 != registryDigest { - return fmt.Errorf("report input digests do not bind the tracked contract and registry") - } - return nil -} - -func validateReportSteps(root string, registry Registry, report GateReport) error { - expectedSteps := uniqueSteps(registry) - if len(report.Steps) != len(expectedSteps) { - return fmt.Errorf("report has %d steps, want %d", len(report.Steps), len(expectedSteps)) - } - passedTests := make(map[string]struct{}) - for index, expected := range expectedSteps { - observed, err := validateReportStep(root, report.RunID, expected, report.Steps[index]) - if err != nil { - return err - } - for _, test := range observed { - if _, duplicate := passedTests[test]; duplicate { - return fmt.Errorf("bound test %s was duplicated across steps", test) - } - passedTests[test] = struct{}{} - } - } - return requireEveryBoundTest(registry, passedTests) -} - -func validateReportStep(root, runID string, expected GateStep, actual StepResult) ([]string, error) { - if actual.ID != expected.ID || actual.Kind != expected.Kind || - !slices.Equal(actual.Argv, expected.Argv) || !slices.Equal(actual.Oracles, expected.Oracles) || - actual.ExitCode != 0 { - return nil, fmt.Errorf("report step does not exactly bind registry step %s", expected.ID) - } - stdout, err := validateOutput(root, actual.Output.StdoutPath, actual.Output.StdoutSHA256, - runID, actual.ID+".stdout") - if err != nil { - return nil, err - } - if _, err := validateOutput(root, actual.Output.StderrPath, actual.Output.StderrSHA256, - runID, actual.ID+".stderr"); err != nil { - return nil, err - } - observed, err := verifyStepOracles(expected, stdout) - if err != nil { - return nil, fmt.Errorf("report step %s: %w", actual.ID, err) - } - if !slices.Equal(actual.PassedTests, observed) { - return nil, fmt.Errorf("report step %s passed_tests do not match output", actual.ID) - } - return observed, nil -} - -func requireEveryBoundTest(registry Registry, passed map[string]struct{}) error { - for _, test := range invariantTests(registry) { - if _, found := passed[test]; !found { - return fmt.Errorf("bound invariant test %s did not pass", test) - } - } - return nil -} - -func validateReportGates(registry Registry, results []GateResult) error { - if len(results) != len(registry.Gates) { - return fmt.Errorf("report has %d gates, want %d", len(results), len(registry.Gates)) - } - for index, binding := range registry.Gates { - result := results[index] - stepIDs := make([]string, len(binding.Steps)) - for stepIndex, step := range binding.Steps { - stepIDs[stepIndex] = step.ID - } - if result.ID != binding.ID || !result.Passed || !slices.Equal(result.StepIDs, stepIDs) { - return fmt.Errorf("report gate %d does not exactly close %s", index, binding.ID) - } - } - return nil -} - -func validateReportContract(contract Contract) error { - if !slices.Equal(contract.Invariants, expectedInvariants) || !slices.Equal(contract.Gates, expectedGates) { - return fmt.Errorf("report evaluated against a non-canonical contract") - } - return nil -} - -func validateOutput(root, relative, digest, runID, base string) ([]byte, error) { - wantPrefix := filepath.ToSlash(filepath.Join(".testdata", "r7", "core-gates", runID)) + "/" - if filepath.ToSlash(relative) != wantPrefix+base || strings.Contains(relative, "..") { - return nil, fmt.Errorf("report output path %q is not exact", relative) - } - data, err := os.ReadFile(filepath.Join(root, filepath.FromSlash(relative))) - if err != nil { - return nil, fmt.Errorf("read report output %s: %w", relative, err) - } - if bytesSHA256(data) != digest { - return nil, fmt.Errorf("report output %s digest mismatch", relative) - } - return data, nil -} diff --git a/harness/tools/corecontract/report_test.go b/harness/tools/corecontract/report_test.go deleted file mode 100644 index 743904c4..00000000 --- a/harness/tools/corecontract/report_test.go +++ /dev/null @@ -1,141 +0,0 @@ -package corecontract - -import ( - "os" - "os/exec" - "path/filepath" - "strings" - "testing" - "time" -) - -func TestReportBindsTreeInputsExactStepOutputAndActualTestPass(t *testing.T) { - fixture := newReportFixture(t) - if err := ValidateReport(fixture.root, fixture.contract, fixture.registry, fixture.report); err != nil { - t.Fatal(err) - } - tampered := fixture.report - tampered.Steps = append([]StepResult(nil), fixture.report.Steps...) - tampered.Steps[0].Argv = []string{"go", "test", "./p"} - if err := ValidateReport(fixture.root, fixture.contract, fixture.registry, tampered); err == nil { - t.Fatal("tampered argv passed report validation") - } - tampered = fixture.report - tampered.Inputs.RegistrySHA256 = bytesSHA256([]byte("other")) - if err := ValidateReport(fixture.root, fixture.contract, fixture.registry, tampered); err == nil { - t.Fatal("tampered input digest passed report validation") - } -} - -type reportFixture struct { - root string - contract Contract - registry Registry - report GateReport -} - -func newReportFixture(t *testing.T) reportFixture { - t.Helper() - root := initializeFixtureRepository(t) - testRef := "./p::TestProof" - step := GateStep{ - ID: "proof", Kind: "go-test", Argv: []string{"go", "test", "-json", "./p"}, - Oracles: []string{"test:" + testRef}, - } - registry := fixtureRegistry(testRef, step) - contract := Contract{Lifecycle: LifecycleActive, - Invariants: append([]string(nil), expectedInvariants...), Gates: append([]string(nil), expectedGates...)} - report := fixtureReport(t, root, testRef, step, registry) - return reportFixture{root: root, contract: contract, registry: registry, report: report} -} - -func initializeFixtureRepository(t *testing.T) string { - t.Helper() - root := t.TempDir() - writeFixture(t, root, ".gitignore", ".testdata/\n") - writeFixture(t, root, DocumentPath, "contract\n") - writeFixture(t, root, RegistryPath, "registry\n") - gitFixture(t, root, "init", "--quiet") - gitFixture(t, root, "config", "user.email", "r7@example.invalid") - gitFixture(t, root, "config", "user.name", "R7 Test") - gitFixture(t, root, "add", ".") - gitFixture(t, root, "commit", "--quiet", "-m", "fixture") - return root -} - -func fixtureRegistry(testRef string, step GateStep) Registry { - registry := Registry{SchemaVersion: 1, Invariants: []InvariantBinding{}, Gates: []GateBinding{}} - for _, id := range expectedInvariants { - registry.Invariants = append(registry.Invariants, InvariantBinding{ID: id, - Oracles: []InvariantOracle{{ID: "proof", Test: testRef}}}) - } - for _, id := range expectedGates { - registry.Gates = append(registry.Gates, GateBinding{ID: id, Steps: []GateStep{step}}) - } - return registry -} - -func fixtureReport(t *testing.T, root, testRef string, step GateStep, registry Registry) GateReport { - t.Helper() - runID := "fixture" - base := filepath.ToSlash(filepath.Join(".testdata", "r7", "core-gates", runID)) - if err := os.MkdirAll(filepath.Join(root, filepath.FromSlash(base)), 0o700); err != nil { - t.Fatal(err) - } - stdout := []byte(`{"Action":"pass","Package":"example/harness/p","Test":"TestProof"}` + "\n") - stdoutPath := filepath.ToSlash(filepath.Join(base, "proof.stdout")) - stderrPath := filepath.ToSlash(filepath.Join(base, "proof.stderr")) - writeFixture(t, root, stdoutPath, string(stdout)) - writeFixture(t, root, stderrPath, "") - now := time.Now().UTC().Format(time.RFC3339Nano) - report := GateReport{ - SchemaVersion: 1, RunID: runID, StartedAt: now, FinishedAt: now, - Source: ReportSource{ - Commit: gitFixture(t, root, "rev-parse", "HEAD"), - Tree: gitFixture(t, root, "rev-parse", "HEAD^{tree}"), - CleanAtStart: true, CleanAtFinish: true, - }, - Steps: []StepResult{{ - ID: step.ID, Kind: step.Kind, Argv: step.Argv, Oracles: step.Oracles, - StartedAt: now, FinishedAt: now, ExitCode: 0, - Output: ReportOutput{StdoutPath: stdoutPath, StdoutSHA256: bytesSHA256(stdout), - StderrPath: stderrPath, StderrSHA256: bytesSHA256(nil)}, - PassedTests: []string{testRef}, - }}, - Gates: []GateResult{}, - } - var err error - report.Inputs.ContractSHA256, err = fileSHA256(filepath.Join(root, filepath.FromSlash(DocumentPath))) - if err != nil { - t.Fatal(err) - } - report.Inputs.RegistrySHA256, err = fileSHA256(filepath.Join(root, filepath.FromSlash(RegistryPath))) - if err != nil { - t.Fatal(err) - } - for _, gate := range registry.Gates { - report.Gates = append(report.Gates, GateResult{ID: gate.ID, StepIDs: []string{"proof"}, Passed: true}) - } - return report -} - -func writeFixture(t *testing.T, root, relative, contents string) { - t.Helper() - path := filepath.Join(root, filepath.FromSlash(relative)) - if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(path, []byte(contents), 0o600); err != nil { - t.Fatal(err) - } -} - -func gitFixture(t *testing.T, root string, arguments ...string) string { - t.Helper() - command := exec.Command("git", append([]string{"-C", root}, arguments...)...) - output, err := command.CombinedOutput() - if err != nil { - t.Fatalf("git %v: %v: %s", arguments, err, output) - } - return strings.TrimSpace(string(output)) -} diff --git a/harness/tools/corecontract/run.go b/harness/tools/corecontract/run.go deleted file mode 100644 index f0ee3182..00000000 --- a/harness/tools/corecontract/run.go +++ /dev/null @@ -1,264 +0,0 @@ -package corecontract - -import ( - "bufio" - "bytes" - "context" - "crypto/sha256" - "encoding/hex" - "encoding/json" - "fmt" - "os" - "os/exec" - "path/filepath" - "slices" - "strings" - "time" -) - -type GateReport struct { - SchemaVersion int `json:"schema_version"` - RunID string `json:"run_id"` - StartedAt string `json:"started_at"` - FinishedAt string `json:"finished_at"` - Source ReportSource `json:"source"` - Inputs ReportInputs `json:"inputs"` - Steps []StepResult `json:"steps"` - Gates []GateResult `json:"gates"` -} - -type ReportSource struct { - Commit string `json:"commit"` - Tree string `json:"tree"` - CleanAtStart bool `json:"clean_at_start"` - CleanAtFinish bool `json:"clean_at_finish"` -} - -type ReportInputs struct { - ContractSHA256 string `json:"contract_sha256"` - RegistrySHA256 string `json:"registry_sha256"` -} - -type StepResult struct { - ID string `json:"id"` - Kind string `json:"kind"` - Argv []string `json:"argv"` - Oracles []string `json:"oracles"` - StartedAt string `json:"started_at"` - FinishedAt string `json:"finished_at"` - ExitCode int `json:"exit_code"` - Output ReportOutput `json:"output"` - PassedTests []string `json:"passed_tests"` -} - -type ReportOutput struct { - StdoutPath string `json:"stdout_path"` - StdoutSHA256 string `json:"stdout_sha256"` - StderrPath string `json:"stderr_path"` - StderrSHA256 string `json:"stderr_sha256"` -} - -type GateResult struct { - ID string `json:"id"` - StepIDs []string `json:"step_ids"` - Passed bool `json:"passed"` -} - -type commandResult struct { - exitCode int - stdout []byte - stderr []byte -} - -var runCommand = executeCommand - -func runStep(ctx context.Context, root, base string, step GateStep) (StepResult, error) { - started := time.Now().UTC() - result := runCommand(ctx, root, step.Argv) - stdoutPath := filepath.ToSlash(filepath.Join(base, step.ID+".stdout")) - stderrPath := filepath.ToSlash(filepath.Join(base, step.ID+".stderr")) - if err := writeOutput(root, stdoutPath, result.stdout); err != nil { - return StepResult{}, err - } - if err := writeOutput(root, stderrPath, result.stderr); err != nil { - return StepResult{}, err - } - if result.exitCode != 0 { - return StepResult{}, fmt.Errorf("step %s exited %d; see %s and %s", - step.ID, result.exitCode, stdoutPath, stderrPath) - } - passedTests, err := verifyStepOracles(step, result.stdout) - if err != nil { - return StepResult{}, fmt.Errorf("step %s: %w", step.ID, err) - } - return StepResult{ - ID: step.ID, Kind: step.Kind, Argv: slices.Clone(step.Argv), - Oracles: slices.Clone(step.Oracles), - StartedAt: started.Format(time.RFC3339Nano), - FinishedAt: time.Now().UTC().Format(time.RFC3339Nano), - ExitCode: 0, - Output: ReportOutput{ - StdoutPath: stdoutPath, StdoutSHA256: bytesSHA256(result.stdout), - StderrPath: stderrPath, StderrSHA256: bytesSHA256(result.stderr), - }, - PassedTests: passedTests, - }, nil -} - -func verifyStepOracles(step GateStep, stdout []byte) ([]string, error) { - if step.Kind == "shell" { - lines := strings.Split(strings.ReplaceAll(string(stdout), "\r\n", "\n"), "\n") - for _, oracle := range step.Oracles { - want := strings.TrimPrefix(oracle, "stdout:") - count := 0 - for _, line := range lines { - if line == want { - count++ - } - } - if count != 1 { - return nil, fmt.Errorf("stdout oracle %q occurred %d times, want exactly one", want, count) - } - } - return []string{}, nil - } - required := make(map[string]struct{}, len(step.Oracles)) - for _, oracle := range step.Oracles { - required[strings.TrimPrefix(oracle, "test:")] = struct{}{} - } - passed, err := parseGoTestJSON(stdout, required) - if err != nil { - return nil, err - } - return passed, nil -} - -type goTestEvent struct { - Action string `json:"Action"` - Package string `json:"Package"` - Test string `json:"Test"` -} - -func parseGoTestJSON(output []byte, required map[string]struct{}) ([]string, error) { - counts := make(map[string]int, len(required)) - scanner := bufio.NewScanner(bytes.NewReader(output)) - scanner.Buffer(make([]byte, 4096), 1024*1024) - for scanner.Scan() { - var event goTestEvent - if err := json.Unmarshal(scanner.Bytes(), &event); err != nil { - return nil, fmt.Errorf("go-test emitted non-JSON output: %w", err) - } - if event.Test == "" || (event.Action != "pass" && event.Action != "fail" && event.Action != "skip") { - continue - } - for reference := range required { - packagePath, symbol, _ := strings.Cut(reference, "::") - if event.Test != symbol || !packageMatches(event.Package, packagePath) { - continue - } - if event.Action != "pass" { - return nil, fmt.Errorf("required test %s reported %s", reference, event.Action) - } - counts[reference]++ - } - } - if err := scanner.Err(); err != nil { - return nil, fmt.Errorf("scan go-test JSON: %w", err) - } - passed := make([]string, 0, len(required)) - for reference := range required { - if counts[reference] != 1 { - return nil, fmt.Errorf("required test %s passed %d times, want exactly one", reference, counts[reference]) - } - passed = append(passed, reference) - } - slices.Sort(passed) - return passed, nil -} - -func packageMatches(importPath, relative string) bool { - suffix := strings.TrimPrefix(relative, ".") - return strings.HasSuffix(importPath, suffix) -} - -func executeCommand(ctx context.Context, root string, argv []string) commandResult { - command := exec.CommandContext(ctx, argv[0], argv[1:]...) - command.Dir = root - var stdout, stderr bytes.Buffer - command.Stdout = &stdout - command.Stderr = &stderr - err := command.Run() - exitCode := 0 - if err != nil { - exitCode = -1 - if exit, ok := err.(*exec.ExitError); ok { - exitCode = exit.ExitCode() - } - } - return commandResult{exitCode: exitCode, stdout: stdout.Bytes(), stderr: stderr.Bytes()} -} - -func canonicalRepositoryRoot(root string) (string, error) { - absolute, err := filepath.Abs(root) - if err != nil { - return "", fmt.Errorf("resolve root: %w", err) - } - resolved, err := gitValue(absolute, "rev-parse", "--show-toplevel") - if err != nil { - return "", err - } - resolved, err = filepath.Abs(resolved) - if err != nil { - return "", fmt.Errorf("resolve Git root: %w", err) - } - if filepath.Clean(absolute) != filepath.Clean(resolved) { - return "", fmt.Errorf("--root %q is not the repository root %q", absolute, resolved) - } - return resolved, nil -} - -func worktreeClean(root string) (bool, error) { - value, err := gitValue(root, "status", "--porcelain=v1", "--untracked-files=all") - if err != nil { - return false, err - } - return value == "", nil -} - -func gitValue(root string, arguments ...string) (string, error) { - command := exec.Command("git", append([]string{"-C", root}, arguments...)...) - output, err := command.CombinedOutput() - if err != nil { - return "", fmt.Errorf("git %s: %w: %s", strings.Join(arguments, " "), err, - strings.TrimSpace(string(output))) - } - return strings.TrimSpace(string(output)), nil -} - -func ensureIgnored(root, relative string) error { - command := exec.Command("git", "-C", root, "check-ignore", "-q", "--", relative) - if err := command.Run(); err != nil { - return fmt.Errorf("report path %s is not ignored", relative) - } - return nil -} - -func writeOutput(root, relative string, data []byte) error { - if err := os.WriteFile(filepath.Join(root, filepath.FromSlash(relative)), data, 0o600); err != nil { - return fmt.Errorf("write step output %s: %w", relative, err) - } - return nil -} - -func fileSHA256(path string) (string, error) { - data, err := os.ReadFile(path) - if err != nil { - return "", fmt.Errorf("read digest input %s: %w", path, err) - } - return bytesSHA256(data), nil -} - -func bytesSHA256(data []byte) string { - digest := sha256.Sum256(data) - return "sha256:" + hex.EncodeToString(digest[:]) -} diff --git a/harness/tools/corecontract/run_test.go b/harness/tools/corecontract/run_test.go deleted file mode 100644 index 43859efb..00000000 --- a/harness/tools/corecontract/run_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package corecontract - -import ( - "strings" - "testing" -) - -func TestGoTestOracleRequiresOneActualPassAndRejectsSkipOrDuplicate(t *testing.T) { - ref := "./internal/agency::TestProof" - required := map[string]struct{}{ref: {}} - pass := `{"Action":"pass","Package":"example/harness/internal/agency","Test":"TestProof"}` + "\n" - got, err := parseGoTestJSON([]byte(pass), required) - if err != nil || len(got) != 1 || got[0] != ref { - t.Fatalf("pass = %v, %v", got, err) - } - for name, output := range map[string]string{ - "skip": `{"Action":"skip","Package":"example/harness/internal/agency","Test":"TestProof"}` + "\n", - "duplicate": pass + pass, - "unknown": `not-json` + "\n", - } { - t.Run(name, func(t *testing.T) { - if _, err := parseGoTestJSON([]byte(output), required); err == nil { - t.Fatalf("output unexpectedly passed") - } - }) - } -} - -func TestShellOracleRequiresOneExactStdoutLine(t *testing.T) { - step := GateStep{Kind: "shell", Oracles: []string{"stdout:proof passed"}} - if _, err := verifyStepOracles(step, []byte("prefix proof passed\nproof passed\n")); err != nil { - t.Fatal(err) - } - for _, output := range []string{"prefix proof passed\n", "proof passed\nproof passed\n"} { - if _, err := verifyStepOracles(step, []byte(output)); err == nil || - !strings.Contains(err.Error(), "exactly one") { - t.Fatalf("output %q error = %v", output, err) - } - } -} diff --git a/harness/tools/quality/check.go b/harness/tools/quality/check.go deleted file mode 100644 index 5b47d2cd..00000000 --- a/harness/tools/quality/check.go +++ /dev/null @@ -1,232 +0,0 @@ -package main - -import ( - "fmt" - "path/filepath" - "strings" - - "github.com/mnemon-dev/mnemon/harness/tools/corecontract" -) - -const ( - baselinePath = "harness/test/contracts/go_quality_baseline.json" - exceptionsPath = "harness/test/contracts/go_quality_exceptions.json" - architecturePath = "harness/test/contracts/go_architecture_debt.json" - requirementsPath = "harness/test/contracts/r7-requirements.json" -) - -type contractBundle struct { - baseline baselineManifest - exceptions exceptionManifest - architecture architectureManifest - core corecontract.Contract - requirements requirementsManifest -} - -func checkRepository(root, baseReference string) error { - root, err := filepath.Abs(root) - if err != nil { - return fmt.Errorf("resolve root: %w", err) - } - exclusions, err := loadQualityExclusions(root, true) - if err != nil { - return err - } - if err := validateExclusionEvidence(root, exclusions); err != nil { - return err - } - contracts, err := loadContractBundle(root) - if err != nil { - return err - } - if err := validateContractBundle(root, contracts); err != nil { - return err - } - return runRepositoryChecks(root, baseReference, contracts) -} - -func loadContractBundle(root string) (contractBundle, error) { - var bundle contractBundle - loads := []func() error{ - func() error { - var err error - bundle.baseline, err = readExactJSON[baselineManifest](filepath.Join(root, baselinePath)) - return err - }, - func() error { - var err error - bundle.exceptions, err = readExactJSON[exceptionManifest](filepath.Join(root, exceptionsPath)) - return err - }, - func() error { - var err error - bundle.architecture, err = readExactJSON[architectureManifest](filepath.Join(root, architecturePath)) - return err - }, - func() error { - var err error - bundle.core, err = corecontract.Load(root) - return err - }, - func() error { - var err error - bundle.requirements, err = readExactJSON[requirementsManifest](filepath.Join(root, requirementsPath)) - return err - }, - } - for _, load := range loads { - if err := load(); err != nil { - return contractBundle{}, err - } - } - return bundle, nil -} - -func validateContractBundle(root string, bundle contractBundle) error { - if err := validateAllManifests(root, bundle.baseline, bundle.exceptions, bundle.architecture, - bundle.core, bundle.requirements); err != nil { - return err - } - if bundle.baseline.SourceCommit != bundle.architecture.SourceCommit { - return fmt.Errorf("quality baseline and architecture debt must use the same source_commit") - } - if _, err := runGit(root, "cat-file", "-e", bundle.baseline.SourceCommit+"^{commit}"); err != nil { - return fmt.Errorf("baseline source_commit does not exist: %w", err) - } - if _, err := runGit(root, "merge-base", "--is-ancestor", bundle.baseline.SourceCommit, "HEAD"); err != nil { - return fmt.Errorf("baseline source_commit is not an ancestor of HEAD") - } - return nil -} - -func runRepositoryChecks(root, baseReference string, bundle contractBundle) error { - files, err := loadHarnessSources(root) - if err != nil { - return err - } - drift, err := gofmtDrift(files) - if err != nil { - return err - } - if len(drift) > 0 { - return fmt.Errorf("gofmt drift: %s", strings.Join(drift, ", ")) - } - if directives := nolintDiagnostics(files); len(directives) > 0 { - return fmt.Errorf("bare, wildcard, or unexplained //nolint directives: %s", strings.Join(directives, ", ")) - } - measured, err := measureTree(root, bundle.baseline.SourceCommit) - if err != nil { - return err - } - if err := compareMeasurement(bundle.baseline, bundle.exceptions, measured); err != nil { - return err - } - findings, err := dependencyFindings(root) - if err != nil { - return err - } - if err := validateArchitectureEvidence(root, bundle.architecture, findings); err != nil { - return err - } - if err := validateRequirementEvidence(root, bundle.core, bundle.requirements); err != nil { - return err - } - if baseReference != "" { - if err := compareManifestHistory(root, baseReference, bundle.baseline, bundle.exceptions, bundle.architecture); err != nil { - return err - } - } - return nil -} - -func validateAllManifests(root string, baseline baselineManifest, exceptions exceptionManifest, - architecture architectureManifest, contract corecontract.Contract, - requirements requirementsManifest, -) error { - validators := []func() error{ - func() error { return validateBaseline(baseline) }, - func() error { return validateExceptions(exceptions) }, - func() error { return validateArchitectureManifest(architecture) }, - func() error { return corecontract.ValidateBindings(root, contract, requirements) }, - } - for _, validate := range validators { - if err := validate(); err != nil { - return err - } - } - return nil -} - -func compareManifestHistory(root, baseReference string, baseline baselineManifest, exceptions exceptionManifest, architecture architectureManifest) error { - if _, err := runGit(root, "rev-parse", "--verify", baseReference+"^{commit}"); err != nil { - return fmt.Errorf("invalid base-ref %q: %w", baseReference, err) - } - mergeBaseBytes, err := runGit(root, "merge-base", baseReference, "HEAD") - if err != nil { - return fmt.Errorf("base-ref %q has no merge base with HEAD: %w", baseReference, err) - } - deltaAnchor := strings.TrimSpace(string(mergeBaseBytes)) - if err := validateFullCommit(deltaAnchor, "base-ref merge base"); err != nil { - return err - } - if err := validateCommittedManifestChain(root, deltaAnchor); err != nil { - return err - } - lifetimeLedger, err := committedManifestLedger(root, baseline.SourceCommit) - if err != nil { - return err - } - prior, found, err := loadContractBundleAtRef(root, "HEAD") - if err != nil { - return err - } - current := contractBundle{baseline: baseline, exceptions: exceptions, architecture: architecture} - if !found { - if err := validateBootstrapSource(root, baseline.SourceCommit, architecture.SourceCommit); err != nil { - return err - } - if err := validateRatchetManifests(current); err != nil { - return err - } - } else { - if err := compareContractBundles(prior, current); err != nil { - return err - } - } - if err := lifetimeLedger.observe(current); err != nil { - return err - } - base, baseFound, err := loadContractBundleAtRef(root, baseReference) - if err != nil { - return err - } - if !baseFound { - return nil - } - if err := compareContractBundles(base, current); err != nil { - return fmt.Errorf("candidate is not monotone relative to base-ref %s: %w", baseReference, err) - } - baseLedger, err := committedManifestLedgerTo(root, baseline.SourceCommit, baseReference) - if err != nil { - return err - } - merged, err := mergeRatchetLedgers([]manifestLineage{{ledger: lifetimeLedger}, {ledger: baseLedger}}) - if err != nil { - return err - } - return merged.observe(current) -} - -func validateBootstrapSource(root, baselineSource, architectureSource string) error { - head, err := currentCommit(root) - if err != nil { - return err - } - parentBytes, parentErr := runGit(root, "rev-parse", "HEAD^") - parent := strings.TrimSpace(string(parentBytes)) - valid := baselineSource == head || (parentErr == nil && baselineSource == parent) - if !valid || architectureSource != baselineSource { - return fmt.Errorf("base-ref has no quality manifests and no deterministic ratchet anchor; bootstrap source_commit must equal HEAD or its first parent") - } - return nil -} diff --git a/harness/tools/quality/check_test.go b/harness/tools/quality/check_test.go deleted file mode 100644 index 15a2a487..00000000 --- a/harness/tools/quality/check_test.go +++ /dev/null @@ -1,107 +0,0 @@ -package main - -import ( - "path/filepath" - "strings" - "testing" - - "github.com/mnemon-dev/mnemon/harness/tools/corecontract" -) - -func TestValidateAllManifestsRejectsMalformedManifest(t *testing.T) { - root := filepath.Clean("../../..") - baseline := validBaselineManifest() - exceptions := exceptionManifest{SchemaVersion: 1, Entries: []exceptionEntry{}} - architecture := architectureManifest{SchemaVersion: 1, SourceCommit: baseline.SourceCommit, Entries: []architectureEntry{}} - contract, err := corecontract.Load(root) - if err != nil { - t.Fatal(err) - } - requirements, err := corecontract.LoadRegistry(root) - if err != nil { - t.Fatal(err) - } - if err := validateAllManifests(root, baseline, exceptions, architecture, contract, requirements); err != nil { - t.Fatalf("valid manifests: %v", err) - } - requirements.Invariants[0].ID = "P-99" - if err := validateAllManifests(root, baseline, exceptions, architecture, contract, requirements); err == nil || - !strings.Contains(err.Error(), "invariant IDs") { - t.Fatalf("unknown invariant error = %v", err) - } - baseline.ToolVersion = "latest" - requirements, err = corecontract.LoadRegistry(root) - if err != nil { - t.Fatal(err) - } - if err := validateAllManifests(root, baseline, exceptions, architecture, contract, - requirements); err == nil || !strings.Contains(err.Error(), "tool_version") { - t.Fatalf("tool version error = %v", err) - } -} - -func TestCommittedManifestChainFindsBootstrapOnSecondMergeParent(t *testing.T) { - root := initTestRepository(t) - writeTestFile(t, root, "README.md", "base\n") - base := commitTestRepository(t, root, "base") - branchBytes, err := runGit(root, "branch", "--show-current") - if err != nil { - t.Fatal(err) - } - mainBranch := strings.TrimSpace(string(branchBytes)) - if _, err := runGit(root, "checkout", "-b", "quality-branch"); err != nil { - t.Fatal(err) - } - manifest := validBaselineManifest() - manifest.SourceCommit = base - writeRatchetBundle(t, root, manifest) - commitTestRepository(t, root, "quality bootstrap") - if _, err := runGit(root, "checkout", mainBranch); err != nil { - t.Fatal(err) - } - writeTestFile(t, root, "README.md", "main branch\n") - commitTestRepository(t, root, "main work") - if _, err := runGit(root, "merge", "--no-ff", "quality-branch", "-m", "merge quality"); err != nil { - t.Fatal(err) - } - if err := validateCommittedManifestChain(root, base); err != nil { - t.Fatalf("merge history chain: %v", err) - } -} - -func initTestRepository(t *testing.T) string { - t.Helper() - root := t.TempDir() - if _, err := runGit(root, "init"); err != nil { - t.Fatal(err) - } - if _, err := runGit(root, "config", "user.email", "quality@example.invalid"); err != nil { - t.Fatal(err) - } - if _, err := runGit(root, "config", "user.name", "Quality Test"); err != nil { - t.Fatal(err) - } - return root -} - -func commitTestRepository(t *testing.T, root, message string) string { - t.Helper() - if _, err := runGit(root, "add", "."); err != nil { - t.Fatal(err) - } - if _, err := runGit(root, "commit", "-m", message); err != nil { - t.Fatal(err) - } - commit, err := currentCommit(root) - if err != nil { - t.Fatal(err) - } - return commit -} - -func writeCanonicalTestFile(t *testing.T, root, relative string, value any) { - t.Helper() - if err := writeCanonicalJSON(root+"/"+relative, value); err != nil { - t.Fatal(err) - } -} diff --git a/harness/tools/quality/closure_identity.go b/harness/tools/quality/closure_identity.go deleted file mode 100644 index 5ccc0d16..00000000 --- a/harness/tools/quality/closure_identity.go +++ /dev/null @@ -1,329 +0,0 @@ -package main - -import ( - "crypto/sha256" - "encoding/hex" - "fmt" - "go/ast" - "go/token" - "strings" -) - -type closureLiteral struct { - literal *ast.FuncLit - ancestors []ast.Node -} - -const closureDigestBytes = 12 - -type closureCollector struct { - ancestors []ast.Node - closures *[]closureLiteral -} - -type closureIdentityVector struct { - cyclomatic int - cognitive int - logicalLines int - statements int - nesting int -} - -type closureIdentityCandidate struct { - closure closureLiteral - base string - descriptor string - equivalent string -} - -func measureFunctionTree(file sourceFile, symbol string, start token.Pos, body *ast.BlockStmt, measured *[]functionMeasurement) error { - *measured = append(*measured, measureFunction(file, symbol, start, body)) - return measureChildClosures(file, symbol, collectDirectClosures(body), measured) -} - -func measurePackageFunctionLiterals(file sourceFile, measured *[]functionMeasurement) error { - var closures []closureLiteral - for _, declaration := range file.AST.Decls { - if _, isFunction := declaration.(*ast.FuncDecl); isFunction { - continue - } - closures = append(closures, collectDirectClosures(declaration)...) - } - return measureChildClosures(file, "$package", closures, measured) -} - -func measureChildClosures(file sourceFile, parent string, closures []closureLiteral, measured *[]functionMeasurement) error { - candidates := make([]closureIdentityCandidate, 0, len(closures)) - equivalenceByBase := make(map[string]string, len(closures)) - groupOrder := make([]string, 0, len(closures)) - groups := make(map[string][]closureIdentityCandidate, len(closures)) - for _, closure := range closures { - candidate := stableClosureIdentity(file, parent, closure) - if prior, exists := equivalenceByBase[candidate.base]; exists && prior != candidate.equivalent { - return fmt.Errorf("closure identity collision in %s below %s for %s at %s; extract or name the closures explicitly", file.Path, parent, candidate.descriptor, candidate.base) - } - if _, exists := equivalenceByBase[candidate.base]; !exists { - groupOrder = append(groupOrder, candidate.base) - } - equivalenceByBase[candidate.base] = candidate.equivalent - candidates = append(candidates, candidate) - groups[candidate.base] = append(groups[candidate.base], candidate) - } - ordinals := make(map[string]int) - for _, candidate := range candidates { - ordinals[candidate.base]++ - symbol := fmt.Sprintf("%s-%03d", candidate.base, ordinals[candidate.base]) - *measured = append(*measured, measureFunction(file, symbol, candidate.closure.literal.Type.Pos(), candidate.closure.literal.Body)) - } - for _, base := range groupOrder { - var descendants []closureLiteral - for _, candidate := range groups[base] { - descendants = append(descendants, collectDirectClosures(candidate.closure.literal.Body)...) - } - if err := measureChildClosures(file, base, descendants, measured); err != nil { - return err - } - } - return nil -} - -func stableClosureIdentity(file sourceFile, parent string, closure closureLiteral) closureIdentityCandidate { - anchor, descriptor := closureAnchor(closure) - tokens, vector := closureOwnShape(file, closure.literal) - digest := tokenFingerprint(tokens) - shape := hex.EncodeToString(digest[:closureDigestBytes]) - vectorKey := vector.key() - return closureIdentityCandidate{ - closure: closure, - base: parent + ".$func-" + anchor + "-" + shape + "-" + vectorKey, - descriptor: descriptor, - equivalent: descriptor + "\x00" + strings.Join(tokens, "\x00") + "\x00" + vectorKey + - "\x00" + closureObservationSignature(file, closure), - } -} - -func closureObservationSignature(file sourceFile, closure closureLiteral) string { - measurement := measureFunction(file, "", closure.literal.Type.Pos(), closure.literal.Body) - var signature strings.Builder - fmt.Fprintf(&signature, "metrics:%d:%d:%d:%d:%d", measurement.Cyclomatic, measurement.Cognitive, - measurement.LogicalLines, measurement.Statements, measurement.Nesting) - appendClosureSignaturePart(&signature, strings.Join(measurement.Tokens, "\x00")) - for _, child := range collectDirectClosures(closure.literal.Body) { - _, descriptor := closureAnchor(child) - appendClosureSignaturePart(&signature, descriptor) - appendClosureSignaturePart(&signature, closureObservationSignature(file, child)) - } - return signature.String() -} - -func appendClosureSignaturePart(signature *strings.Builder, value string) { - fmt.Fprintf(signature, "|%d:", len(value)) - signature.WriteString(value) -} - -func (vector closureIdentityVector) key() string { - return fmt.Sprintf("cy%d-co%d-li%d-st%d-ne%d", vector.cyclomatic, vector.cognitive, vector.logicalLines, vector.statements, vector.nesting) -} - -func closureOwnShape(file sourceFile, literal *ast.FuncLit) ([]string, closureIdentityVector) { - nodes := directNestedClosureCarriers(literal.Body) - spans := make([]tokenSpan, 0, len(nodes)) - excluded := make(map[token.Pos]token.Pos, len(nodes)) - for _, node := range nodes { - spans = append(spans, tokenSpan{ - start: file.FileSet.PositionFor(node.Pos(), false).Offset, - end: file.FileSet.PositionFor(node.End(), false).Offset, - }) - excluded[node.Pos()] = node.End() - } - tokens := normalizedTokenSpan(file, literal.Type.Pos(), literal.Body.End(), spans) - metrics := &flowMetrics{cyclomatic: 1} - ast.Walk(flowVisitor{metrics: metrics, excluded: excluded}, literal.Body) - return tokens, closureIdentityVector{ - cyclomatic: metrics.cyclomatic, cognitive: metrics.cognitive, - logicalLines: logicalLinesExcluding(file, literal.Type.Pos(), literal.Body.End(), spans), - statements: metrics.statements, nesting: metrics.maxNesting, - } -} - -func directNestedClosureCarriers(body *ast.BlockStmt) []ast.Node { - seen := make(map[token.Pos]token.Pos) - var carriers []ast.Node - for _, closure := range collectDirectClosures(body) { - carrier := directNestedClosureCarrier(closure) - if end, exists := seen[carrier.Pos()]; exists && end == carrier.End() { - continue - } - seen[carrier.Pos()] = carrier.End() - carriers = append(carriers, carrier) - } - return carriers -} - -func directNestedClosureCarrier(closure closureLiteral) ast.Node { - for index := len(closure.ancestors) - 1; index >= 0; index-- { - node := closure.ancestors[index] - if _, block := node.(*ast.BlockStmt); block { - continue - } - if _, statement := node.(ast.Stmt); statement { - return node - } - } - return closure.literal -} - -func collectDirectClosures(root ast.Node) []closureLiteral { - var closures []closureLiteral - ast.Walk(closureCollector{closures: &closures}, root) - return closures -} - -func (collector closureCollector) Visit(node ast.Node) ast.Visitor { - if node == nil { - return nil - } - if literal, ok := node.(*ast.FuncLit); ok { - ancestors := append([]ast.Node(nil), collector.ancestors...) - *collector.closures = append(*collector.closures, closureLiteral{literal: literal, ancestors: ancestors}) - return nil - } - ancestors := append(append([]ast.Node(nil), collector.ancestors...), node) - return closureCollector{ancestors: ancestors, closures: collector.closures} -} - -func closureAnchor(closure closureLiteral) (string, string) { - var contexts []string - for index := 0; index < len(closure.ancestors); index++ { - if candidate, _, ok := closureContextDescriptor(closure.ancestors[index], closure.literal); ok { - if strings.HasPrefix(candidate, "key:") { - if label, exists := compositeSiblingStringLabel(closure, index); exists { - candidate += ":label:" + label - } - } - contexts = append(contexts, candidate) - } - } - if len(contexts) == 0 { - contexts = append(contexts, "closure") - } - descriptor := strings.Join(contexts, "/") - digest := sha256.Sum256([]byte(descriptor)) - return hex.EncodeToString(digest[:closureDigestBytes]), descriptor -} - -func compositeSiblingStringLabel(closure closureLiteral, contextIndex int) (string, bool) { - for index := contextIndex - 1; index >= 0; index-- { - literal, ok := closure.ancestors[index].(*ast.CompositeLit) - if !ok { - continue - } - for _, preferred := range []string{"name", "id", "kind", "key"} { - for _, element := range literal.Elts { - field, ok := element.(*ast.KeyValueExpr) - if !ok || expressionName(field.Key) != preferred { - continue - } - value, ok := field.Value.(*ast.BasicLit) - if !ok || value.Kind != token.STRING { - continue - } - digest := sha256.Sum256([]byte(preferred + "\x00" + value.Value)) - return hex.EncodeToString(digest[:closureDigestBytes]), true - } - } - return "", false - } - return "", false -} - -func closureContextDescriptor(node ast.Node, literal *ast.FuncLit) (string, bool, bool) { - switch value := node.(type) { - case *ast.AssignStmt: - return assignmentClosureDescriptor(value, literal) - case *ast.ValueSpec: - return valueClosureDescriptor(value, literal) - case *ast.CallExpr: - return callClosureDescriptor(value, literal) - case *ast.KeyValueExpr: - if containsNode(value.Value, literal) { - return "key:" + expressionName(value.Key), true, true - } - case *ast.ReturnStmt: - for index, result := range value.Results { - if containsNode(result, literal) { - return fmt.Sprintf("return:%d", index), false, true - } - } - } - return "", false, false -} - -func assignmentClosureDescriptor(statement *ast.AssignStmt, literal *ast.FuncLit) (string, bool, bool) { - for index, expression := range statement.Rhs { - if !containsNode(expression, literal) { - continue - } - name := "result" - if index < len(statement.Lhs) { - name = expressionName(statement.Lhs[index]) - } - return fmt.Sprintf("assign:%s:%d", name, index), name != "_" && name != "result", true - } - return "", false, false -} - -func valueClosureDescriptor(specification *ast.ValueSpec, literal *ast.FuncLit) (string, bool, bool) { - for index, expression := range specification.Values { - if !containsNode(expression, literal) { - continue - } - name := "value" - if index < len(specification.Names) { - name = specification.Names[index].Name - } - return fmt.Sprintf("value:%s:%d", name, index), name != "value", true - } - return "", false, false -} - -func callClosureDescriptor(call *ast.CallExpr, literal *ast.FuncLit) (string, bool, bool) { - for index, argument := range call.Args { - if containsNode(argument, literal) { - label, labelled := callLabel(call) - return fmt.Sprintf("call:%s:%d:%s", expressionName(call.Fun), index, label), labelled, true - } - } - return "", false, false -} - -func callLabel(call *ast.CallExpr) (string, bool) { - for _, argument := range call.Args { - if literal, ok := argument.(*ast.BasicLit); ok && literal.Kind == token.STRING { - digest := sha256.Sum256([]byte(literal.Value)) - return hex.EncodeToString(digest[:closureDigestBytes]), true - } - } - return "unlabelled", false -} - -func expressionName(expression ast.Expr) string { - switch value := expression.(type) { - case *ast.Ident: - return value.Name - case *ast.SelectorExpr: - return expressionName(value.X) + "." + value.Sel.Name - case *ast.IndexExpr: - return expressionName(value.X) - case *ast.IndexListExpr: - return expressionName(value.X) - case *ast.StarExpr: - return "ptr-" + expressionName(value.X) - default: - return fmt.Sprintf("%T", expression) - } -} - -func containsNode(container, target ast.Node) bool { - return container.Pos() <= target.Pos() && container.End() >= target.End() -} diff --git a/harness/tools/quality/closure_identity_test.go b/harness/tools/quality/closure_identity_test.go deleted file mode 100644 index 8ead5bb6..00000000 --- a/harness/tools/quality/closure_identity_test.go +++ /dev/null @@ -1,304 +0,0 @@ -package main - -import ( - "fmt" - "strings" - "testing" -) - -func TestClosureIdentityDoesNotChurnWhenEarlierClosureIsInserted(t *testing.T) { - original := closureSymbolsByMarker(t, `package harness -func Run() { - use(func() { laterMarker() }) -} -`) - withInsertion := closureSymbolsByMarker(t, `package harness -func Run() { - use(func() { insertedMarker() }) - use(func() { laterMarker() }) -} -`) - if original["laterMarker"] == "" || original["laterMarker"] != withInsertion["laterMarker"] { - t.Fatalf("later closure identity churned: %q != %q", original["laterMarker"], withInsertion["laterMarker"]) - } -} - -func TestNamedClosureIdentitySurvivesUnrelatedSiblingInsertion(t *testing.T) { - original := closureSymbolsByMarker(t, `package harness -func Run() { - later := func() { laterMarker() } - _ = later -} -`) - withInsertion := closureSymbolsByMarker(t, `package harness -func Run() { - earlier := func() { insertedMarker() } - later := func() { laterMarker() } - _, _ = earlier, later -} -`) - if original["laterMarker"] != withInsertion["laterMarker"] { - t.Fatalf("named closure identity churned: %q != %q", original["laterMarker"], withInsertion["laterMarker"]) - } -} - -func TestEquivalentClosureGroupGrowsWithoutIdentityChurn(t *testing.T) { - original := closureSymbolsForMarker(t, `package harness -func Run() { - use(func() { repeatedMarker() }) - use(func() { repeatedMarker() }) -} -`, "repeatedMarker") - withInsertion := closureSymbolsForMarker(t, `package harness -func Run() { - use(func() { repeatedMarker() }) - use(func() { repeatedMarker() }) - use(func() { repeatedMarker() }) -} -`, "repeatedMarker") - if len(original) != 2 || len(withInsertion) != 3 { - t.Fatalf("equivalent symbols = %#v / %#v", original, withInsertion) - } - for _, symbol := range original { - if !containsString(withInsertion, symbol) { - t.Fatalf("existing equivalent identity %q churned: %#v", symbol, withInsertion) - } - } -} - -func TestPackageClosuresAcrossDeclarationsHaveDistinctIdentities(t *testing.T) { - symbols := closureSymbolsForMarker(t, `package harness -var first = use(func() { packageMarker() }) -var second = use(func() { packageMarker() }) -`, "packageMarker") - if len(symbols) != 2 || symbols[0] == symbols[1] { - t.Fatalf("package closure symbols = %#v", symbols) - } -} - -func TestNonEquivalentNestedClosureTreesFailClosed(t *testing.T) { - root := t.TempDir() - writeTestFile(t, root, "harness/a.go", `package harness -func Run() { - use(func() { use(func() { nestedInsertedMarker() }) }) - use(func() { use(func() { nestedLaterMarker() }) }) -} -`) - files, err := loadHarnessSources(root) - if err != nil { - t.Fatal(err) - } - if _, err := measureFunctions(files); err == nil || !strings.Contains(err.Error(), "closure identity collision") { - t.Fatalf("non-equivalent nested trees error = %v", err) - } -} - -func TestCompositeSiblingLabelStabilizesNestedClosureTrees(t *testing.T) { - original := closureSymbolsForMarker(t, `package harness -func Run() { - tests := []struct { name string; configure func() }{ - {name: "later", configure: func() { use(func() { laterNestedMarker() }) }}, - } - _ = tests -} -`, "laterNestedMarker") - withInsertion := closureSymbolsForMarker(t, `package harness -func Run() { - tests := []struct { name string; configure func() }{ - {name: "earlier", configure: func() { use(func() { insertedNestedMarker() }) }}, - {name: "later", configure: func() { use(func() { laterNestedMarker() }) }}, - } - _ = tests -} -`, "laterNestedMarker") - if len(original) != 1 || len(withInsertion) != 1 || original[0] != withInsertion[0] { - t.Fatalf("labelled composite identity churned: %#v != %#v", original, withInsertion) - } -} - -func TestClosureRatchetSameNameNestedScopeInsertion(t *testing.T) { - body := ratchetClosureBody("laterMarker") - assertClosureRatchetStable(t, `package harness -func Run() { - { x := func() { `+body+` }; _ = x } -} -`, `package harness -func Run() { - { x := func() { insertedMarker() }; _ = x } - { x := func() { `+body+` }; _ = x } -} -`, "laterMarker") -} - -func TestClosureRatchetUnlabelledOuterNestedInsertion(t *testing.T) { - body := ratchetClosureBody("outerMarker") - assertClosureRatchetStable(t, `package harness -func Run() { - use(func() { `+body+` }) -} -`, `package harness -func Run() { - use(func() { use(func() { nestedMarker() }); `+body+` }) -} -`, "outerMarker") -} - -func TestClosureRatchetNestedChildSiblingInsertion(t *testing.T) { - body := ratchetClosureBody("nestedLaterMarker") - assertClosureRatchetStable(t, `package harness -func Run() { - use(func() { use(func() { `+body+` }) }) -} -`, `package harness -func Run() { - use(func() { - use(func() { insertedNestedMarker() }) - use(func() { `+body+` }) - }) -} -`, "nestedLaterMarker") -} - -func TestClosureRatchetPackagePrefixInsertion(t *testing.T) { - body := ratchetClosureBody("packageLaterMarker") - assertClosureRatchetStable(t, `package harness -var later = use(func() { `+body+` }) -`, `package harness -var earlier = use(func() { insertedMarker() }) -var later = use(func() { `+body+` }) -`, "packageLaterMarker") -} - -func TestClosureRatchetExactEquivalentPrefixInsertion(t *testing.T) { - body := ratchetClosureBody("equivalentMarker") - original := `package harness -func Run() { - use(func() { ` + body + ` }) -} -` - modified := `package harness -func Run() { - use(func() { ` + body + ` }) - use(func() { ` + body + ` }) -} -` - assertClosureRatchetStable(t, original, modified, "equivalentMarker") -} - -func closureSymbolsByMarker(t *testing.T, source string) map[string]string { - t.Helper() - root := t.TempDir() - writeTestFile(t, root, "harness/a.go", source) - files, err := loadHarnessSources(root) - if err != nil { - t.Fatal(err) - } - result := make(map[string]string) - functions, err := measureFunctions(files) - if err != nil { - t.Fatal(err) - } - for _, function := range functions { - for _, token := range function.Tokens { - if token == "laterMarker" || token == "insertedMarker" { - result[token] = function.Symbol - } - } - } - return result -} - -func closureSymbolsForMarker(t *testing.T, source, marker string) []string { - t.Helper() - root := t.TempDir() - writeTestFile(t, root, "harness/a.go", source) - files, err := loadHarnessSources(root) - if err != nil { - t.Fatal(err) - } - var symbols []string - functions, err := measureFunctions(files) - if err != nil { - t.Fatal(err) - } - for _, function := range functions { - if containsString(function.Tokens, marker) { - symbols = append(symbols, function.Symbol) - } - } - return symbols -} - -func containsString(values []string, target string) bool { - for _, value := range values { - if value == target { - return true - } - } - return false -} - -func assertClosureRatchetStable(t *testing.T, original, modified, marker string) { - t.Helper() - before := closureDebtsForMarker(t, original, marker) - after := closureDebtsForMarker(t, modified, marker) - if len(before) != 1 { - t.Fatalf("original debts for %s = %#v", marker, before) - } - var current *baselineEntry - for index := range after { - if after[index].Identity == before[0].Identity { - current = &after[index] - break - } - } - if current == nil || current.Ceiling != before[0].Ceiling { - t.Fatalf("ratcheted closure debt churned: before=%#v after=%#v", before, after) - } - baseline := validBaselineManifest() - baseline.Entries = before - prior := contractBundle{ - baseline: baseline, exceptions: exceptionManifest{SchemaVersion: 1, Entries: []exceptionEntry{}}, - architecture: architectureManifest{SchemaVersion: 1, SourceCommit: baseline.SourceCommit, Entries: []architectureEntry{}}, - } - candidate := prior - candidate.baseline.Entries = []baselineEntry{} - candidate.exceptions.Entries = []exceptionEntry{ratchetException(*current, current.Ceiling)} - if err := compareContractBundles(prior, candidate); err == nil || !strings.Contains(err.Error(), "historical baseline identity") { - t.Fatalf("closure baseline laundering error = %v", err) - } -} - -func closureDebtsForMarker(t *testing.T, source, marker string) []baselineEntry { - t.Helper() - root := t.TempDir() - writeTestFile(t, root, "harness/a.go", source) - files, err := loadHarnessSources(root) - if err != nil { - t.Fatal(err) - } - functions, err := measureFunctions(files) - if err != nil { - t.Fatal(err) - } - var debts []baselineEntry - for _, function := range functions { - if !containsString(function.Tokens, marker) || function.Cognitive <= 25 { - continue - } - debts = append(debts, baselineEntry{ - Rule: ruleCognitive, Identity: functionIdentity(ruleCognitive, function.Path, function.Symbol), - Path: function.Path, Symbol: function.Symbol, Ceiling: function.Cognitive, - }) - } - return debts -} - -func ratchetClosureBody(marker string) string { - var body strings.Builder - fmt.Fprintf(&body, "ok := true; %s(); ", marker) - for index := 0; index < 26; index++ { - fmt.Fprintf(&body, "if ok { ok = false }; ") - } - return body.String() -} diff --git a/harness/tools/quality/constants.go b/harness/tools/quality/constants.go deleted file mode 100644 index 7a0bd043..00000000 --- a/harness/tools/quality/constants.go +++ /dev/null @@ -1,105 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/mnemon-dev/mnemon/harness/tools/corecontract" -) - -const ( - manifestSchemaVersion = 1 - qualityToolVersion = "harness-quality/v1" - duplicateTokenMinimum = 150 -) - -const ( - ruleCyclomatic = "cyclomatic_complexity" - ruleCognitive = "cognitive_complexity" - ruleFunctionLines = "function_logical_lines" - ruleStatements = "function_statements" - ruleNesting = "control_flow_nesting" - ruleProductionFile = "production_file_lines" - rulePairedTestFile = "paired_test_file_lines" - ruleDuplicate = "normalized_duplicate_tokens" -) - -type threshold struct { - Rule string `json:"rule"` - Limit int `json:"limit"` -} - -var qualityThresholds = []threshold{ - {Rule: ruleCognitive, Limit: 25}, - {Rule: ruleNesting, Limit: 4}, - {Rule: ruleCyclomatic, Limit: 20}, - {Rule: ruleFunctionLines, Limit: 80}, - {Rule: ruleStatements, Limit: 50}, - {Rule: ruleDuplicate, Limit: duplicateTokenMinimum - 1}, - {Rule: rulePairedTestFile, Limit: 800}, - {Rule: ruleProductionFile, Limit: 400}, -} - -type baselineManifest struct { - SchemaVersion int `json:"schema_version"` - ToolVersion string `json:"tool_version"` - SourceCommit string `json:"source_commit"` - Thresholds []threshold `json:"thresholds"` - Entries []baselineEntry `json:"entries"` -} - -type baselineEntry struct { - Rule string `json:"rule"` - Identity string `json:"identity"` - Path string `json:"path"` - Symbol string `json:"symbol,omitempty"` - DebtID string `json:"debt_id,omitempty"` - Owners []string `json:"owners,omitempty"` - Fingerprint string `json:"fingerprint,omitempty"` - Ceiling int `json:"ceiling"` -} - -type exceptionManifest struct { - SchemaVersion int `json:"schema_version"` - Entries []exceptionEntry `json:"entries"` -} - -type exceptionEntry struct { - Rule string `json:"rule"` - Identity string `json:"identity"` - Path string `json:"path"` - Symbol string `json:"symbol,omitempty"` - Component string `json:"component,omitempty"` - Ceiling int `json:"ceiling"` - Reason string `json:"reason"` - Risk string `json:"risk"` - Owner string `json:"owner"` - RemovalCheckpoint string `json:"removal_checkpoint"` -} - -type architectureManifest struct { - SchemaVersion int `json:"schema_version"` - SourceCommit string `json:"source_commit"` - Entries []architectureEntry `json:"entries"` -} - -type architectureEntry struct { - Rule string `json:"rule"` - Identity string `json:"identity"` - Path string `json:"path"` - Symbol string `json:"symbol,omitempty"` - Component string `json:"component,omitempty"` - Risk string `json:"risk"` - Evidence string `json:"evidence"` - Owner string `json:"owner"` - RemovalCheckpoint string `json:"removal_checkpoint"` -} - -type requirementsManifest = corecontract.Registry - -func functionIdentity(rule, path, symbol string) string { - return fmt.Sprintf("%s:%s::%s", rule, path, symbol) -} - -func fileIdentity(rule, path string) string { - return rule + ":" + path -} diff --git a/harness/tools/quality/constants_test.go b/harness/tools/quality/constants_test.go deleted file mode 100644 index 0dc9eb5d..00000000 --- a/harness/tools/quality/constants_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package main - -import "testing" - -func TestQualityThresholdsAreCanonical(t *testing.T) { - want := []threshold{ - {Rule: ruleCognitive, Limit: 25}, - {Rule: ruleNesting, Limit: 4}, - {Rule: ruleCyclomatic, Limit: 20}, - {Rule: ruleFunctionLines, Limit: 80}, - {Rule: ruleStatements, Limit: 50}, - {Rule: ruleDuplicate, Limit: 149}, - {Rule: rulePairedTestFile, Limit: 800}, - {Rule: ruleProductionFile, Limit: 400}, - } - if len(qualityThresholds) != len(want) { - t.Fatalf("threshold count = %d, want %d", len(qualityThresholds), len(want)) - } - for i := range want { - if qualityThresholds[i] != want[i] { - t.Fatalf("threshold[%d] = %#v, want %#v", i, qualityThresholds[i], want[i]) - } - } -} - -func TestStableIdentitiesDoNotUseLines(t *testing.T) { - if got := functionIdentity(ruleCyclomatic, "harness/a.go", "(*T).Run"); got != "cyclomatic_complexity:harness/a.go::(*T).Run" { - t.Fatalf("function identity = %q", got) - } - if got := fileIdentity(ruleProductionFile, "harness/a.go"); got != "production_file_lines:harness/a.go" { - t.Fatalf("file identity = %q", got) - } -} diff --git a/harness/tools/quality/dependency.go b/harness/tools/quality/dependency.go deleted file mode 100644 index 6c350fdd..00000000 --- a/harness/tools/quality/dependency.go +++ /dev/null @@ -1,241 +0,0 @@ -package main - -import ( - "bytes" - "fmt" - "go/ast" - "go/parser" - "go/token" - "io/fs" - "os" - "path/filepath" - "sort" - "strconv" - "strings" -) - -type dependencyCollector struct { - root string - scope repositoryGoScope - findings map[string]architectureFinding -} - -type repositoryGoScope struct { - paths map[string]struct{} - directories map[string]struct{} - gitScoped bool -} - -type dependencySource struct { - relative string - packagePath string - layer string - inHarness bool - isProduction bool -} - -func dependencyFindings(root string) ([]architectureFinding, error) { - collector := &dependencyCollector{root: root, scope: loadRepositoryGoScope(root), findings: make(map[string]architectureFinding)} - err := filepath.WalkDir(root, collector.inspectPath) - findings := make([]architectureFinding, 0, len(collector.findings)) - for _, finding := range collector.findings { - findings = append(findings, finding) - } - sort.Slice(findings, func(i, j int) bool { return findings[i].Identity < findings[j].Identity }) - return findings, err -} - -func (collector *dependencyCollector) inspectPath(path string, entry fs.DirEntry, walkErr error) error { - if walkErr != nil { - return walkErr - } - if entry.IsDir() { - if path != collector.root && skipRepositoryDirectory(collector.root, path, entry.Name(), collector.scope) { - return filepath.SkipDir - } - return nil - } - if entry.Type()&os.ModeSymlink != 0 || !strings.HasSuffix(entry.Name(), ".go") { - return nil - } - source, included, err := collector.describeSource(path) - if err != nil || !included { - return err - } - data, err := os.ReadFile(path) - if err != nil { - return err - } - parsed, err := parser.ParseFile(token.NewFileSet(), path, data, parser.ImportsOnly) - if err != nil { - return fmt.Errorf("parse imports in %s: %w", source.relative, err) - } - collector.recordUnexpectedLayer(source) - for _, imported := range parsed.Imports { - if err := collector.recordImport(source, imported); err != nil { - return err - } - } - return nil -} - -func (collector *dependencyCollector) describeSource(path string) (dependencySource, bool, error) { - relative, err := filepath.Rel(collector.root, path) - if err != nil { - return dependencySource{}, false, err - } - relative = filepath.ToSlash(relative) - if collector.scope.gitScoped { - if _, included := collector.scope.paths[relative]; !included { - return dependencySource{}, false, nil - } - } - inHarness := strings.HasPrefix(relative, "harness/") - return dependencySource{ - relative: relative, packagePath: sourcePackagePath(relative), layer: harnessPackage(relative), - inHarness: inHarness, isProduction: !strings.HasSuffix(relative, "_test.go") && !isTestdataPath(relative), - }, true, nil -} - -func isTestdataPath(path string) bool { - for _, part := range strings.Split(filepath.ToSlash(path), "/") { - if part == "testdata" { - return true - } - } - return false -} - -func (collector *dependencyCollector) recordUnexpectedLayer(source dependencySource) { - if source.inHarness && source.isProduction && source.layer != "" && !knownHarnessPackage(source.layer) { - collector.add("unexpected_harness_package", source.packagePath, source.layer, source.relative) - } -} - -func (collector *dependencyCollector) recordImport(source dependencySource, imported *ast.ImportSpec) error { - importPath, err := strconv.Unquote(imported.Path.Value) - if err != nil { - return fmt.Errorf("decode import in %s: %w", source.relative, err) - } - if !source.inHarness && isHarnessImport(importPath) { - collector.add("root_harness_dependency", source.packagePath, importPath, source.relative) - } - if source.inHarness && strings.HasPrefix(importPath, modulePath+"/internal/") { - collector.add("harness_legacy_dependency", source.packagePath, importPath, source.relative) - } - if strings.HasPrefix(importPath, "github.com/libp2p/go-libp2p-core/") { - collector.add("deprecated_libp2p_core", source.packagePath, importPath, source.relative) - } - collector.recordLayerEdge(source, importPath) - return nil -} - -func (collector *dependencyCollector) recordLayerEdge(source dependencySource, importPath string) { - if !source.inHarness || !source.isProduction || !knownHarnessPackage(source.layer) { - return - } - target := importedHarnessPackage(importPath) - if target != "" && !allowedHarnessImport(source.layer, target) { - collector.add("dependency_direction", source.packagePath, target, source.relative) - } -} - -func (collector *dependencyCollector) add(rule, path, component, evidence string) { - identity := rule + ":" + path + "::" + component - finding := architectureFinding{Rule: rule, Identity: identity, Path: path, Component: component, Evidence: evidence} - prior, exists := collector.findings[identity] - if !exists || evidence < prior.Evidence { - collector.findings[identity] = finding - } -} - -func loadRepositoryGoScope(root string) repositoryGoScope { - data, err := runGit(root, "ls-files", "--cached", "--others", "--exclude-standard", "-z") - if err != nil { - return repositoryGoScope{} - } - paths := make(map[string]struct{}) - directories := map[string]struct{}{".": {}} - for _, raw := range bytes.Split(data, []byte{0}) { - path := filepath.ToSlash(string(raw)) - if path != "" && strings.HasSuffix(path, ".go") { - paths[path] = struct{}{} - for directory := filepath.ToSlash(filepath.Dir(path)); directory != "."; directory = filepath.ToSlash(filepath.Dir(directory)) { - directories[directory] = struct{}{} - } - } - } - return repositoryGoScope{paths: paths, directories: directories, gitScoped: true} -} - -func skipRepositoryDirectory(root, absolute, name string, scope repositoryGoScope) bool { - if name == ".git" || name == "vendor" { - return true - } - if !scope.gitScoped { - return strings.HasPrefix(name, ".") - } - relative, err := filepath.Rel(root, absolute) - if err != nil { - return true - } - _, included := scope.directories[filepath.ToSlash(relative)] - return !included -} - -func newArchitectureFinding(rule, path, component string) architectureFinding { - return architectureFinding{Rule: rule, Identity: rule + ":" + path + "::" + component, Path: path, Component: component, Evidence: path} -} - -func sourcePackagePath(relative string) string { - directory := filepath.ToSlash(filepath.Dir(relative)) - if directory == "." { - return relative - } - return directory -} - -func harnessPackage(path string) string { - parts := strings.Split(filepath.ToSlash(path), "/") - if len(parts) >= 3 && parts[0] == "harness" && parts[1] == "cmd" { - return "cmd" - } - if len(parts) >= 3 && parts[0] == "harness" && parts[1] == "internal" { - return parts[2] - } - return "" -} - -func importedHarnessPackage(importPath string) string { - prefix := modulePath + "/harness/internal/" - if !strings.HasPrefix(importPath, prefix) { - return "" - } - return strings.Split(strings.TrimPrefix(importPath, prefix), "/")[0] -} - -func isHarnessImport(importPath string) bool { - return importPath == modulePath+"/harness" || strings.HasPrefix(importPath, modulePath+"/harness/") -} - -func allowedHarnessImport(source, target string) bool { - allowed := map[string]map[string]bool{ - "attach": {"agency": true}, - "cmd": {"attach": true, "cli": true, "daemon": true}, - "cli": {"agency": true}, - "cas": {"agency": true}, - "daemon": {"agency": true, "authority": true, "cas": true, "peerlink": true}, - "peerlink": {"agency": true, "cas": true}, - "agency": {}, "authority": {"agency": true}, "selector": {"agency": true}, - } - return allowed[source][target] -} - -func knownHarnessPackage(name string) bool { - switch name { - case "cmd", "agency", "attach", "authority", "cas", "cli", "daemon", "peerlink", "selector": - return true - default: - return false - } -} diff --git a/harness/tools/quality/dependency_test.go b/harness/tools/quality/dependency_test.go deleted file mode 100644 index c77570ec..00000000 --- a/harness/tools/quality/dependency_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package main - -import "testing" - -func TestAllowedHarnessImportMatchesR7ModuleLayout(t *testing.T) { - for _, edge := range [][2]string{ - {"authority", "agency"}, {"selector", "agency"}, {"cli", "agency"}, - {"cas", "agency"}, {"peerlink", "agency"}, {"peerlink", "cas"}, - {"attach", "agency"}, {"daemon", "agency"}, {"daemon", "authority"}, - {"daemon", "cas"}, {"daemon", "peerlink"}, {"cmd", "attach"}, {"cmd", "cli"}, - {"cmd", "daemon"}, - } { - if !allowedHarnessImport(edge[0], edge[1]) { - t.Errorf("expected allowed edge %s -> %s", edge[0], edge[1]) - } - } - for _, edge := range [][2]string{ - {"agency", "authority"}, {"selector", "authority"}, {"authority", "cas"}, - {"agency", "selector"}, {"cli", "selector"}, {"cli", "daemon"}, - {"cas", "authority"}, - {"peerlink", "authority"}, {"cas", "peerlink"}, {"attach", "authority"}, - {"attach", "cas"}, {"attach", "peerlink"}, {"daemon", "cli"}, - {"cmd", "agency"}, {"cmd", "authority"}, {"cmd", "cas"}, {"cmd", "peerlink"}, - {"cmd", "selector"}, - } { - if allowedHarnessImport(edge[0], edge[1]) { - t.Errorf("unexpected allowed edge %s -> %s", edge[0], edge[1]) - } - } -} diff --git a/harness/tools/quality/duplicate.go b/harness/tools/quality/duplicate.go deleted file mode 100644 index e8d737ce..00000000 --- a/harness/tools/quality/duplicate.go +++ /dev/null @@ -1,171 +0,0 @@ -package main - -import ( - "crypto/sha256" - "encoding/binary" - "encoding/hex" - "fmt" - "sort" - "strings" -) - -const ( - maximumSeedWindows = 2_000_000 - maximumSeedOccurrences = 256 -) - -type duplicateMeasurement struct { - DebtID string - Owners []string - Fingerprint string - Tokens int -} - -type tokenOccurrence struct { - function int - start int -} - -type duplicateCandidate struct { - fingerprint string - tokens int - owners map[string]struct{} -} - -func measureDuplicates(functions []functionMeasurement) ([]duplicateMeasurement, error) { - seeds, err := buildDuplicateSeeds(functions) - if err != nil { - return nil, err - } - candidates := collectDuplicateCandidates(functions, seeds) - return finalizeDuplicates(candidates), nil -} - -func buildDuplicateSeeds(functions []functionMeasurement) (map[[sha256.Size]byte][]tokenOccurrence, error) { - seeds := make(map[[sha256.Size]byte][]tokenOccurrence) - windows := 0 - for functionIndex, function := range functions { - for start := 0; start+duplicateTokenMinimum <= len(function.Tokens); start++ { - windows++ - if windows > maximumSeedWindows { - return nil, fmt.Errorf("duplicate analysis exceeds %d seed windows", maximumSeedWindows) - } - fingerprint := tokenFingerprint(function.Tokens[start : start+duplicateTokenMinimum]) - occurrences := seeds[fingerprint] - if len(occurrences) >= maximumSeedOccurrences { - return nil, fmt.Errorf("duplicate seed %x exceeds %d occurrences", fingerprint[:8], maximumSeedOccurrences) - } - seeds[fingerprint] = append(occurrences, tokenOccurrence{function: functionIndex, start: start}) - } - } - return seeds, nil -} - -func collectDuplicateCandidates(functions []functionMeasurement, seeds map[[sha256.Size]byte][]tokenOccurrence) map[string]*duplicateCandidate { - candidates := make(map[string]*duplicateCandidate) - seenMatches := make(map[string]struct{}) - for _, occurrences := range seeds { - if len(occurrences) < 2 { - continue - } - collectOccurrencePairs(functions, occurrences, candidates, seenMatches) - } - return candidates -} - -func collectOccurrencePairs(functions []functionMeasurement, occurrences []tokenOccurrence, candidates map[string]*duplicateCandidate, seen map[string]struct{}) { - for leftIndex := 0; leftIndex < len(occurrences); leftIndex++ { - for rightIndex := leftIndex + 1; rightIndex < len(occurrences); rightIndex++ { - collectOccurrencePair(functions, occurrences[leftIndex], occurrences[rightIndex], candidates, seen) - } - } -} - -func collectOccurrencePair(functions []functionMeasurement, left, right tokenOccurrence, candidates map[string]*duplicateCandidate, seen map[string]struct{}) { - leftOwner := functionOwner(functions[left.function]) - rightOwner := functionOwner(functions[right.function]) - if leftOwner == rightOwner { - return - } - leftStart, rightStart, length := extendDuplicate(functions[left.function].Tokens, left.start, functions[right.function].Tokens, right.start) - matchKey := duplicateMatchKey(leftOwner, leftStart, rightOwner, rightStart, length) - if _, exists := seen[matchKey]; exists { - return - } - seen[matchKey] = struct{}{} - fingerprintBytes := tokenFingerprint(functions[left.function].Tokens[leftStart : leftStart+length]) - fingerprint := hex.EncodeToString(fingerprintBytes[:]) - candidate := candidates[fingerprint] - if candidate == nil { - candidate = &duplicateCandidate{fingerprint: fingerprint, tokens: length, owners: make(map[string]struct{})} - candidates[fingerprint] = candidate - } - candidate.owners[leftOwner] = struct{}{} - candidate.owners[rightOwner] = struct{}{} -} - -func finalizeDuplicates(candidates map[string]*duplicateCandidate) []duplicateMeasurement { - measured := make([]duplicateMeasurement, 0, len(candidates)) - for _, candidate := range candidates { - owners := sortedSet(candidate.owners) - measured = append(measured, duplicateMeasurement{Owners: owners, Fingerprint: candidate.fingerprint, Tokens: candidate.tokens}) - } - sort.Slice(measured, func(i, j int) bool { - leftOwners := strings.Join(measured[i].Owners, "\x00") - rightOwners := strings.Join(measured[j].Owners, "\x00") - if leftOwners != rightOwners { - return leftOwners < rightOwners - } - return measured[i].Fingerprint < measured[j].Fingerprint - }) - for index := range measured { - measured[index].DebtID = fmt.Sprintf("dup-%04d", index+1) - } - return measured -} - -func tokenFingerprint(tokens []string) [sha256.Size]byte { - digest := sha256.New() - var length [4]byte - for _, item := range tokens { - binary.BigEndian.PutUint32(length[:], uint32(len(item))) - digest.Write(length[:]) - digest.Write([]byte(item)) - } - var result [sha256.Size]byte - copy(result[:], digest.Sum(nil)) - return result -} - -func extendDuplicate(left []string, leftStart int, right []string, rightStart int) (int, int, int) { - for leftStart > 0 && rightStart > 0 && left[leftStart-1] == right[rightStart-1] { - leftStart-- - rightStart-- - } - length := duplicateTokenMinimum - for leftStart+length < len(left) && rightStart+length < len(right) && left[leftStart+length] == right[rightStart+length] { - length++ - } - return leftStart, rightStart, length -} - -func duplicateMatchKey(leftOwner string, leftStart int, rightOwner string, rightStart int, length int) string { - if rightOwner < leftOwner { - leftOwner, rightOwner = rightOwner, leftOwner - leftStart, rightStart = rightStart, leftStart - } - return fmt.Sprintf("%s\x00%d\x00%s\x00%d\x00%d", leftOwner, leftStart, rightOwner, rightStart, length) -} - -func functionOwner(function functionMeasurement) string { - return function.Path + "::" + function.Symbol -} - -func sortedSet(values map[string]struct{}) []string { - result := make([]string, 0, len(values)) - for value := range values { - result = append(result, value) - } - sort.Strings(result) - return result -} diff --git a/harness/tools/quality/duplicate_test.go b/harness/tools/quality/duplicate_test.go deleted file mode 100644 index 7598ccab..00000000 --- a/harness/tools/quality/duplicate_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package main - -import ( - "fmt" - "strings" - "testing" -) - -func TestMeasureDuplicatesIsDeterministic(t *testing.T) { - root := t.TempDir() - body := duplicateFixtureBody(55) - writeTestFile(t, root, "harness/b.go", "package harness\nfunc second(seed int) int {\n"+body+"return seed\n}\n") - writeTestFile(t, root, "harness/a.go", "package harness\nfunc first(seed int) int {\n"+body+"return seed\n}\n") - files, err := loadHarnessSources(root) - if err != nil { - t.Fatal(err) - } - functions, err := measureFunctions(files) - if err != nil { - t.Fatal(err) - } - first, err := measureDuplicates(functions) - if err != nil { - t.Fatal(err) - } - second, err := measureDuplicates(functions) - if err != nil { - t.Fatal(err) - } - if fmt.Sprintf("%#v", first) != fmt.Sprintf("%#v", second) { - t.Fatalf("duplicate result changed: %#v != %#v", first, second) - } - if len(first) != 1 || first[0].DebtID != "dup-0001" || first[0].Tokens < duplicateTokenMinimum { - t.Fatalf("duplicates = %#v", first) - } - wantOwners := []string{"harness/a.go::first", "harness/b.go::second"} - if strings.Join(first[0].Owners, "|") != strings.Join(wantOwners, "|") { - t.Fatalf("owners = %#v", first[0].Owners) - } -} - -func TestMeasureDuplicatesIgnoresShortBlocks(t *testing.T) { - functions := []functionMeasurement{ - {Path: "harness/a.go", Symbol: "a", Tokens: repeatedTokens(duplicateTokenMinimum - 1)}, - {Path: "harness/b.go", Symbol: "b", Tokens: repeatedTokens(duplicateTokenMinimum - 1)}, - } - duplicates, err := measureDuplicates(functions) - if err != nil { - t.Fatal(err) - } - if len(duplicates) != 0 { - t.Fatalf("duplicates = %#v", duplicates) - } -} - -func duplicateFixtureBody(statements int) string { - var body strings.Builder - for index := 0; index < statements; index++ { - fmt.Fprintf(&body, "value%d := seed + %d\nseed = value%d\n", index, index, index) - } - return body.String() -} - -func repeatedTokens(count int) []string { - tokens := make([]string, count) - for index := range tokens { - tokens[index] = fmt.Sprintf("token-%d", index) - } - return tokens -} diff --git a/harness/tools/quality/evidence.go b/harness/tools/quality/evidence.go deleted file mode 100644 index ee548520..00000000 --- a/harness/tools/quality/evidence.go +++ /dev/null @@ -1,124 +0,0 @@ -package main - -import ( - "fmt" - "go/ast" - "go/parser" - "go/token" - "os" - "path/filepath" - - "github.com/mnemon-dev/mnemon/harness/tools/corecontract" -) - -func validateArchitectureEvidence(root string, manifest architectureManifest, findings []architectureFinding) error { - entries := make(map[string]architectureEntry, len(manifest.Entries)) - for _, entry := range manifest.Entries { - if err := pathEvidenceExists(root, entry.Path, entry.Symbol); err != nil { - return fmt.Errorf("architecture debt %s path evidence: %w", entry.Identity, err) - } - evidencePath, evidenceSymbol, _ := parseEvidence(entry.Evidence) - if err := pathEvidenceExists(root, evidencePath, evidenceSymbol); err != nil { - return fmt.Errorf("architecture debt %s evidence: %w", entry.Identity, err) - } - entries[entry.Identity] = entry - } - staticFindings := make(map[string]struct{}, len(findings)) - for _, finding := range findings { - staticFindings[finding.Identity] = struct{}{} - entry, exists := entries[finding.Identity] - if !exists { - return fmt.Errorf("untracked architecture violation %s", finding.Identity) - } - if entry.Rule != finding.Rule || entry.Path != finding.Path || entry.Component != finding.Component || entry.Evidence != finding.Evidence { - return fmt.Errorf("architecture debt %s does not exactly describe its static finding", finding.Identity) - } - } - for _, entry := range manifest.Entries { - if !automaticArchitectureRule(entry.Rule) { - continue - } - if _, exists := staticFindings[entry.Identity]; !exists { - return fmt.Errorf("stale auto-detected architecture debt %s has no current static finding", entry.Identity) - } - } - return nil -} - -func automaticArchitectureRule(rule string) bool { - switch rule { - case "dependency_direction", "unexpected_harness_package", "root_harness_dependency", "harness_legacy_dependency", "deprecated_libp2p_core": - return true - default: - return false - } -} - -func validateRequirementEvidence(root string, contract corecontract.Contract, - requirements requirementsManifest, -) error { - return corecontract.ValidateBindings(root, contract, requirements) -} - -func pathEvidenceExists(root, relative, symbol string) error { - absolute := filepath.Join(root, filepath.FromSlash(relative)) - info, err := os.Stat(absolute) - if err != nil { - return fmt.Errorf("%s does not exist", relative) - } - if symbol == "" { - return nil - } - if info.IsDir() || filepath.Ext(absolute) != ".go" { - return fmt.Errorf("%s cannot provide Go symbol %s", relative, symbol) - } - parsed, err := parser.ParseFile(token.NewFileSet(), absolute, nil, parser.SkipObjectResolution) - if err != nil { - return fmt.Errorf("parse %s: %w", relative, err) - } - if fileDeclaresSymbol(parsed, symbol) { - return nil - } - return fmt.Errorf("%s does not declare symbol %s", relative, symbol) -} - -func fileDeclaresSymbol(file *ast.File, symbol string) bool { - for _, declaration := range file.Decls { - if declarationDeclaresSymbol(declaration, symbol) { - return true - } - } - return false -} - -func declarationDeclaresSymbol(declaration ast.Decl, symbol string) bool { - if function, ok := declaration.(*ast.FuncDecl); ok { - return functionSymbol(function) == symbol - } - general, ok := declaration.(*ast.GenDecl) - if !ok { - return false - } - for _, specification := range general.Specs { - if specificationDeclaresSymbol(specification, symbol) { - return true - } - } - return false -} - -func specificationDeclaresSymbol(specification ast.Spec, symbol string) bool { - if value, ok := specification.(*ast.TypeSpec); ok { - return value.Name.Name == symbol - } - value, ok := specification.(*ast.ValueSpec) - if !ok { - return false - } - for _, name := range value.Names { - if name.Name == symbol { - return true - } - } - return false -} diff --git a/harness/tools/quality/evidence_test.go b/harness/tools/quality/evidence_test.go deleted file mode 100644 index fa64daee..00000000 --- a/harness/tools/quality/evidence_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package main - -import ( - "path/filepath" - "strings" - "testing" - - "github.com/mnemon-dev/mnemon/harness/tools/corecontract" -) - -func TestValidateArchitectureEvidenceRequiresTrackedFindingAndLiveSymbol(t *testing.T) { - root := t.TempDir() - writeTestFile(t, root, "harness/a.go", "package harness\nfunc Run() {}\n") - finding := newArchitectureFinding("dependency_direction", "harness/a.go", "legacy") - entry := architectureEntry{ - Rule: finding.Rule, Identity: finding.Identity, Path: finding.Path, Component: finding.Component, - Risk: "high", Evidence: finding.Evidence, Owner: "team", RemovalCheckpoint: "7R", - } - if err := validateArchitectureEvidence(root, architectureManifest{Entries: []architectureEntry{entry}}, []architectureFinding{finding}); err != nil { - t.Fatal(err) - } - entry.Evidence = "harness/a.go::Missing" - if err := validateArchitectureEvidence(root, architectureManifest{Entries: []architectureEntry{entry}}, nil); err == nil || !strings.Contains(err.Error(), "does not declare") { - t.Fatalf("missing symbol error = %v", err) - } - if err := validateArchitectureEvidence(root, architectureManifest{Entries: []architectureEntry{}}, []architectureFinding{finding}); err == nil || !strings.Contains(err.Error(), "untracked") { - t.Fatalf("untracked finding error = %v", err) - } -} - -func TestValidateArchitectureEvidenceRejectsStaleAutoFindingButAllowsManualDebt(t *testing.T) { - root := t.TempDir() - writeTestFile(t, root, "harness/a.go", "package harness\nfunc Run() {}\n") - automatic := newArchitectureFinding("dependency_direction", "harness/a.go", "legacy") - automaticEntry := architectureEntry{ - Rule: automatic.Rule, Identity: automatic.Identity, Path: automatic.Path, Component: automatic.Component, - Risk: "medium", Evidence: automatic.Evidence, Owner: "team", RemovalCheckpoint: "7R", - } - if err := validateArchitectureEvidence(root, architectureManifest{Entries: []architectureEntry{automaticEntry}}, nil); err == nil || !strings.Contains(err.Error(), "stale auto-detected") { - t.Fatalf("stale automatic debt error = %v", err) - } - manual := architectureEntry{ - Rule: "goroutine_ownership", Identity: "goroutine_ownership:run", Path: "harness/a.go", Symbol: "Run", - Risk: "medium", Evidence: "harness/a.go::Run", Owner: "team", RemovalCheckpoint: "7R", - } - if err := validateArchitectureEvidence(root, architectureManifest{Entries: []architectureEntry{manual}}, nil); err != nil { - t.Fatalf("manual evidence-only debt: %v", err) - } -} - -func TestValidateRequirementEvidenceMatchesInvariantIDsAndCurrentTests(t *testing.T) { - root := filepath.Clean("../../..") - contract, err := corecontract.Load(root) - if err != nil { - t.Fatal(err) - } - requirements, err := corecontract.LoadRegistry(root) - if err != nil { - t.Fatal(err) - } - if err := validateRequirementEvidence(root, contract, requirements); err != nil { - t.Fatal(err) - } - requirements.Invariants[0].ID = "P-99" - if err := validateRequirementEvidence(root, contract, requirements); err == nil || - !strings.Contains(err.Error(), "invariant IDs") { - t.Fatalf("unknown invariant error = %v", err) - } -} diff --git a/harness/tools/quality/exclusion.go b/harness/tools/quality/exclusion.go deleted file mode 100644 index 076fc7d0..00000000 --- a/harness/tools/quality/exclusion.go +++ /dev/null @@ -1,116 +0,0 @@ -package main - -import ( - "fmt" - "os" - "path/filepath" - "sort" - "strings" -) - -const exclusionsPath = "harness/test/contracts/go_quality_exclusions.json" - -const ( - exclusionGenerated = "generated" - exclusionTestdata = "testdata" -) - -type exclusionManifest struct { - SchemaVersion int `json:"schema_version"` - Entries []exclusionEntry `json:"entries"` -} - -type exclusionEntry struct { - Path string `json:"path"` - Kind string `json:"kind"` - Reason string `json:"reason"` - Owner string `json:"owner"` -} - -func loadQualityExclusions(root string, required bool) (exclusionManifest, error) { - path := filepath.Join(root, filepath.FromSlash(exclusionsPath)) - manifest, err := readExactJSON[exclusionManifest](path) - if err == nil { - return manifest, validateExclusionManifest(manifest) - } - if !required && os.IsNotExist(unwrapPathError(err)) { - return exclusionManifest{SchemaVersion: manifestSchemaVersion, Entries: []exclusionEntry{}}, nil - } - return exclusionManifest{}, err -} - -func unwrapPathError(err error) error { - for err != nil { - pathError, ok := err.(*os.PathError) - if ok { - return pathError - } - unwrapper, ok := err.(interface{ Unwrap() error }) - if !ok { - return err - } - err = unwrapper.Unwrap() - } - return nil -} - -func validateExclusionManifest(manifest exclusionManifest) error { - if err := validateSchema(manifest.SchemaVersion, "quality exclusions"); err != nil { - return err - } - if manifest.Entries == nil { - return fmt.Errorf("quality exclusion entries must be a JSON array, not null") - } - if !sort.SliceIsSorted(manifest.Entries, func(i, j int) bool { return manifest.Entries[i].Path < manifest.Entries[j].Path }) { - return fmt.Errorf("quality exclusion entries must be sorted by path") - } - for index, entry := range manifest.Entries { - if index > 0 && manifest.Entries[index-1].Path == entry.Path { - return fmt.Errorf("quality exclusions repeat path %s", entry.Path) - } - if err := validateExclusionEntry(entry); err != nil { - return fmt.Errorf("quality exclusion %s: %w", entry.Path, err) - } - } - return nil -} - -func validateExclusionEntry(entry exclusionEntry) error { - if err := validateHarnessPath(entry.Path, "path"); err != nil { - return err - } - if !strings.HasSuffix(entry.Path, ".go") { - return fmt.Errorf("path must identify one exact Go file") - } - if entry.Kind != exclusionGenerated && entry.Kind != exclusionTestdata { - return fmt.Errorf("unsupported kind %q", entry.Kind) - } - if entry.Kind == exclusionTestdata && !strings.Contains(entry.Path, "/testdata/") { - return fmt.Errorf("testdata exclusion path is not inside testdata") - } - if err := requireText(entry.Reason, "reason"); err != nil { - return err - } - return requireText(entry.Owner, "owner") -} - -func validateExclusionEvidence(root string, manifest exclusionManifest) error { - for _, entry := range manifest.Entries { - data, err := os.ReadFile(filepath.Join(root, filepath.FromSlash(entry.Path))) - if err != nil { - return fmt.Errorf("quality exclusion %s is stale: %w", entry.Path, err) - } - if entry.Kind == exclusionGenerated && !isGeneratedSource(data) { - return fmt.Errorf("quality exclusion %s lacks a canonical generated header", entry.Path) - } - } - return nil -} - -func exclusionKinds(manifest exclusionManifest) map[string]string { - kinds := make(map[string]string, len(manifest.Entries)) - for _, entry := range manifest.Entries { - kinds[entry.Path] = entry.Kind - } - return kinds -} diff --git a/harness/tools/quality/exclusion_test.go b/harness/tools/quality/exclusion_test.go deleted file mode 100644 index 6dc2e451..00000000 --- a/harness/tools/quality/exclusion_test.go +++ /dev/null @@ -1,100 +0,0 @@ -package main - -import ( - "fmt" - "strings" - "testing" -) - -func TestExclusionManifestRequiresExactTrackedKinds(t *testing.T) { - manifest := exclusionManifest{SchemaVersion: 1, Entries: []exclusionEntry{ - {Path: "harness/internal/generated.go", Kind: exclusionGenerated, Reason: "generated protocol projection", Owner: "model"}, - {Path: "harness/internal/testdata/fixture.go", Kind: exclusionTestdata, Reason: "parser fixture", Owner: "testkit"}, - }} - if err := validateExclusionManifest(manifest); err != nil { - t.Fatal(err) - } - manifest.Entries[1].Path = "harness/internal/zfixture.go" - if err := validateExclusionManifest(manifest); err == nil || !strings.Contains(err.Error(), "not inside testdata") { - t.Fatalf("testdata path error = %v", err) - } -} - -func TestExclusionManifestIsOptionalForMeasurementButRequiredForRepositoryCheck(t *testing.T) { - root := t.TempDir() - manifest, err := loadQualityExclusions(root, false) - if err != nil || manifest.SchemaVersion != 1 || len(manifest.Entries) != 0 { - t.Fatalf("optional exclusion manifest = %#v, error = %v", manifest, err) - } - if _, err := loadQualityExclusions(root, true); err == nil || !strings.Contains(err.Error(), "go_quality_exclusions.json") { - t.Fatalf("required exclusion error = %v", err) - } - writeTestFile(t, root, exclusionsPath, "{\"schema_version\":1,\"entries\":[]}") - if _, err := loadQualityExclusions(root, true); err == nil || !strings.Contains(err.Error(), "canonical") { - t.Fatalf("noncanonical exclusion error = %v", err) - } -} - -func TestExclusionEvidenceRejectsForgedGeneratedClassification(t *testing.T) { - root := t.TempDir() - writeTestFile(t, root, "harness/forged.go", "package harness\nconst text = `Code generated DO NOT EDIT.`\n") - manifest := exclusionManifest{SchemaVersion: 1, Entries: []exclusionEntry{{ - Path: "harness/forged.go", Kind: exclusionGenerated, Reason: "claimed generated", Owner: "test", - }}} - if err := validateExclusionEvidence(root, manifest); err == nil || !strings.Contains(err.Error(), "canonical generated header") { - t.Fatalf("forged generated evidence error = %v", err) - } -} - -func TestGeneratedMetricExclusionDoesNotHideNolintOrDependencies(t *testing.T) { - root := t.TempDir() - writeTestFile(t, root, "harness/internal/cli/generated.go", `// Code generated by fixture. DO NOT EDIT. -package cli - -import _ "github.com/mnemon-dev/mnemon/harness/internal/authority" - -//nolint -func generated() {} -`) - writeCanonicalTestFile(t, root, exclusionsPath, exclusionManifest{SchemaVersion: 1, Entries: []exclusionEntry{{ - Path: "harness/internal/cli/generated.go", Kind: exclusionGenerated, Reason: "generated fixture", Owner: "test", - }}}) - files, err := loadHarnessSources(root) - if err != nil { - t.Fatal(err) - } - if len(metricEligibleSources(files)) != 0 { - t.Fatalf("generated file remained metric eligible: %#v", sourcePaths(files)) - } - if diagnostics := nolintDiagnostics(files); len(diagnostics) != 1 { - t.Fatalf("generated nolint diagnostics = %#v", diagnostics) - } - findings, err := dependencyFindings(root) - if err != nil { - t.Fatal(err) - } - if len(findings) != 1 || findings[0].Rule != "dependency_direction" { - t.Fatalf("generated dependency findings = %#v", findings) - } -} - -func TestCanonicalHeaderAloneCannotHideMeasuredDebt(t *testing.T) { - root := t.TempDir() - var source strings.Builder - source.WriteString("// Code generated by fixture. DO NOT EDIT.\npackage harness\n") - for index := 0; index < 410; index++ { - fmt.Fprintln(&source, "// measured") - } - writeTestFile(t, root, "harness/generated.go", source.String()) - manifest, err := measureTree(root, strings.Repeat("a", 40)) - if err != nil { - t.Fatal(err) - } - want := fileIdentity(ruleProductionFile, "harness/generated.go") - for _, entry := range manifest.Entries { - if entry.Identity == want { - return - } - } - t.Fatalf("header-only generated source did not produce %s: %#v", want, manifest.Entries) -} diff --git a/harness/tools/quality/history.go b/harness/tools/quality/history.go deleted file mode 100644 index bcae758e..00000000 --- a/harness/tools/quality/history.go +++ /dev/null @@ -1,162 +0,0 @@ -package main - -import ( - "fmt" - "os/exec" - "reflect" -) - -func runGit(root string, arguments ...string) ([]byte, error) { - command := exec.Command("git", append([]string{"-C", root}, arguments...)...) - output, err := command.Output() - if err == nil { - return output, nil - } - if exit, ok := err.(*exec.ExitError); ok { - return nil, fmt.Errorf("git %v: %s", arguments, exit.Stderr) - } - return nil, fmt.Errorf("run git %v: %w", arguments, err) -} - -func gitManifest[T any](root, reference, path string) (T, bool, error) { - var zero T - command := exec.Command("git", "-C", root, "show", reference+":"+path) - data, err := command.Output() - if err != nil { - if exit, ok := err.(*exec.ExitError); ok && exit.ExitCode() != 0 { - return zero, false, nil - } - return zero, false, fmt.Errorf("read %s at %s: %w", path, reference, err) - } - manifest, err := decodeExactJSON[T](data, reference+":"+path) - if err != nil { - return zero, false, err - } - canonical, err := canonicalJSON(manifest) - if err != nil { - return zero, false, err - } - if !reflect.DeepEqual(data, canonical) { - return zero, false, fmt.Errorf("%s at %s is not canonical JSON", path, reference) - } - return manifest, true, nil -} - -func compareBaseBaseline(base, candidate baselineManifest) error { - if candidate.SourceCommit != base.SourceCommit { - return fmt.Errorf("quality baseline source_commit changed from %s to %s", base.SourceCommit, candidate.SourceCommit) - } - baseEntries := make(map[string]baselineEntry, len(base.Entries)) - for _, entry := range base.Entries { - baseEntries[entry.Identity] = entry - } - for _, entry := range candidate.Entries { - prior, exists := baseEntries[entry.Identity] - if !exists { - return fmt.Errorf("quality baseline adds identity %s relative to base", entry.Identity) - } - if entry.Rule != prior.Rule || entry.Symbol != prior.Symbol || entry.DebtID != prior.DebtID { - return fmt.Errorf("quality baseline rebinds identity %s relative to base", entry.Identity) - } - if entry.Rule == ruleDuplicate { - if err := validateDuplicateEvolution(prior, entry); err != nil { - return err - } - } else if entry.Path != prior.Path { - return fmt.Errorf("quality baseline rebinds identity %s relative to base", entry.Identity) - } - if entry.Ceiling > prior.Ceiling { - return fmt.Errorf("quality baseline raises %s from %d to %d", entry.Identity, prior.Ceiling, entry.Ceiling) - } - } - return nil -} - -func validateDuplicateEvolution(prior, current baselineEntry) error { - ownersChanged := !reflect.DeepEqual(prior.Owners, current.Owners) - if prior.Fingerprint != current.Fingerprint { - return fmt.Errorf("duplicate debt %s cannot rebind fingerprint", current.Identity) - } - if !ownersChanged { - if current.Path != prior.Path { - return fmt.Errorf("duplicate debt %s rebinds its derived path without owner cleanup", current.Identity) - } - return nil - } - priorOwners := make(map[string]struct{}, len(prior.Owners)) - for _, owner := range prior.Owners { - priorOwners[owner] = struct{}{} - } - for _, owner := range current.Owners { - if _, existed := priorOwners[owner]; !existed { - return fmt.Errorf("duplicate debt %s adds or rebinds owner %s", current.Identity, owner) - } - } - if len(current.Owners) >= len(prior.Owners) { - return fmt.Errorf("duplicate debt %s owner change is not a strict cleanup", current.Identity) - } - if len(current.Owners) < 2 { - return fmt.Errorf("duplicate debt %s owner cleanup leaves fewer than two owners", current.Identity) - } - path, _, err := parsePathSymbol(current.Owners[0]) - if err != nil || current.Path != path { - return fmt.Errorf("duplicate debt %s path does not follow its first remaining owner", current.Identity) - } - return nil -} - -func compareBaseArchitecture(base, candidate architectureManifest) error { - if candidate.SourceCommit != base.SourceCommit { - return fmt.Errorf("architecture debt source_commit changed from %s to %s", base.SourceCommit, candidate.SourceCommit) - } - baseEntries := make(map[string]architectureEntry, len(base.Entries)) - for _, entry := range base.Entries { - baseEntries[entry.Identity] = entry - } - for _, entry := range candidate.Entries { - prior, exists := baseEntries[entry.Identity] - if !exists { - return fmt.Errorf("architecture debt adds identity %s relative to base", entry.Identity) - } - if entry.Rule != prior.Rule || entry.Path != prior.Path || entry.Symbol != prior.Symbol || entry.Component != prior.Component { - return fmt.Errorf("architecture debt rebinds identity %s relative to base", entry.Identity) - } - if riskRank(entry.Risk) > riskRank(prior.Risk) { - return fmt.Errorf("architecture debt upgrades %s risk from %s to %s", entry.Identity, prior.Risk, entry.Risk) - } - } - return nil -} - -func compareBaseExceptions(base, candidate exceptionManifest) error { - baseEntries := make(map[string]exceptionEntry, len(base.Entries)) - for _, entry := range base.Entries { - baseEntries[entry.Identity] = entry - } - for _, entry := range candidate.Entries { - prior, exists := baseEntries[entry.Identity] - if !exists { - continue - } - if entry.Rule != prior.Rule || entry.Path != prior.Path || entry.Symbol != prior.Symbol || entry.Component != prior.Component { - return fmt.Errorf("quality exception %s broadens or rebinds its scope", entry.Identity) - } - if entry.Ceiling > prior.Ceiling { - return fmt.Errorf("quality exception %s raises ceiling from %d to %d", entry.Identity, prior.Ceiling, entry.Ceiling) - } - } - return nil -} - -func riskRank(risk string) int { - switch risk { - case "critical": - return 3 - case "high": - return 2 - case "medium": - return 1 - default: - return 0 - } -} diff --git a/harness/tools/quality/history_chain.go b/harness/tools/quality/history_chain.go deleted file mode 100644 index 84476405..00000000 --- a/harness/tools/quality/history_chain.go +++ /dev/null @@ -1,360 +0,0 @@ -package main - -import ( - "fmt" - "strings" -) - -type ratchetLedger struct { - baselineIdentities map[string]struct{} - exceptions map[string]exceptionEntry - activeExceptions map[string]struct{} - exceptionTombstones map[string]struct{} -} - -func newRatchetLedger() *ratchetLedger { - return &ratchetLedger{ - baselineIdentities: make(map[string]struct{}), - exceptions: make(map[string]exceptionEntry), - activeExceptions: make(map[string]struct{}), - exceptionTombstones: make(map[string]struct{}), - } -} - -type manifestCommit struct { - hash string - parents []string -} - -type manifestLineage struct { - bundle contractBundle - found bool - ledger *ratchetLedger -} - -func validateCommittedManifestChain(root, baseReference string) error { - _, err := committedManifestLedgerTo(root, baseReference, "HEAD") - return err -} - -func committedManifestLedger(root, anchorReference string) (*ratchetLedger, error) { - return committedManifestLedgerTo(root, anchorReference, "HEAD") -} - -func committedManifestLedgerTo(root, anchorReference, targetReference string) (*ratchetLedger, error) { - anchorHash, err := resolveCommitHash(root, anchorReference) - if err != nil { - return nil, err - } - commits, err := manifestHistoryCommits(root, anchorReference, targetReference) - if err != nil { - return nil, err - } - anchor, err := loadBoundaryLineage(root, anchorHash) - if err != nil { - return nil, err - } - states := map[string]manifestLineage{anchorHash: anchor} - boundaries := make(map[string]manifestLineage) - for _, commit := range commits { - state, err := buildManifestLineage(root, commit, states, boundaries) - if err != nil { - return nil, err - } - states[commit.hash] = state - } - targetHash, err := resolveCommitHash(root, targetReference) - if err != nil { - return nil, err - } - if targetHash == anchorHash { - return anchor.ledger, nil - } - state, exists := states[targetHash] - if !exists { - state, err = loadBoundaryLineage(root, targetHash) - if err != nil { - return nil, err - } - } - return state.ledger, nil -} - -func resolveCommitHash(root, reference string) (string, error) { - data, err := runGit(root, "rev-parse", reference+"^{commit}") - if err != nil { - return "", err - } - return strings.TrimSpace(string(data)), nil -} - -func buildManifestLineage( - root string, - commit manifestCommit, - states map[string]manifestLineage, - boundaries map[string]manifestLineage, -) (manifestLineage, error) { - parents, err := loadParentLineages(root, commit.parents, states, boundaries) - if err != nil { - return manifestLineage{}, err - } - ledger, err := mergeRatchetLedgers(parents) - if err != nil { - return manifestLineage{}, err - } - current, found, err := loadContractBundleAtRef(root, commit.hash) - if err != nil { - return manifestLineage{}, err - } - foundParents, err := validateManifestHistoryEdges(commit.hash, current, found, parents) - if err != nil { - return manifestLineage{}, err - } - if found && foundParents == 0 { - if err := validateBootstrapCommit(root, commit.hash, current); err != nil { - return manifestLineage{}, err - } - } - if found { - if err := ledger.observe(current); err != nil { - return manifestLineage{}, fmt.Errorf("manifest history at %s: %w", commit.hash, err) - } - } - return manifestLineage{bundle: current, found: found, ledger: ledger}, nil -} - -func loadParentLineages( - root string, - parentHashes []string, - states map[string]manifestLineage, - boundaries map[string]manifestLineage, -) ([]manifestLineage, error) { - parents := make([]manifestLineage, 0, len(parentHashes)) - for _, parentHash := range parentHashes { - state, exists := states[parentHash] - if !exists { - state, exists = boundaries[parentHash] - } - if !exists { - var err error - state, err = loadBoundaryLineage(root, parentHash) - if err != nil { - return nil, err - } - boundaries[parentHash] = state - } - parents = append(parents, state) - } - return parents, nil -} - -func validateManifestHistoryEdges( - commitHash string, - current contractBundle, - found bool, - parents []manifestLineage, -) (int, error) { - foundParents := 0 - for _, parent := range parents { - if !parent.found { - continue - } - foundParents++ - if !found { - return 0, fmt.Errorf("manifest history at %s removes the complete contract bundle", commitHash) - } - if err := compareContractBundles(parent.bundle, current); err != nil { - return 0, fmt.Errorf("manifest history edge into %s: %w", commitHash, err) - } - } - return foundParents, nil -} - -func (ledger *ratchetLedger) observe(bundle contractBundle) error { - for _, entry := range bundle.baseline.Entries { - ledger.baselineIdentities[entry.Identity] = struct{}{} - } - current := make(map[string]struct{}, len(bundle.exceptions.Entries)) - for _, entry := range bundle.exceptions.Entries { - current[entry.Identity] = struct{}{} - } - for identity := range ledger.activeExceptions { - if _, remains := current[identity]; !remains { - ledger.exceptionTombstones[identity] = struct{}{} - } - } - nextActive := make(map[string]struct{}, len(current)) - for _, entry := range bundle.exceptions.Entries { - if _, wasBaseline := ledger.baselineIdentities[entry.Identity]; wasBaseline { - return fmt.Errorf("historical baseline identity %s cannot move to exceptions", entry.Identity) - } - if _, removed := ledger.exceptionTombstones[entry.Identity]; removed { - return fmt.Errorf("historical quality exception %s cannot be resurrected after removal", entry.Identity) - } - prior, seen := ledger.exceptions[entry.Identity] - if seen { - if entry.Rule != prior.Rule || entry.Path != prior.Path || entry.Symbol != prior.Symbol || entry.Component != prior.Component { - return fmt.Errorf("historical quality exception %s cannot rebind its scope", entry.Identity) - } - if entry.Ceiling > prior.Ceiling { - return fmt.Errorf("historical quality exception %s raises ceiling from %d to %d", entry.Identity, prior.Ceiling, entry.Ceiling) - } - } - ledger.exceptions[entry.Identity] = entry - nextActive[entry.Identity] = struct{}{} - } - ledger.activeExceptions = nextActive - return nil -} - -func loadBoundaryLineage(root, reference string) (manifestLineage, error) { - bundle, found, err := loadContractBundleAtRef(root, reference) - if err != nil { - return manifestLineage{}, err - } - ledger := newRatchetLedger() - if found { - if err := validateRatchetManifests(bundle); err != nil { - return manifestLineage{}, fmt.Errorf("manifest history at %s: %w", reference, err) - } - if err := ledger.observe(bundle); err != nil { - return manifestLineage{}, fmt.Errorf("manifest history at %s: %w", reference, err) - } - } - return manifestLineage{bundle: bundle, found: found, ledger: ledger}, nil -} - -func mergeRatchetLedgers(parents []manifestLineage) (*ratchetLedger, error) { - merged := newRatchetLedger() - for _, parent := range parents { - for identity := range parent.ledger.baselineIdentities { - merged.baselineIdentities[identity] = struct{}{} - } - for identity := range parent.ledger.activeExceptions { - merged.activeExceptions[identity] = struct{}{} - } - for identity := range parent.ledger.exceptionTombstones { - merged.exceptionTombstones[identity] = struct{}{} - } - for identity, entry := range parent.ledger.exceptions { - prior, exists := merged.exceptions[identity] - if exists { - if entry.Rule != prior.Rule || entry.Path != prior.Path || entry.Symbol != prior.Symbol || entry.Component != prior.Component { - return nil, fmt.Errorf("historical quality exception %s has conflicting parent scopes", identity) - } - if entry.Ceiling >= prior.Ceiling { - continue - } - } - merged.exceptions[identity] = entry - } - } - for identity := range merged.exceptionTombstones { - delete(merged.activeExceptions, identity) - } - return merged, nil -} - -func manifestHistoryCommits(root, anchorReference, targetReference string) ([]manifestCommit, error) { - arguments := []string{"rev-list", "--reverse", "--topo-order", "--parents", "--full-history", anchorReference + ".." + targetReference} - data, err := runGit(root, arguments...) - if err != nil { - return nil, err - } - lines := strings.Split(strings.TrimSpace(string(data)), "\n") - commits := make([]manifestCommit, 0, len(lines)) - for _, line := range lines { - fields := strings.Fields(line) - if len(fields) == 0 { - continue - } - commits = append(commits, manifestCommit{hash: fields[0], parents: append([]string(nil), fields[1:]...)}) - } - return commits, nil -} - -func loadContractBundleAtRef(root, reference string) (contractBundle, bool, error) { - baseline, baselineFound, err := gitManifest[baselineManifest](root, reference, baselinePath) - if err != nil { - return contractBundle{}, false, err - } - exceptions, exceptionsFound, err := gitManifest[exceptionManifest](root, reference, exceptionsPath) - if err != nil { - return contractBundle{}, false, err - } - architecture, architectureFound, err := gitManifest[architectureManifest](root, reference, architecturePath) - if err != nil { - return contractBundle{}, false, err - } - foundCount := boolCount(baselineFound) + boolCount(exceptionsFound) + boolCount(architectureFound) - if foundCount == 0 { - return contractBundle{}, false, nil - } - if foundCount != 3 { - return contractBundle{}, false, fmt.Errorf("%s contains a partial quality manifest set", reference) - } - return contractBundle{baseline: baseline, exceptions: exceptions, architecture: architecture}, true, nil -} - -func validateBootstrapCommit(root, commit string, bundle contractBundle) error { - if err := validateRatchetManifests(bundle); err != nil { - return err - } - parents, err := runGit(root, "rev-list", "--parents", "-n", "1", commit) - if err != nil { - return err - } - fields := strings.Fields(string(parents)) - for _, parent := range fields[1:] { - if bundle.baseline.SourceCommit == parent && bundle.architecture.SourceCommit == parent { - return nil - } - } - return fmt.Errorf("bootstrap manifest commit %s source_commit is not an exact parent", commit) -} - -func compareContractBundles(prior, current contractBundle) error { - if err := validateRatchetManifests(current); err != nil { - return err - } - if err := forbidBaselineExceptionConversion(prior.baseline, current.exceptions); err != nil { - return err - } - if err := compareBaseBaseline(prior.baseline, current.baseline); err != nil { - return err - } - if err := compareBaseExceptions(prior.exceptions, current.exceptions); err != nil { - return err - } - return compareBaseArchitecture(prior.architecture, current.architecture) -} - -func validateRatchetManifests(bundle contractBundle) error { - if err := validateBaseline(bundle.baseline); err != nil { - return err - } - if err := validateExceptions(bundle.exceptions); err != nil { - return err - } - return validateArchitectureManifest(bundle.architecture) -} - -func forbidBaselineExceptionConversion(prior baselineManifest, current exceptionManifest) error { - baselineIdentities := make(map[string]struct{}, len(prior.Entries)) - for _, entry := range prior.Entries { - baselineIdentities[entry.Identity] = struct{}{} - } - for _, exception := range current.Entries { - if _, converted := baselineIdentities[exception.Identity]; converted { - return fmt.Errorf("historical baseline identity %s cannot move to exceptions", exception.Identity) - } - } - return nil -} - -func boolCount(value bool) int { - if value { - return 1 - } - return 0 -} diff --git a/harness/tools/quality/history_chain_test.go b/harness/tools/quality/history_chain_test.go deleted file mode 100644 index 4f554109..00000000 --- a/harness/tools/quality/history_chain_test.go +++ /dev/null @@ -1,354 +0,0 @@ -package main - -import ( - "strings" - "testing" -) - -func TestCommittedManifestChainRejectsRaiseThenLower(t *testing.T) { - root := initTestRepository(t) - writeTestFile(t, root, "README.md", "base\n") - base := commitTestRepository(t, root, "base") - entry := baselineEntry{Rule: ruleFunctionLines, Identity: functionIdentity(ruleFunctionLines, "harness/a.go", "Run"), Path: "harness/a.go", Symbol: "Run", Ceiling: 90} - baseline := validBaselineManifest() - baseline.SourceCommit = base - baseline.Entries = []baselineEntry{entry} - writeRatchetBundle(t, root, baseline) - commitTestRepository(t, root, "bootstrap") - baseline.Entries[0].Ceiling = 100 - writeRatchetBundle(t, root, baseline) - commitTestRepository(t, root, "illegal raise") - baseline.Entries[0].Ceiling = 90 - writeRatchetBundle(t, root, baseline) - commitTestRepository(t, root, "hide raise") - if err := validateCommittedManifestChain(root, base); err == nil || !strings.Contains(err.Error(), "raises") { - t.Fatalf("chain error = %v", err) - } -} - -func TestContractBundlesRejectBaselineToExceptionConversion(t *testing.T) { - entry := baselineEntry{Rule: ruleFunctionLines, Identity: functionIdentity(ruleFunctionLines, "harness/a.go", "Run"), Path: "harness/a.go", Symbol: "Run", Ceiling: 90} - prior := contractBundle{baseline: validBaselineManifest(), exceptions: exceptionManifest{SchemaVersion: 1, Entries: []exceptionEntry{}}, architecture: architectureManifest{SchemaVersion: 1, SourceCommit: strings.Repeat("a", 40), Entries: []architectureEntry{}}} - prior.baseline.Entries = []baselineEntry{entry} - current := prior - current.baseline.Entries = []baselineEntry{} - current.exceptions.Entries = []exceptionEntry{{ - Rule: entry.Rule, Identity: entry.Identity, Path: entry.Path, Symbol: entry.Symbol, Ceiling: 90, - Reason: "move debt", Risk: "medium", Owner: "team", RemovalCheckpoint: "7R", - }} - if err := compareContractBundles(prior, current); err == nil || !strings.Contains(err.Error(), "cannot move") { - t.Fatalf("conversion error = %v", err) - } -} - -func TestCommittedManifestLedgerRejectsReclassificationAcrossThreeCommitGap(t *testing.T) { - root := initTestRepository(t) - writeTestFile(t, root, "README.md", "base\n") - anchor := commitTestRepository(t, root, "base") - entry := baselineEntry{Rule: ruleFunctionLines, Identity: functionIdentity(ruleFunctionLines, "harness/a.go", "Run"), Path: "harness/a.go", Symbol: "Run", Ceiling: 90} - baseline := validBaselineManifest() - baseline.SourceCommit = anchor - baseline.Entries = []baselineEntry{entry} - writeRatchetContractBundle(t, root, baseline, exceptionManifest{SchemaVersion: 1, Entries: []exceptionEntry{}}) - commitTestRepository(t, root, "bootstrap") - - baseline.Entries = []baselineEntry{} - writeRatchetContractBundle(t, root, baseline, exceptionManifest{SchemaVersion: 1, Entries: []exceptionEntry{}}) - commitTestRepository(t, root, "remove baseline debt") - commitHistoryGaps(t, root, 3) - - exceptions := exceptionManifest{SchemaVersion: 1, Entries: []exceptionEntry{ratchetException(entry, 90)}} - writeRatchetContractBundle(t, root, baseline, exceptions) - commitTestRepository(t, root, "reclassify removed debt") - if err := validateCommittedManifestChain(root, anchor); err == nil || !strings.Contains(err.Error(), "historical baseline identity") { - t.Fatalf("lifetime reclassification error = %v", err) - } -} - -func TestCommittedManifestLedgerRejectsExceptionResurrectionAcrossThreeCommitGap(t *testing.T) { - root := initTestRepository(t) - writeTestFile(t, root, "README.md", "base\n") - anchor := commitTestRepository(t, root, "base") - entry := baselineEntry{Rule: ruleFunctionLines, Identity: functionIdentity(ruleFunctionLines, "harness/a.go", "Run"), Path: "harness/a.go", Symbol: "Run", Ceiling: 90} - baseline := validBaselineManifest() - baseline.SourceCommit = anchor - baseline.Entries = []baselineEntry{} - exceptions := exceptionManifest{SchemaVersion: 1, Entries: []exceptionEntry{ratchetException(entry, 90)}} - writeRatchetContractBundle(t, root, baseline, exceptions) - commitTestRepository(t, root, "bootstrap") - - exceptions.Entries = []exceptionEntry{} - writeRatchetContractBundle(t, root, baseline, exceptions) - commitTestRepository(t, root, "remove exception") - commitHistoryGaps(t, root, 3) - - exceptions.Entries = []exceptionEntry{ratchetException(entry, 91)} - writeRatchetContractBundle(t, root, baseline, exceptions) - commitTestRepository(t, root, "raise restored exception") - if err := validateCommittedManifestChain(root, anchor); err == nil || !strings.Contains(err.Error(), "cannot be resurrected") { - t.Fatalf("lifetime exception resurrection error = %v", err) - } -} - -func TestManifestHistoryUsesLifetimeAnchorBeforeCurrentPRBase(t *testing.T) { - root := initTestRepository(t) - writeTestFile(t, root, "README.md", "base\n") - anchor := commitTestRepository(t, root, "base") - entry := baselineEntry{Rule: ruleFunctionLines, Identity: functionIdentity(ruleFunctionLines, "harness/a.go", "Run"), Path: "harness/a.go", Symbol: "Run", Ceiling: 90} - baseline := validBaselineManifest() - baseline.SourceCommit = anchor - baseline.Entries = []baselineEntry{entry} - writeRatchetContractBundle(t, root, baseline, exceptionManifest{SchemaVersion: 1, Entries: []exceptionEntry{}}) - commitTestRepository(t, root, "bootstrap") - - baseline.Entries = []baselineEntry{} - writeRatchetContractBundle(t, root, baseline, exceptionManifest{SchemaVersion: 1, Entries: []exceptionEntry{}}) - currentPRBase := commitTestRepository(t, root, "previous PR removes debt") - exceptions := exceptionManifest{SchemaVersion: 1, Entries: []exceptionEntry{ratchetException(entry, 90)}} - writeRatchetContractBundle(t, root, baseline, exceptions) - architecture := architectureManifest{SchemaVersion: 1, SourceCommit: anchor, Entries: []architectureEntry{}} - - if err := compareManifestHistory(root, currentPRBase, baseline, exceptions, architecture); err == nil || !strings.Contains(err.Error(), "historical baseline identity") { - t.Fatalf("cross-PR lifetime error = %v", err) - } -} - -func TestManifestDAGAllowsDisjointSiblingRemovalsAtMerge(t *testing.T) { - root, anchor, bootstrap, baseline := bootstrapBaselineHistory(t, ratchetMetricEntry("x"), ratchetMetricEntry("y")) - x, y := baseline.Entries[0], baseline.Entries[1] - checkoutTestBranch(t, root, "remove-x", bootstrap) - baseline.Entries = []baselineEntry{y} - writeRatchetBundle(t, root, baseline) - commitTestRepository(t, root, "remove x") - - checkoutTestBranch(t, root, "remove-y", bootstrap) - baseline.Entries = []baselineEntry{x} - writeRatchetBundle(t, root, baseline) - commitTestRepository(t, root, "remove y") - - checkoutTestReference(t, root, "remove-x") - beginTestMerge(t, root, "remove-y") - baseline.Entries = []baselineEntry{} - writeRatchetBundle(t, root, baseline) - commitTestRepository(t, root, "merge both removals") - if err := validateCommittedManifestChain(root, anchor); err != nil { - t.Fatalf("disjoint removal merge: %v", err) - } -} - -func TestManifestDAGRejectsMergeResurrectionFromSecondParent(t *testing.T) { - root, anchor, bootstrap, baseline := bootstrapBaselineHistory(t, ratchetMetricEntry("x")) - original := append([]baselineEntry(nil), baseline.Entries...) - checkoutTestBranch(t, root, "removed", bootstrap) - baseline.Entries = []baselineEntry{} - writeRatchetBundle(t, root, baseline) - commitTestRepository(t, root, "remove x") - - checkoutTestBranch(t, root, "retained", bootstrap) - writeTestFile(t, root, "retained.txt", "side branch\n") - commitTestRepository(t, root, "retain old manifest") - - checkoutTestReference(t, root, "removed") - beginTestMerge(t, root, "retained") - baseline.Entries = original - writeRatchetBundle(t, root, baseline) - commitTestRepository(t, root, "resurrect from second parent") - if err := validateCommittedManifestChain(root, anchor); err == nil || !strings.Contains(err.Error(), "adds identity") { - t.Fatalf("merge resurrection error = %v", err) - } -} - -func TestManifestDAGMergesExceptionCeilingsAtHistoricalMinimum(t *testing.T) { - for _, test := range []struct { - name string - mergeCeiling int - wantError bool - }{ - {name: "reject one hundred", mergeCeiling: 100, wantError: true}, - {name: "accept ninety", mergeCeiling: 90, wantError: false}, - } { - t.Run(test.name, func(t *testing.T) { - root := initTestRepository(t) - writeTestFile(t, root, "README.md", "base\n") - anchor := commitTestRepository(t, root, "base") - entry := ratchetMetricEntry("exception") - baseline := validBaselineManifest() - baseline.SourceCommit = anchor - baseline.Entries = []baselineEntry{} - writeRatchetContractBundle(t, root, baseline, exceptionManifest{SchemaVersion: 1, Entries: []exceptionEntry{ratchetException(entry, 100)}}) - bootstrap := commitTestRepository(t, root, "bootstrap") - - checkoutTestBranch(t, root, "lower", bootstrap) - writeRatchetContractBundle(t, root, baseline, exceptionManifest{SchemaVersion: 1, Entries: []exceptionEntry{ratchetException(entry, 90)}}) - commitTestRepository(t, root, "lower exception") - checkoutTestBranch(t, root, "retain", bootstrap) - writeTestFile(t, root, "retain.txt", "retain\n") - commitTestRepository(t, root, "retain exception") - - checkoutTestReference(t, root, "lower") - beginTestMerge(t, root, "retain") - writeRatchetContractBundle(t, root, baseline, exceptionManifest{SchemaVersion: 1, Entries: []exceptionEntry{ratchetException(entry, test.mergeCeiling)}}) - commitTestRepository(t, root, "merge exception ceilings") - err := validateCommittedManifestChain(root, anchor) - if test.wantError && (err == nil || !strings.Contains(err.Error(), "raises ceiling")) { - t.Fatalf("ceiling merge error = %v", err) - } - if !test.wantError && err != nil { - t.Fatalf("ceiling merge: %v", err) - } - }) - } -} - -func TestManifestDAGRejectsExceptionKeptAfterSiblingRemoval(t *testing.T) { - root := initTestRepository(t) - writeTestFile(t, root, "README.md", "base\n") - anchor := commitTestRepository(t, root, "base") - entry := ratchetMetricEntry("exception") - baseline := validBaselineManifest() - baseline.SourceCommit = anchor - baseline.Entries = []baselineEntry{} - exceptions := exceptionManifest{SchemaVersion: 1, Entries: []exceptionEntry{ratchetException(entry, 90)}} - writeRatchetContractBundle(t, root, baseline, exceptions) - bootstrap := commitTestRepository(t, root, "bootstrap") - - checkoutTestBranch(t, root, "remove-exception", bootstrap) - writeRatchetContractBundle(t, root, baseline, exceptionManifest{SchemaVersion: 1, Entries: []exceptionEntry{}}) - commitTestRepository(t, root, "remove exception") - checkoutTestBranch(t, root, "keep-exception", bootstrap) - writeTestFile(t, root, "keep.txt", "keep\n") - commitTestRepository(t, root, "keep exception") - - checkoutTestReference(t, root, "remove-exception") - beginTestMerge(t, root, "keep-exception") - writeRatchetContractBundle(t, root, baseline, exceptions) - commitTestRepository(t, root, "resurrect exception at merge") - if err := validateCommittedManifestChain(root, anchor); err == nil || !strings.Contains(err.Error(), "cannot be resurrected") { - t.Fatalf("exception merge resurrection error = %v", err) - } -} - -func TestManifestHistoryAcceptsRawHeadAgainstAdvancedUnrelatedBase(t *testing.T) { - root, anchor, bootstrap, baseline := bootstrapBaselineHistory(t, ratchetMetricEntry("x")) - checkoutTestBranch(t, root, "advanced-base", bootstrap) - writeTestFile(t, root, "base.txt", "advanced base\n") - base := commitTestRepository(t, root, "advance base") - - checkoutTestBranch(t, root, "raw-head", bootstrap) - baseline.Entries = []baselineEntry{} - writeRatchetBundle(t, root, baseline) - commitTestRepository(t, root, "remove x on raw head") - architecture := architectureManifest{SchemaVersion: 1, SourceCommit: anchor, Entries: []architectureEntry{}} - if err := compareManifestHistory(root, base, baseline, exceptionManifest{SchemaVersion: 1, Entries: []exceptionEntry{}}, architecture); err != nil { - t.Fatalf("advanced base/raw head: %v", err) - } -} - -func TestManifestHistoryRejectsRawHeadDebtRemovedOnAdvancedBase(t *testing.T) { - root, anchor, bootstrap, baseline := bootstrapBaselineHistory(t, ratchetMetricEntry("x")) - checkoutTestBranch(t, root, "advanced-base", bootstrap) - baseBaseline := baseline - baseBaseline.Entries = []baselineEntry{} - writeRatchetBundle(t, root, baseBaseline) - base := commitTestRepository(t, root, "remove x on base") - - checkoutTestBranch(t, root, "stale-head", bootstrap) - writeTestFile(t, root, "head.txt", "stale head\n") - commitTestRepository(t, root, "retain x on raw head") - architecture := architectureManifest{SchemaVersion: 1, SourceCommit: anchor, Entries: []architectureEntry{}} - if err := compareManifestHistory(root, base, baseline, exceptionManifest{SchemaVersion: 1, Entries: []exceptionEntry{}}, architecture); err == nil || !strings.Contains(err.Error(), "not monotone relative") { - t.Fatalf("stale raw head error = %v", err) - } -} - -func TestManifestHistoryRejectsRawHeadExceptionRemovedOnAdvancedBase(t *testing.T) { - root := initTestRepository(t) - writeTestFile(t, root, "README.md", "base\n") - anchor := commitTestRepository(t, root, "base") - entry := ratchetMetricEntry("exception") - baseline := validBaselineManifest() - baseline.SourceCommit = anchor - baseline.Entries = []baselineEntry{} - exceptions := exceptionManifest{SchemaVersion: 1, Entries: []exceptionEntry{ratchetException(entry, 90)}} - writeRatchetContractBundle(t, root, baseline, exceptions) - bootstrap := commitTestRepository(t, root, "bootstrap") - - checkoutTestBranch(t, root, "advanced-base", bootstrap) - writeRatchetContractBundle(t, root, baseline, exceptionManifest{SchemaVersion: 1, Entries: []exceptionEntry{}}) - base := commitTestRepository(t, root, "remove exception on base") - checkoutTestBranch(t, root, "stale-head", bootstrap) - writeTestFile(t, root, "head.txt", "stale exception\n") - commitTestRepository(t, root, "retain exception on raw head") - architecture := architectureManifest{SchemaVersion: 1, SourceCommit: anchor, Entries: []architectureEntry{}} - if err := compareManifestHistory(root, base, baseline, exceptions, architecture); err == nil || !strings.Contains(err.Error(), "cannot be resurrected") { - t.Fatalf("stale raw head exception error = %v", err) - } -} - -func writeRatchetBundle(t *testing.T, root string, baseline baselineManifest) { - t.Helper() - writeRatchetContractBundle(t, root, baseline, exceptionManifest{SchemaVersion: 1, Entries: []exceptionEntry{}}) -} - -func writeRatchetContractBundle(t *testing.T, root string, baseline baselineManifest, exceptions exceptionManifest) { - t.Helper() - writeCanonicalTestFile(t, root, baselinePath, baseline) - writeCanonicalTestFile(t, root, exceptionsPath, exceptions) - writeCanonicalTestFile(t, root, architecturePath, architectureManifest{SchemaVersion: 1, SourceCommit: baseline.SourceCommit, Entries: []architectureEntry{}}) -} - -func ratchetException(entry baselineEntry, ceiling int) exceptionEntry { - return exceptionEntry{ - Rule: entry.Rule, Identity: entry.Identity, Path: entry.Path, Symbol: entry.Symbol, Ceiling: ceiling, - Reason: "temporary reviewed debt", Risk: "medium", Owner: "quality", RemovalCheckpoint: "7R", - } -} - -func commitHistoryGaps(t *testing.T, root string, count int) { - t.Helper() - for index := 1; index <= count; index++ { - writeTestFile(t, root, "README.md", strings.Repeat("gap\n", index)) - commitTestRepository(t, root, "unrelated gap") - } -} - -func bootstrapBaselineHistory(t *testing.T, entries ...baselineEntry) (string, string, string, baselineManifest) { - t.Helper() - root := initTestRepository(t) - writeTestFile(t, root, "README.md", "base\n") - anchor := commitTestRepository(t, root, "base") - baseline := validBaselineManifest() - baseline.SourceCommit = anchor - baseline.Entries = append([]baselineEntry(nil), entries...) - writeRatchetBundle(t, root, baseline) - bootstrap := commitTestRepository(t, root, "bootstrap") - return root, anchor, bootstrap, baseline -} - -func ratchetMetricEntry(name string) baselineEntry { - path := "harness/" + name + ".go" - symbol := strings.ToUpper(name[:1]) + name[1:] - return baselineEntry{Rule: ruleFunctionLines, Identity: functionIdentity(ruleFunctionLines, path, symbol), Path: path, Symbol: symbol, Ceiling: 90} -} - -func checkoutTestBranch(t *testing.T, root, name, start string) { - t.Helper() - if _, err := runGit(root, "checkout", "-b", name, start); err != nil { - t.Fatal(err) - } -} - -func checkoutTestReference(t *testing.T, root, reference string) { - t.Helper() - if _, err := runGit(root, "checkout", reference); err != nil { - t.Fatal(err) - } -} - -func beginTestMerge(t *testing.T, root, reference string) { - t.Helper() - _, _ = runGit(root, "merge", "--no-ff", "--no-commit", reference) - if _, err := runGit(root, "rev-parse", "--verify", "MERGE_HEAD"); err != nil { - t.Fatalf("merge %s did not enter a merge state: %v", reference, err) - } -} diff --git a/harness/tools/quality/history_test.go b/harness/tools/quality/history_test.go deleted file mode 100644 index 92d2f92b..00000000 --- a/harness/tools/quality/history_test.go +++ /dev/null @@ -1,107 +0,0 @@ -package main - -import ( - "strings" - "testing" -) - -func TestCompareBaseBaselineRejectsAdditionAndIncrease(t *testing.T) { - entry := baselineEntry{Rule: ruleFunctionLines, Identity: functionIdentity(ruleFunctionLines, "harness/a.go", "Run"), Path: "harness/a.go", Symbol: "Run", Ceiling: 90} - base := validBaselineManifest() - base.Entries = []baselineEntry{entry} - candidate := base - candidate.Entries = append([]baselineEntry(nil), base.Entries...) - candidate.Entries[0].Ceiling = 91 - if err := compareBaseBaseline(base, candidate); err == nil || !strings.Contains(err.Error(), "raises") { - t.Fatalf("increase error = %v", err) - } - candidate.Entries[0] = entry - candidate.Entries = append(candidate.Entries, baselineEntry{Rule: ruleProductionFile, Identity: fileIdentity(ruleProductionFile, "harness/b.go"), Path: "harness/b.go", Ceiling: 401}) - if err := compareBaseBaseline(base, candidate); err == nil || !strings.Contains(err.Error(), "adds") { - t.Fatalf("addition error = %v", err) - } -} - -func TestCompareBaseManifestsRejectSourceCommitRebinding(t *testing.T) { - base := validBaselineManifest() - candidate := base - candidate.SourceCommit = strings.Repeat("b", 40) - if err := compareBaseBaseline(base, candidate); err == nil || !strings.Contains(err.Error(), "source_commit") { - t.Fatalf("baseline source rebind error = %v", err) - } - baseArchitecture := architectureManifest{SourceCommit: base.SourceCommit} - candidateArchitecture := architectureManifest{SourceCommit: candidate.SourceCommit} - if err := compareBaseArchitecture(baseArchitecture, candidateArchitecture); err == nil || !strings.Contains(err.Error(), "source_commit") { - t.Fatalf("architecture source rebind error = %v", err) - } -} - -func TestCompareBaseArchitectureAndExceptionsRejectBroaderDebt(t *testing.T) { - architecture := architectureEntry{Rule: "dependency_direction", Identity: "dep:a", Path: "harness/a.go", Component: "legacy", Risk: "medium", Evidence: "harness/a.go", Owner: "team", RemovalCheckpoint: "7R"} - baseArchitecture := architectureManifest{Entries: []architectureEntry{architecture}} - candidateArchitecture := baseArchitecture - candidateArchitecture.Entries = append([]architectureEntry(nil), baseArchitecture.Entries...) - candidateArchitecture.Entries[0].Risk = "high" - if err := compareBaseArchitecture(baseArchitecture, candidateArchitecture); err == nil { - t.Fatal("architecture risk upgrade was accepted") - } - exception := exceptionEntry{Rule: ruleFunctionLines, Identity: "id", Path: "harness/a.go", Symbol: "Run"} - if err := compareBaseExceptions(exceptionManifest{}, exceptionManifest{Entries: []exceptionEntry{exception}}); err != nil { - t.Fatalf("exact new exception was rejected: %v", err) - } - changed := exception - changed.Path = "harness/b.go" - if err := compareBaseExceptions(exceptionManifest{Entries: []exceptionEntry{exception}}, exceptionManifest{Entries: []exceptionEntry{changed}}); err == nil { - t.Fatal("rebound exception was accepted") - } -} - -func TestDuplicateEvolutionAllowsOneDimensionalCleanupOnly(t *testing.T) { - prior := baselineEntry{ - Rule: ruleDuplicate, Identity: ruleDuplicate + ":dup-reviewed", DebtID: "dup-reviewed", - Path: "harness/a.go", - Owners: []string{"harness/a.go::A", "harness/b.go::B", "harness/c.go::C"}, - Fingerprint: strings.Repeat("a", 64), - } - ownerCleanup := prior - ownerCleanup.Owners = ownerCleanup.Owners[:2] - if err := validateDuplicateEvolution(prior, ownerCleanup); err != nil { - t.Fatalf("owner cleanup: %v", err) - } - fingerprintRebind := prior - fingerprintRebind.Fingerprint = strings.Repeat("b", 64) - if err := validateDuplicateEvolution(prior, fingerprintRebind); err == nil || !strings.Contains(err.Error(), "cannot rebind fingerprint") { - t.Fatalf("fingerprint rebind error = %v", err) - } - rebound := ownerCleanup - rebound.Fingerprint = fingerprintRebind.Fingerprint - if err := validateDuplicateEvolution(prior, rebound); err == nil { - t.Fatal("simultaneous owner and fingerprint rebind was accepted") - } - added := prior - added.Owners = append(append([]string(nil), prior.Owners...), "harness/d.go::D") - if err := validateDuplicateEvolution(prior, added); err == nil { - t.Fatal("duplicate owner addition was accepted") - } -} - -func TestCompareBaseBaselineAllowsDuplicateDerivedPathToFollowOwnerCleanup(t *testing.T) { - entry := baselineEntry{ - Rule: ruleDuplicate, Identity: ruleDuplicate + ":dup-reviewed", DebtID: "dup-reviewed", - Path: "harness/a.go", Owners: []string{"harness/a.go::A", "harness/b.go::B", "harness/c.go::C"}, - Fingerprint: strings.Repeat("a", 64), Ceiling: 160, - } - base := validBaselineManifest() - base.Entries = []baselineEntry{entry} - candidate := base - candidate.Entries = append([]baselineEntry(nil), base.Entries...) - candidate.Entries[0].Owners = []string{"harness/b.go::B", "harness/c.go::C"} - candidate.Entries[0].Path = "harness/b.go" - if err := compareBaseBaseline(base, candidate); err != nil { - t.Fatalf("derived path cleanup: %v", err) - } - candidate.Entries[0].Path = "harness/c.go" - if err := compareBaseBaseline(base, candidate); err == nil || !strings.Contains(err.Error(), "first remaining owner") { - t.Fatalf("misbound derived path error = %v", err) - } -} diff --git a/harness/tools/quality/main.go b/harness/tools/quality/main.go deleted file mode 100644 index 21c7f17e..00000000 --- a/harness/tools/quality/main.go +++ /dev/null @@ -1,76 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "io" - "os" -) - -func main() { - if err := execute(os.Args[1:], os.Stdout, os.Stderr); err != nil { - fmt.Fprintln(os.Stderr, "harness-quality:", err) - os.Exit(2) - } -} - -func execute(arguments []string, stdout, stderr io.Writer) error { - if len(arguments) == 0 { - return fmt.Errorf("expected measure or check subcommand") - } - switch arguments[0] { - case "measure": - return executeMeasure(arguments[1:], stdout, stderr) - case "check": - return executeCheck(arguments[1:], stdout, stderr) - default: - return fmt.Errorf("unknown subcommand %q; expected measure or check", arguments[0]) - } -} - -func executeMeasure(arguments []string, stdout, stderr io.Writer) error { - flags := flag.NewFlagSet("measure", flag.ContinueOnError) - flags.SetOutput(stderr) - root := flags.String("root", "", "source tree root") - sourceCommit := flags.String("source-commit", "", "full source commit hash") - output := flags.String("output", "", "baseline JSON output path") - if err := flags.Parse(arguments); err != nil { - return err - } - if flags.NArg() != 0 { - return fmt.Errorf("measure does not accept positional arguments") - } - if *root == "" || *sourceCommit == "" || *output == "" { - return fmt.Errorf("measure requires --root, --source-commit, and --output") - } - manifest, err := measureTree(*root, *sourceCommit) - if err != nil { - return err - } - if err := writeCanonicalJSON(*output, manifest); err != nil { - return err - } - _, err = fmt.Fprintf(stdout, "measured %d quality violations with %s\n", len(manifest.Entries), qualityToolVersion) - return err -} - -func executeCheck(arguments []string, stdout, stderr io.Writer) error { - flags := flag.NewFlagSet("check", flag.ContinueOnError) - flags.SetOutput(stderr) - root := flags.String("root", "", "repository root") - baseReference := flags.String("base-ref", "", "optional Git base reference") - if err := flags.Parse(arguments); err != nil { - return err - } - if flags.NArg() != 0 { - return fmt.Errorf("check does not accept positional arguments") - } - if *root == "" { - return fmt.Errorf("check requires --root") - } - if err := checkRepository(*root, *baseReference); err != nil { - return err - } - _, err := fmt.Fprintln(stdout, "harness quality check passed") - return err -} diff --git a/harness/tools/quality/main_test.go b/harness/tools/quality/main_test.go deleted file mode 100644 index 39c99e57..00000000 --- a/harness/tools/quality/main_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package main - -import ( - "bytes" - "path/filepath" - "strings" - "testing" -) - -func TestExecuteMeasureWritesBaseline(t *testing.T) { - root := t.TempDir() - writeTestFile(t, root, "harness/a.go", "package harness\nfunc Run() {}\n") - output := filepath.Join(root, "out", "baseline.json") - var stdout bytes.Buffer - err := execute([]string{ - "measure", "--root", root, "--source-commit", strings.Repeat("a", 40), "--output", output, - }, &stdout, &bytes.Buffer{}) - if err != nil { - t.Fatal(err) - } - if !strings.Contains(stdout.String(), qualityToolVersion) { - t.Fatalf("stdout = %q", stdout.String()) - } - manifest, err := readExactJSON[baselineManifest](output) - if err != nil { - t.Fatal(err) - } - if manifest.SchemaVersion != 1 || manifest.Entries == nil { - t.Fatalf("manifest = %#v", manifest) - } -} - -func TestExecuteRejectsUnknownAndIncompleteCommands(t *testing.T) { - for _, arguments := range [][]string{{}, {"unknown"}, {"measure", "--root", t.TempDir()}, {"check"}} { - if err := execute(arguments, &bytes.Buffer{}, &bytes.Buffer{}); err == nil { - t.Fatalf("arguments %#v were accepted", arguments) - } - } -} diff --git a/harness/tools/quality/manifest.go b/harness/tools/quality/manifest.go deleted file mode 100644 index a28e53e6..00000000 --- a/harness/tools/quality/manifest.go +++ /dev/null @@ -1,147 +0,0 @@ -package main - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "os" - "path" - "regexp" - "sort" - "strings" -) - -var pointerReceiverPattern = regexp.MustCompile(`\(\*[A-Za-z_][A-Za-z0-9_]*\)`) - -func readExactJSON[T any](path string) (T, error) { - var zero T - data, err := os.ReadFile(path) - if err != nil { - return zero, fmt.Errorf("read %s: %w", path, err) - } - value, err := decodeExactJSON[T](data, path) - if err != nil { - return zero, err - } - canonical, err := canonicalJSON(value) - if err != nil { - return zero, fmt.Errorf("encode canonical %s: %w", path, err) - } - if !bytes.Equal(data, canonical) { - return zero, fmt.Errorf("%s is not canonical indented JSON with a trailing newline", path) - } - return value, nil -} - -func decodeExactJSON[T any](data []byte, label string) (T, error) { - var value T - decoder := json.NewDecoder(bytes.NewReader(data)) - decoder.DisallowUnknownFields() - if err := decoder.Decode(&value); err != nil { - return value, fmt.Errorf("decode %s: %w", label, err) - } - var trailing any - if err := decoder.Decode(&trailing); err != io.EOF { - if err == nil { - return value, fmt.Errorf("decode %s: multiple JSON values", label) - } - return value, fmt.Errorf("decode trailing %s: %w", label, err) - } - return value, nil -} - -func canonicalJSON(value any) ([]byte, error) { - data, err := json.MarshalIndent(value, "", " ") - if err != nil { - return nil, err - } - return append(data, '\n'), nil -} - -func validateSchema(version int, label string) error { - if version != manifestSchemaVersion { - return fmt.Errorf("%s schema_version = %d, want %d", label, version, manifestSchemaVersion) - } - return nil -} - -func validateFullCommit(value, field string) error { - if len(value) != 40 { - return fmt.Errorf("%s must be a full 40-character commit hash", field) - } - for _, character := range value { - if !strings.ContainsRune("0123456789abcdef", character) { - return fmt.Errorf("%s must be a lowercase hexadecimal commit hash", field) - } - } - return nil -} - -func requireText(value, field string) error { - if value == "" || strings.TrimSpace(value) != value { - return fmt.Errorf("%s must be non-empty and have no surrounding whitespace", field) - } - return nil -} - -func rejectWildcard(value, field string) error { - if strings.ContainsAny(value, "*?[") { - return fmt.Errorf("%s contains a wildcard", field) - } - return nil -} - -func rejectSymbolWildcard(value, field string) error { - withoutPointerReceivers := pointerReceiverPattern.ReplaceAllString(value, "(receiver)") - return rejectWildcard(withoutPointerReceivers, field) -} - -func validateSortedUnique(values []string, field string, requireNonEmpty bool) error { - if values == nil { - return fmt.Errorf("%s must be a JSON array, not null", field) - } - if requireNonEmpty && len(values) == 0 { - return fmt.Errorf("%s must not be empty", field) - } - if !sort.StringsAreSorted(values) { - return fmt.Errorf("%s must be sorted", field) - } - for index, value := range values { - if err := requireText(value, fmt.Sprintf("%s[%d]", field, index)); err != nil { - return err - } - if index > 0 && values[index-1] == value { - return fmt.Errorf("%s contains duplicate %q", field, value) - } - } - return nil -} - -func validateHarnessPath(value, field string) error { - if err := validateRepoPath(value, field); err != nil { - return err - } - if !strings.HasPrefix(value, "harness/") { - return fmt.Errorf("%s must be below harness/", field) - } - return nil -} - -func validateRepoPath(value, field string) error { - if err := requireText(value, field); err != nil { - return err - } - if strings.ContainsAny(value, "\\ \t\r\n") || strings.HasPrefix(value, "/") || value == "." || path.Clean(value) != value { - return fmt.Errorf("%s must be a clean repository-relative path", field) - } - return rejectWildcard(value, field) -} - -func parsePathSymbol(value string) (string, string, error) { - separator := strings.LastIndex(value, "::") - if separator <= 0 || separator+2 == len(value) { - return "", "", fmt.Errorf("%q must use path::symbol", value) - } - return value[:separator], value[separator+2:], nil -} diff --git a/harness/tools/quality/manifest_quality.go b/harness/tools/quality/manifest_quality.go deleted file mode 100644 index 6fcd14f2..00000000 --- a/harness/tools/quality/manifest_quality.go +++ /dev/null @@ -1,239 +0,0 @@ -package main - -import ( - "fmt" - "regexp" - "sort" - "strings" -) - -var debtIDPattern = regexp.MustCompile(`^dup-[a-z0-9][a-z0-9-]*$`) - -func validateBaseline(manifest baselineManifest) error { - if err := validateSchema(manifest.SchemaVersion, "quality baseline"); err != nil { - return err - } - if manifest.ToolVersion != qualityToolVersion { - return fmt.Errorf("quality baseline tool_version = %q, want %q", manifest.ToolVersion, qualityToolVersion) - } - if err := validateFullCommit(manifest.SourceCommit, "quality baseline source_commit"); err != nil { - return err - } - if len(manifest.Thresholds) != len(qualityThresholds) { - return fmt.Errorf("quality baseline has %d thresholds, want %d", len(manifest.Thresholds), len(qualityThresholds)) - } - for index := range qualityThresholds { - if manifest.Thresholds[index] != qualityThresholds[index] { - return fmt.Errorf("quality baseline threshold[%d] = %#v, want %#v", index, manifest.Thresholds[index], qualityThresholds[index]) - } - } - if manifest.Entries == nil { - return fmt.Errorf("quality baseline entries must be a JSON array, not null") - } - previous := "" - identities := make(map[string]struct{}) - debtIDs := make(map[string]struct{}) - for index, entry := range manifest.Entries { - key := entry.Rule + "\x00" + entry.Identity - if index > 0 && key <= previous { - return fmt.Errorf("quality baseline entries must be uniquely sorted by rule and identity") - } - previous = key - if _, exists := identities[entry.Identity]; exists { - return fmt.Errorf("quality baseline repeats identity %q", entry.Identity) - } - identities[entry.Identity] = struct{}{} - if err := validateBaselineEntry(entry); err != nil { - return fmt.Errorf("quality baseline entry %q: %w", entry.Identity, err) - } - if entry.DebtID != "" { - if _, exists := debtIDs[entry.DebtID]; exists { - return fmt.Errorf("quality baseline repeats debt_id %q", entry.DebtID) - } - debtIDs[entry.DebtID] = struct{}{} - } - } - return nil -} - -func validateBaselineEntry(entry baselineEntry) error { - limit, ok := thresholdLimit(entry.Rule) - if !ok { - return fmt.Errorf("unknown rule %q", entry.Rule) - } - if err := validateHarnessPath(entry.Path, "path"); err != nil { - return err - } - if entry.Ceiling <= limit { - return fmt.Errorf("ceiling %d does not exceed rule limit %d", entry.Ceiling, limit) - } - if entry.Rule == ruleDuplicate { - return validateDuplicateBaselineEntry(entry) - } - if entry.DebtID != "" || entry.Owners != nil || entry.Fingerprint != "" { - return fmt.Errorf("non-duplicate entry carries duplicate matching fields") - } - if isFunctionRule(entry.Rule) { - if err := requireText(entry.Symbol, "symbol"); err != nil { - return err - } - if err := rejectSymbolWildcard(entry.Symbol, "symbol"); err != nil { - return err - } - if entry.Identity != functionIdentity(entry.Rule, entry.Path, entry.Symbol) { - return fmt.Errorf("identity does not match rule/path/symbol") - } - return nil - } - if entry.Symbol != "" || entry.Identity != fileIdentity(entry.Rule, entry.Path) { - return fmt.Errorf("file identity does not match rule/path") - } - return nil -} - -func validateDuplicateBaselineEntry(entry baselineEntry) error { - if !debtIDPattern.MatchString(entry.DebtID) { - return fmt.Errorf("invalid duplicate debt_id %q", entry.DebtID) - } - if entry.Identity != ruleDuplicate+":"+entry.DebtID { - return fmt.Errorf("duplicate identity must be rule:debt_id") - } - if entry.Symbol != "" { - return fmt.Errorf("duplicate entry must use owners rather than symbol") - } - if err := validateSortedUnique(entry.Owners, "owners", true); err != nil { - return err - } - if len(entry.Owners) < 2 { - return fmt.Errorf("duplicate entry must have at least two owners") - } - for index, owner := range entry.Owners { - path, symbol, err := parsePathSymbol(owner) - if err != nil { - return fmt.Errorf("owners[%d]: %w", index, err) - } - if err := validateHarnessPath(path, fmt.Sprintf("owners[%d] path", index)); err != nil { - return err - } - if err := requireText(symbol, fmt.Sprintf("owners[%d] symbol", index)); err != nil { - return err - } - if err := rejectSymbolWildcard(symbol, fmt.Sprintf("owners[%d] symbol", index)); err != nil { - return err - } - } - firstPath, _, _ := parsePathSymbol(entry.Owners[0]) - if entry.Path != firstPath { - return fmt.Errorf("duplicate path must equal the first sorted owner path") - } - if len(entry.Fingerprint) != 64 || !isLowerHex(entry.Fingerprint) { - return fmt.Errorf("fingerprint must be a lowercase SHA-256 digest") - } - return nil -} - -func validateExceptions(manifest exceptionManifest) error { - if err := validateSchema(manifest.SchemaVersion, "quality exceptions"); err != nil { - return err - } - if manifest.Entries == nil { - return fmt.Errorf("quality exception entries must be a JSON array, not null") - } - previous := "" - for index, entry := range manifest.Entries { - key := entry.Rule + "\x00" + entry.Identity - if index > 0 && key <= previous { - return fmt.Errorf("quality exception entries must be uniquely sorted by rule and identity") - } - previous = key - if err := validateExceptionEntry(entry); err != nil { - return fmt.Errorf("quality exception %q: %w", entry.Identity, err) - } - } - return nil -} - -func validateExceptionEntry(entry exceptionEntry) error { - limit, ok := thresholdLimit(entry.Rule) - if !ok { - return fmt.Errorf("rule %q is not waivable quality debt", entry.Rule) - } - if entry.Rule == ruleDuplicate { - return fmt.Errorf("normalized duplicate exceptions require stable owner evidence unavailable in v1") - } - if entry.Ceiling <= limit { - return fmt.Errorf("ceiling %d does not exceed rule limit %d", entry.Ceiling, limit) - } - if entry.Rule == ruleCyclomatic && entry.Ceiling > 30 { - return fmt.Errorf("cyclomatic ceiling %d cannot exceed 30", entry.Ceiling) - } - if err := validateHarnessPath(entry.Path, "path"); err != nil { - return err - } - for field, value := range map[string]string{"path": entry.Path, "component": entry.Component} { - if err := rejectWildcard(value, field); err != nil { - return err - } - } - if err := rejectSymbolWildcard(entry.Identity, "identity"); err != nil { - return err - } - if err := rejectSymbolWildcard(entry.Symbol, "symbol"); err != nil { - return err - } - if err := validateExceptionScope(entry); err != nil { - return err - } - return validateExceptionMetadata(entry) -} - -func validateExceptionScope(entry exceptionEntry) error { - if isFunctionRule(entry.Rule) { - if entry.Identity != functionIdentity(entry.Rule, entry.Path, entry.Symbol) || entry.Symbol == "" { - return fmt.Errorf("function exception identity does not match rule/path/symbol") - } - } else if entry.Identity != fileIdentity(entry.Rule, entry.Path) || entry.Symbol != "" { - return fmt.Errorf("file exception identity does not match rule/path") - } - if entry.Component != "" { - return fmt.Errorf("metric exceptions do not use component") - } - return nil -} - -func validateExceptionMetadata(entry exceptionEntry) error { - for field, value := range map[string]string{"reason": entry.Reason, "risk": entry.Risk, "owner": entry.Owner, "removal_checkpoint": entry.RemovalCheckpoint} { - if err := requireText(value, field); err != nil { - return err - } - } - if !validExceptionRisk(entry.Risk) { - return fmt.Errorf("unsupported risk %q", entry.Risk) - } - return nil -} - -func thresholdLimit(rule string) (int, bool) { - index := sort.Search(len(qualityThresholds), func(index int) bool { return qualityThresholds[index].Rule >= rule }) - if index == len(qualityThresholds) || qualityThresholds[index].Rule != rule { - return 0, false - } - return qualityThresholds[index].Limit, true -} - -func isFunctionRule(rule string) bool { - return rule == ruleCyclomatic || rule == ruleCognitive || rule == ruleFunctionLines || rule == ruleStatements || rule == ruleNesting -} - -func validExceptionRisk(risk string) bool { - return risk == "critical" || risk == "high" || risk == "medium" || risk == "low" -} - -func isLowerHex(value string) bool { - for _, character := range value { - if !strings.ContainsRune("0123456789abcdef", character) { - return false - } - } - return true -} diff --git a/harness/tools/quality/manifest_quality_test.go b/harness/tools/quality/manifest_quality_test.go deleted file mode 100644 index b9074e5b..00000000 --- a/harness/tools/quality/manifest_quality_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package main - -import ( - "strings" - "testing" -) - -func TestValidateBaselineRejectsStaleThresholdAndMalformedDuplicate(t *testing.T) { - manifest := validBaselineManifest() - manifest.Thresholds[0].Limit++ - if err := validateBaseline(manifest); err == nil || !strings.Contains(err.Error(), "threshold") { - t.Fatalf("threshold error = %v", err) - } - manifest = validBaselineManifest() - manifest.Entries = []baselineEntry{{ - Rule: ruleDuplicate, Identity: ruleDuplicate + ":dup-0001", Path: "harness/a.go", - DebtID: "dup-0001", Owners: []string{"harness/b.go::B", "harness/a.go::A"}, - Fingerprint: strings.Repeat("a", 64), Ceiling: 160, - }} - if err := validateBaseline(manifest); err == nil || !strings.Contains(err.Error(), "sorted") { - t.Fatalf("owner sorting error = %v", err) - } -} - -func TestValidateExceptionsRejectsWildcardAndForbiddenRule(t *testing.T) { - entry := exceptionEntry{ - Rule: ruleFunctionLines, Identity: functionIdentity(ruleFunctionLines, "harness/a.go", "Run"), - Path: "harness/a.go", Symbol: "Run", Reason: "legacy transaction shell", Risk: "medium", - Owner: "harness", RemovalCheckpoint: "7R", Ceiling: 90, - } - if err := validateExceptions(exceptionManifest{SchemaVersion: 1, Entries: []exceptionEntry{entry}}); err != nil { - t.Fatal(err) - } - entry.Path = "harness/*" - if err := validateExceptionEntry(entry); err == nil { - t.Fatal("wildcard exception was accepted") - } - entry.Rule = "unowned_goroutine" - if err := validateExceptionEntry(entry); err == nil { - t.Fatal("forbidden exception rule was accepted") - } - entry.Rule = ruleDuplicate - entry.Identity = ruleDuplicate + ":dup-0001" - entry.Symbol = "" - entry.Ceiling = 160 - if err := validateExceptionEntry(entry); err == nil || !strings.Contains(err.Error(), "unavailable in v1") { - t.Fatalf("duplicate exception error = %v", err) - } -} - -func validBaselineManifest() baselineManifest { - thresholds := append([]threshold(nil), qualityThresholds...) - return baselineManifest{ - SchemaVersion: 1, - ToolVersion: qualityToolVersion, - SourceCommit: strings.Repeat("a", 40), - Thresholds: thresholds, - Entries: []baselineEntry{}, - } -} diff --git a/harness/tools/quality/manifest_test.go b/harness/tools/quality/manifest_test.go deleted file mode 100644 index 8659a547..00000000 --- a/harness/tools/quality/manifest_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package main - -import ( - "strings" - "testing" -) - -func TestReadExactJSONRejectsUnknownAndNoncanonicalInput(t *testing.T) { - root := t.TempDir() - writeTestFile(t, root, "unknown.json", `{"schema_version":1,"unknown":true}`+"\n") - if _, err := readExactJSON[exceptionManifest](root + "/unknown.json"); err == nil || !strings.Contains(err.Error(), "unknown field") { - t.Fatalf("unknown field error = %v", err) - } - writeTestFile(t, root, "compact.json", `{"schema_version":1,"entries":[]}`+"\n") - if _, err := readExactJSON[exceptionManifest](root + "/compact.json"); err == nil || !strings.Contains(err.Error(), "not canonical") { - t.Fatalf("canonical error = %v", err) - } -} - -func TestCanonicalJSONHasStableIndentAndNewline(t *testing.T) { - data, err := canonicalJSON(exceptionManifest{SchemaVersion: 1, Entries: []exceptionEntry{}}) - if err != nil { - t.Fatal(err) - } - want := "{\n \"schema_version\": 1,\n \"entries\": []\n}\n" - if string(data) != want { - t.Fatalf("canonical JSON = %q, want %q", data, want) - } -} - -func TestValidateFullCommitAndSortedUnique(t *testing.T) { - if err := validateFullCommit(strings.Repeat("a", 40), "commit"); err != nil { - t.Fatal(err) - } - if err := validateFullCommit("abcd", "commit"); err == nil { - t.Fatal("short commit was accepted") - } - if err := validateSortedUnique([]string{"b", "a"}, "values", true); err == nil { - t.Fatal("unsorted values were accepted") - } -} - -func TestSymbolWildcardValidationAllowsPointerReceiverOnly(t *testing.T) { - if err := rejectSymbolWildcard("function_logical_lines:harness/a.go::(*Runner).Run", "identity"); err != nil { - t.Fatalf("pointer receiver rejected: %v", err) - } - for _, value := range []string{"(*Runner*).Run", "Runner.*", "Runner.?", "Runner[all]"} { - if err := rejectSymbolWildcard(value, "symbol"); err == nil { - t.Errorf("wildcard symbol %q was accepted", value) - } - } -} diff --git a/harness/tools/quality/manifest_trace.go b/harness/tools/quality/manifest_trace.go deleted file mode 100644 index 98b43969..00000000 --- a/harness/tools/quality/manifest_trace.go +++ /dev/null @@ -1,87 +0,0 @@ -package main - -import ( - "fmt" - "strings" -) - -func validateArchitectureManifest(manifest architectureManifest) error { - if err := validateSchema(manifest.SchemaVersion, "architecture debt"); err != nil { - return err - } - if err := validateFullCommit(manifest.SourceCommit, "architecture debt source_commit"); err != nil { - return err - } - if manifest.Entries == nil { - return fmt.Errorf("architecture debt entries must be a JSON array, not null") - } - previous := "" - for index, entry := range manifest.Entries { - key := entry.Rule + "\x00" + entry.Identity - if index > 0 && key <= previous { - return fmt.Errorf("architecture debt entries must be uniquely sorted by rule and identity") - } - previous = key - if err := validateArchitectureEntry(entry); err != nil { - return fmt.Errorf("architecture debt %q: %w", entry.Identity, err) - } - } - return nil -} - -func validateArchitectureEntry(entry architectureEntry) error { - for field, value := range map[string]string{ - "rule": entry.Rule, "identity": entry.Identity, "path": entry.Path, - "risk": entry.Risk, "evidence": entry.Evidence, "owner": entry.Owner, - "removal_checkpoint": entry.RemovalCheckpoint, - } { - if err := requireText(value, field); err != nil { - return err - } - } - for field, value := range map[string]string{ - "rule": entry.Rule, "path": entry.Path, "component": entry.Component, - } { - if err := rejectWildcard(value, field); err != nil { - return err - } - } - if err := rejectSymbolWildcard(entry.Identity, "identity"); err != nil { - return err - } - if err := rejectSymbolWildcard(entry.Symbol, "symbol"); err != nil { - return err - } - if err := validateRepoPath(entry.Path, "path"); err != nil { - return err - } - if entry.Symbol != "" && entry.Component != "" { - return fmt.Errorf("symbol and component are mutually exclusive") - } - if entry.Risk != "critical" && entry.Risk != "high" && entry.Risk != "medium" { - return fmt.Errorf("unsupported risk %q", entry.Risk) - } - evidencePath, evidenceSymbol, err := parseEvidence(entry.Evidence) - if err != nil { - return err - } - if err := validateRepoPath(evidencePath, "evidence path"); err != nil { - return err - } - if evidenceSymbol != "" { - if err := requireText(evidenceSymbol, "evidence symbol"); err != nil { - return err - } - if err := rejectSymbolWildcard(evidenceSymbol, "evidence symbol"); err != nil { - return err - } - } - return nil -} - -func parseEvidence(value string) (string, string, error) { - if !strings.Contains(value, "::") { - return value, "", nil - } - return parsePathSymbol(value) -} diff --git a/harness/tools/quality/manifest_trace_test.go b/harness/tools/quality/manifest_trace_test.go deleted file mode 100644 index 16c51e3a..00000000 --- a/harness/tools/quality/manifest_trace_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package main - -import ( - "strings" - "testing" -) - -func TestValidateArchitectureRequiresStableEvidence(t *testing.T) { - entry := architectureEntry{ - Rule: "dependency_direction", Identity: "dependency_direction:harness/a.go::legacy", - Path: "harness/a.go", Component: "legacy", Risk: "high", Evidence: "harness/a.go::Run", - Owner: "harness", RemovalCheckpoint: "7R", - } - manifest := architectureManifest{SchemaVersion: 1, SourceCommit: strings.Repeat("a", 40), Entries: []architectureEntry{entry}} - if err := validateArchitectureManifest(manifest); err != nil { - t.Fatal(err) - } - manifest.Entries[0].Evidence = "free form evidence" - if err := validateArchitectureManifest(manifest); err == nil { - t.Fatal("free-form evidence was accepted") - } -} diff --git a/harness/tools/quality/measure.go b/harness/tools/quality/measure.go deleted file mode 100644 index 159cbcea..00000000 --- a/harness/tools/quality/measure.go +++ /dev/null @@ -1,144 +0,0 @@ -package main - -import ( - "fmt" - "os" - "path/filepath" - "sort" - "strings" -) - -func measureTree(root, sourceCommit string) (baselineManifest, error) { - if err := validateFullCommit(sourceCommit, "source_commit"); err != nil { - return baselineManifest{}, err - } - files, err := loadHarnessSources(root) - if err != nil { - return baselineManifest{}, err - } - metricFiles := metricEligibleSources(files) - functions, err := measureFunctions(metricFiles) - if err != nil { - return baselineManifest{}, err - } - entries := make([]baselineEntry, 0) - for _, function := range functions { - metrics := []struct { - rule string - value int - }{ - {rule: ruleCognitive, value: function.Cognitive}, - {rule: ruleCyclomatic, value: function.Cyclomatic}, - {rule: ruleNesting, value: function.Nesting}, - {rule: ruleFunctionLines, value: function.LogicalLines}, - {rule: ruleStatements, value: function.Statements}, - } - for _, metric := range metrics { - limit, _ := thresholdLimit(metric.rule) - if metric.value > limit { - entries = append(entries, baselineEntry{ - Rule: metric.rule, Identity: functionIdentity(metric.rule, function.Path, function.Symbol), - Path: function.Path, Symbol: function.Symbol, Ceiling: metric.value, - }) - } - } - } - for _, file := range metricFiles { - rule := ruleProductionFile - if file.IsTest { - rule = rulePairedTestFile - } - limit, _ := thresholdLimit(rule) - if file.LineCount > limit { - entries = append(entries, baselineEntry{ - Rule: rule, Identity: fileIdentity(rule, file.Path), Path: file.Path, Ceiling: file.LineCount, - }) - } - } - duplicates, err := measureDuplicates(functions) - if err != nil { - return baselineManifest{}, err - } - for _, duplicate := range duplicates { - path, _, splitErr := parsePathSymbol(duplicate.Owners[0]) - if splitErr != nil { - return baselineManifest{}, fmt.Errorf("internal duplicate owner: %w", splitErr) - } - entries = append(entries, baselineEntry{ - Rule: ruleDuplicate, Identity: ruleDuplicate + ":" + duplicate.DebtID, - Path: path, DebtID: duplicate.DebtID, Owners: append([]string(nil), duplicate.Owners...), - Fingerprint: duplicate.Fingerprint, Ceiling: duplicate.Tokens, - }) - } - sort.Slice(entries, func(i, j int) bool { - if entries[i].Rule != entries[j].Rule { - return entries[i].Rule < entries[j].Rule - } - return entries[i].Identity < entries[j].Identity - }) - manifest := baselineManifest{ - SchemaVersion: manifestSchemaVersion, - ToolVersion: qualityToolVersion, - SourceCommit: sourceCommit, - Thresholds: append([]threshold(nil), qualityThresholds...), - Entries: entries, - } - if err := validateBaseline(manifest); err != nil { - return baselineManifest{}, fmt.Errorf("measured baseline is invalid: %w", err) - } - return manifest, nil -} - -func writeCanonicalJSON(path string, value any) error { - data, err := canonicalJSON(value) - if err != nil { - return fmt.Errorf("encode output: %w", err) - } - directory := filepath.Dir(path) - if err := os.MkdirAll(directory, 0o755); err != nil { - return fmt.Errorf("create output directory: %w", err) - } - temporary, err := os.CreateTemp(directory, ".quality-*.json") - if err != nil { - return fmt.Errorf("create temporary output: %w", err) - } - temporaryPath := temporary.Name() - removeTemporary := true - defer func() { - if removeTemporary { - _ = os.Remove(temporaryPath) - } - }() - if err := temporary.Chmod(0o644); err != nil { - _ = temporary.Close() - return fmt.Errorf("set output mode: %w", err) - } - if _, err := temporary.Write(data); err != nil { - _ = temporary.Close() - return fmt.Errorf("write output: %w", err) - } - if err := temporary.Sync(); err != nil { - _ = temporary.Close() - return fmt.Errorf("sync output: %w", err) - } - if err := temporary.Close(); err != nil { - return fmt.Errorf("close output: %w", err) - } - if err := os.Rename(temporaryPath, path); err != nil { - return fmt.Errorf("replace output: %w", err) - } - removeTemporary = false - return nil -} - -func currentCommit(root string) (string, error) { - output, err := runGit(root, "rev-parse", "HEAD") - if err != nil { - return "", err - } - commit := strings.TrimSpace(string(output)) - if err := validateFullCommit(commit, "current commit"); err != nil { - return "", err - } - return commit, nil -} diff --git a/harness/tools/quality/measure_test.go b/harness/tools/quality/measure_test.go deleted file mode 100644 index 3a2da9aa..00000000 --- a/harness/tools/quality/measure_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package main - -import ( - "os" - "path/filepath" - "strings" - "testing" -) - -func TestMeasureTreeFindsFileAndFunctionDebt(t *testing.T) { - root := t.TempDir() - var source strings.Builder - source.WriteString("package harness\nfunc long(ok bool) {\n") - for index := 0; index < 90; index++ { - source.WriteString("if ok { ok = false }\n") - } - source.WriteString("}\n") - for source.Len() < 5000 { - source.WriteString("\n") - } - writeTestFile(t, root, "harness/long.go", source.String()) - manifest, err := measureTree(root, strings.Repeat("a", 40)) - if err != nil { - t.Fatal(err) - } - rules := make(map[string]bool) - for _, entry := range manifest.Entries { - rules[entry.Rule] = true - } - for _, rule := range []string{ruleCognitive, ruleCyclomatic, ruleFunctionLines, ruleStatements, ruleProductionFile} { - if !rules[rule] { - t.Errorf("missing measured rule %s", rule) - } - } -} - -func TestWriteCanonicalJSONReplacesOutput(t *testing.T) { - root := t.TempDir() - path := filepath.Join(root, "nested", "baseline.json") - manifest := validBaselineManifest() - if err := writeCanonicalJSON(path, manifest); err != nil { - t.Fatal(err) - } - loaded, err := readExactJSON[baselineManifest](path) - if err != nil { - t.Fatal(err) - } - if loaded.SourceCommit != manifest.SourceCommit { - t.Fatalf("source commit = %q", loaded.SourceCommit) - } - info, err := os.Stat(path) - if err != nil { - t.Fatal(err) - } - if info.Mode().Perm() != 0o644 { - t.Fatalf("mode = %o", info.Mode().Perm()) - } -} diff --git a/harness/tools/quality/metrics.go b/harness/tools/quality/metrics.go deleted file mode 100644 index f2b0d883..00000000 --- a/harness/tools/quality/metrics.go +++ /dev/null @@ -1,241 +0,0 @@ -package main - -import ( - "go/ast" - "go/scanner" - "go/token" - "sort" -) - -type functionMeasurement struct { - Path string - Symbol string - Cyclomatic int - Cognitive int - LogicalLines int - Statements int - Nesting int - Tokens []string -} - -type flowMetrics struct { - cyclomatic int - cognitive int - statements int - maxNesting int -} - -type tokenSpan struct { - start int - end int -} - -func measureFunctions(files []sourceFile) ([]functionMeasurement, error) { - var measured []functionMeasurement - for _, file := range files { - for _, declaration := range file.AST.Decls { - function, ok := declaration.(*ast.FuncDecl) - if !ok || function.Body == nil { - continue - } - symbol := functionSymbol(function) - if err := measureFunctionTree(file, symbol, function.Type.Pos(), function.Body, &measured); err != nil { - return nil, err - } - } - if err := measurePackageFunctionLiterals(file, &measured); err != nil { - return nil, err - } - } - sort.Slice(measured, func(i, j int) bool { - if measured[i].Path != measured[j].Path { - return measured[i].Path < measured[j].Path - } - return measured[i].Symbol < measured[j].Symbol - }) - return measured, nil -} - -func measureFunction(file sourceFile, symbol string, start token.Pos, body *ast.BlockStmt) functionMeasurement { - metrics := &flowMetrics{cyclomatic: 1} - ast.Walk(flowVisitor{metrics: metrics}, body) - nestedSpans := directFunctionLiteralSpans(file, body) - return functionMeasurement{ - Path: file.Path, - Symbol: symbol, - Cyclomatic: metrics.cyclomatic, - Cognitive: metrics.cognitive, - LogicalLines: logicalLinesExcluding(file, start, body.End(), nil), - Statements: metrics.statements, - Nesting: metrics.maxNesting, - Tokens: normalizedTokenSpan(file, start, body.End(), nestedSpans), - } -} - -type flowVisitor struct { - metrics *flowMetrics - nesting int - excluded map[token.Pos]token.Pos -} - -func (visitor flowVisitor) Visit(node ast.Node) ast.Visitor { - if node == nil { - return nil - } - if end, excluded := visitor.excluded[node.Pos()]; excluded && end == node.End() { - return nil - } - if _, nestedFunction := node.(*ast.FuncLit); nestedFunction { - return nil - } - visitor.countStatement(node) - childNesting := visitor.controlNesting(node) - visitor.countFlatComplexity(node) - if childNesting > visitor.metrics.maxNesting { - visitor.metrics.maxNesting = childNesting - } - return flowVisitor{metrics: visitor.metrics, nesting: childNesting, excluded: visitor.excluded} -} - -func (visitor flowVisitor) countStatement(node ast.Node) { - statement, ok := node.(ast.Stmt) - if !ok { - return - } - switch statement.(type) { - case *ast.BlockStmt, *ast.EmptyStmt: - default: - visitor.metrics.statements++ - } -} - -func (visitor flowVisitor) controlNesting(node ast.Node) int { - childNesting := visitor.nesting - switch value := node.(type) { - case *ast.IfStmt: - visitor.addDecision() - if value.Else != nil { - visitor.metrics.cognitive++ - } - childNesting++ - case *ast.ForStmt, *ast.RangeStmt, *ast.SwitchStmt, *ast.TypeSwitchStmt, *ast.SelectStmt: - visitor.addDecision() - childNesting++ - case *ast.CaseClause: - if len(value.List) > 0 { - visitor.metrics.cyclomatic++ - } - childNesting++ - case *ast.CommClause: - if value.Comm != nil { - visitor.metrics.cyclomatic++ - } - childNesting++ - } - return childNesting -} - -func (visitor flowVisitor) countFlatComplexity(node ast.Node) { - if branch, ok := node.(*ast.BranchStmt); ok { - if branch.Tok == token.BREAK || branch.Tok == token.CONTINUE || branch.Tok == token.GOTO { - visitor.metrics.cognitive++ - } - } - binary, ok := node.(*ast.BinaryExpr) - if ok && (binary.Op == token.LAND || binary.Op == token.LOR) { - visitor.metrics.cyclomatic++ - visitor.metrics.cognitive++ - } -} - -func (visitor flowVisitor) addDecision() { - visitor.metrics.cyclomatic++ - visitor.metrics.cognitive += 1 + visitor.nesting -} - -func logicalLinesExcluding(file sourceFile, start, end token.Pos, excluded []tokenSpan) int { - startOffset := file.FileSet.PositionFor(start, false).Offset - endOffset := file.FileSet.PositionFor(end, false).Offset - if startOffset < 0 || endOffset <= startOffset || endOffset > len(file.Data) { - return 0 - } - var lexical scanner.Scanner - lexicalFile := token.NewFileSet().AddFile(file.Path, -1, endOffset-startOffset) - lexical.Init(lexicalFile, file.Data[startOffset:endOffset], nil, 0) - lines := make(map[int]struct{}) - for { - position, item, _ := lexical.Scan() - if item == token.EOF { - break - } - absoluteOffset := startOffset + lexicalFile.Offset(position) - if offsetInSpans(absoluteOffset, excluded) { - continue - } - if item == token.SEMICOLON || item == token.LBRACE || item == token.RBRACE || item == token.COMMA || item == token.LPAREN || item == token.RPAREN { - continue - } - lines[lexicalFile.Position(position).Line] = struct{}{} - } - return len(lines) -} - -func directFunctionLiteralSpans(file sourceFile, body *ast.BlockStmt) []tokenSpan { - var excluded []tokenSpan - ast.Inspect(body, func(node ast.Node) bool { - literal, ok := node.(*ast.FuncLit) - if !ok { - return true - } - excluded = append(excluded, tokenSpan{ - start: file.FileSet.PositionFor(literal.Pos(), false).Offset, - end: file.FileSet.PositionFor(literal.End(), false).Offset, - }) - return false - }) - return excluded -} - -func normalizedTokenSpan(file sourceFile, start, end token.Pos, excluded []tokenSpan) []string { - startOffset := file.FileSet.PositionFor(start, false).Offset - endOffset := file.FileSet.PositionFor(end, false).Offset - if startOffset < 0 || endOffset <= startOffset || endOffset > len(file.Data) { - return nil - } - var lexical scanner.Scanner - lexicalFile := token.NewFileSet().AddFile(file.Path, -1, endOffset-startOffset) - lexical.Init(lexicalFile, file.Data[startOffset:endOffset], nil, 0) - var tokens []string - for { - position, item, literal := lexical.Scan() - if item == token.EOF { - return tokens - } - absoluteOffset := startOffset + lexicalFile.Offset(position) - if offsetInSpans(absoluteOffset, excluded) { - continue - } - if item == token.SEMICOLON { - continue - } - switch item { - case token.INT, token.FLOAT, token.IMAG: - tokens = append(tokens, "$number") - case token.CHAR, token.STRING: - tokens = append(tokens, "$string") - case token.IDENT: - tokens = append(tokens, literal) - default: - tokens = append(tokens, item.String()) - } - } -} - -func offsetInSpans(offset int, spans []tokenSpan) bool { - for _, span := range spans { - if offset >= span.start && offset < span.end { - return true - } - } - return false -} diff --git a/harness/tools/quality/metrics_test.go b/harness/tools/quality/metrics_test.go deleted file mode 100644 index 36acc42e..00000000 --- a/harness/tools/quality/metrics_test.go +++ /dev/null @@ -1,140 +0,0 @@ -package main - -import ( - "strings" - "testing" -) - -func TestMeasureFunctionsCountsControlFlowAndNestedLiterals(t *testing.T) { - root := t.TempDir() - writeTestFile(t, root, "harness/flow.go", `package harness -func flow(v int) int { - if v > 0 && v < 10 { - for i := 0; i < v; i++ { - if i == 3 { break } - } - } else { v = 2 } - f := func(ok bool) bool { if ok { return true }; return false } - _ = f - return v -} -`) - files, err := loadHarnessSources(root) - if err != nil { - t.Fatal(err) - } - measured, err := measureFunctions(files) - if err != nil { - t.Fatal(err) - } - if len(measured) != 2 { - t.Fatalf("function count = %d, want 2: %#v", len(measured), measured) - } - outer := measured[0] - if outer.Symbol != "flow" || outer.Cyclomatic != 5 { - t.Fatalf("outer metrics = %#v", outer) - } - if outer.Nesting < 3 || outer.Cognitive < 7 || outer.Statements < 8 { - t.Fatalf("outer structural metrics = %#v", outer) - } - if !strings.HasPrefix(measured[1].Symbol, "flow.$func-") || measured[1].Cyclomatic != 2 { - t.Fatalf("literal metrics = %#v", measured[1]) - } -} - -func TestClosureIdentityIsScopedToItsParent(t *testing.T) { - root := t.TempDir() - writeTestFile(t, root, "harness/a.go", `package harness -func First() { _ = func() {} } -func Second() { _ = func() { _ = func() {} } } -`) - files, err := loadHarnessSources(root) - if err != nil { - t.Fatal(err) - } - measured, err := measureFunctions(files) - if err != nil { - t.Fatal(err) - } - wantParents := map[string]bool{"First.$func-": false, "Second.$func-": false} - for _, function := range measured { - for prefix := range wantParents { - if strings.HasPrefix(function.Symbol, prefix) { - wantParents[prefix] = true - } - } - } - for prefix, found := range wantParents { - if !found { - t.Fatalf("missing parent-scoped closure identity %s: %#v", prefix, measured) - } - } -} - -func TestNormalizedTokensIgnoreLiteralValuesAndFormatting(t *testing.T) { - root := t.TempDir() - writeTestFile(t, root, "harness/a.go", "package harness\nfunc a() string { return \"one\" }\nfunc b() string {\nreturn \"two\"\n}\n") - files, err := loadHarnessSources(root) - if err != nil { - t.Fatal(err) - } - measured, err := measureFunctions(files) - if err != nil { - t.Fatal(err) - } - if len(measured) != 2 { - t.Fatalf("functions = %#v", measured) - } - if measured[0].Tokens[len(measured[0].Tokens)-2] != "$string" || measured[1].Tokens[len(measured[1].Tokens)-2] != "$string" { - t.Fatalf("tokens were not normalized: %#v / %#v", measured[0].Tokens, measured[1].Tokens) - } -} - -func TestLogicalLinesTrackEnclosingSpanAndNestedClosureIndependently(t *testing.T) { - outer, child := measuredLogicalLines(t, `package harness -func outer() { - before() - use(func() { - nestedOne() - nestedTwo() - }) - after() -} -`) - if outer != 6 || child != 3 { - t.Fatalf("logical lines outer=%d child=%d, want 6/3", outer, child) - } - outer, child = measuredLogicalLines(t, `package harness -func outer() { - before() - use(func() { - nestedOne() - nestedTwo() - nestedThree() - nestedFour() - }) - after() -} -`) - if outer != 8 || child != 5 { - t.Fatalf("grown nested body logical lines outer=%d child=%d, want 8/5", outer, child) - } -} - -func measuredLogicalLines(t *testing.T, source string) (int, int) { - t.Helper() - root := t.TempDir() - writeTestFile(t, root, "harness/a.go", source) - files, err := loadHarnessSources(root) - if err != nil { - t.Fatal(err) - } - functions, err := measureFunctions(files) - if err != nil { - t.Fatal(err) - } - if len(functions) != 2 || functions[0].Symbol != "outer" || !strings.HasPrefix(functions[1].Symbol, "outer.$func-") { - t.Fatalf("measured functions = %#v", functions) - } - return functions[0].LogicalLines, functions[1].LogicalLines -} diff --git a/harness/tools/quality/ratchet.go b/harness/tools/quality/ratchet.go deleted file mode 100644 index d6a92e4a..00000000 --- a/harness/tools/quality/ratchet.go +++ /dev/null @@ -1,143 +0,0 @@ -package main - -import ( - "fmt" - "reflect" -) - -type measurementRatchet struct { - regularBaseline map[string]baselineEntry - duplicateBaseline []baselineEntry - exceptions map[string]exceptionEntry - usedBaseline map[string]struct{} - usedExceptions map[string]struct{} -} - -func compareMeasurement(baseline baselineManifest, exceptions exceptionManifest, measured baselineManifest) error { - ratchet := newMeasurementRatchet(baseline, exceptions) - for _, current := range measured.Entries { - if err := ratchet.check(current); err != nil { - return err - } - } - return ratchet.checkStale(baseline, exceptions) -} - -func newMeasurementRatchet(baseline baselineManifest, exceptions exceptionManifest) *measurementRatchet { - ratchet := &measurementRatchet{ - regularBaseline: make(map[string]baselineEntry), exceptions: make(map[string]exceptionEntry), - usedBaseline: make(map[string]struct{}), usedExceptions: make(map[string]struct{}), - } - for _, entry := range baseline.Entries { - if entry.Rule == ruleDuplicate { - ratchet.duplicateBaseline = append(ratchet.duplicateBaseline, entry) - } else { - ratchet.regularBaseline[entry.Identity] = entry - } - } - for _, entry := range exceptions.Entries { - ratchet.exceptions[entry.Identity] = entry - } - return ratchet -} - -func (ratchet *measurementRatchet) check(current baselineEntry) error { - tracked, err := ratchet.trackedEntry(current) - if err != nil { - return err - } - if tracked != nil { - return ratchet.checkTracked(*tracked, current) - } - return ratchet.checkException(current) -} - -func (ratchet *measurementRatchet) trackedEntry(current baselineEntry) (*baselineEntry, error) { - if current.Rule == ruleDuplicate { - return matchDuplicateBaseline(current, ratchet.duplicateBaseline, ratchet.usedBaseline) - } - prior, exists := ratchet.regularBaseline[current.Identity] - if !exists { - return nil, nil - } - return &prior, nil -} - -func (ratchet *measurementRatchet) checkTracked(prior, current baselineEntry) error { - if prior.Rule == ruleDuplicate && prior.Fingerprint != current.Fingerprint { - return fmt.Errorf("duplicate debt %s matched by owners but its baseline fingerprint is stale", prior.Identity) - } - if err := compareCeiling(prior, current); err != nil { - return err - } - ratchet.usedBaseline[prior.Identity] = struct{}{} - return nil -} - -func (ratchet *measurementRatchet) checkException(current baselineEntry) error { - exception, excepted := ratchet.exceptions[current.Identity] - if !excepted { - return fmt.Errorf("new untracked quality violation %s measured at %d", current.Identity, current.Ceiling) - } - if exception.Rule != current.Rule || exception.Path != current.Path || exception.Symbol != current.Symbol { - return fmt.Errorf("quality exception %s does not exactly match the measured violation", exception.Identity) - } - if current.Rule == ruleCyclomatic && current.Ceiling > 30 { - return fmt.Errorf("new cyclomatic complexity %s is %d and cannot be waived above 30", current.Identity, current.Ceiling) - } - if current.Ceiling != exception.Ceiling { - return compareExceptionCeiling(exception, current.Ceiling) - } - ratchet.usedExceptions[exception.Identity] = struct{}{} - return nil -} - -func compareExceptionCeiling(exception exceptionEntry, current int) error { - if current > exception.Ceiling { - return fmt.Errorf("quality exception %s increased from %d to %d", exception.Identity, exception.Ceiling, current) - } - return fmt.Errorf("quality exception %s improved from %d to %d; lower its ceiling in the same change", exception.Identity, exception.Ceiling, current) -} - -func (ratchet *measurementRatchet) checkStale(baseline baselineManifest, exceptions exceptionManifest) error { - for _, entry := range baseline.Entries { - if _, used := ratchet.usedBaseline[entry.Identity]; !used { - return fmt.Errorf("stale quality baseline entry %s has no current violation", entry.Identity) - } - } - for _, entry := range exceptions.Entries { - if _, used := ratchet.usedExceptions[entry.Identity]; !used { - return fmt.Errorf("stale quality exception %s has no current violation", entry.Identity) - } - } - return nil -} - -func compareCeiling(baseline, current baselineEntry) error { - if current.Ceiling > baseline.Ceiling { - return fmt.Errorf("quality violation %s increased from %d to %d", baseline.Identity, baseline.Ceiling, current.Ceiling) - } - if current.Ceiling < baseline.Ceiling { - return fmt.Errorf("quality violation %s improved from %d to %d; lower its baseline ceiling in the same change", baseline.Identity, baseline.Ceiling, current.Ceiling) - } - return nil -} - -func matchDuplicateBaseline(current baselineEntry, candidates []baselineEntry, used map[string]struct{}) (*baselineEntry, error) { - var exact []baselineEntry - for _, candidate := range candidates { - if _, alreadyUsed := used[candidate.Identity]; alreadyUsed || !reflect.DeepEqual(candidate.Owners, current.Owners) { - continue - } - if candidate.Fingerprint == current.Fingerprint { - exact = append(exact, candidate) - } - } - if len(exact) == 1 { - return &exact[0], nil - } - if len(exact) > 1 { - return nil, fmt.Errorf("duplicate violation with owners %v has ambiguous exact baseline matches", current.Owners) - } - return nil, nil -} diff --git a/harness/tools/quality/ratchet_test.go b/harness/tools/quality/ratchet_test.go deleted file mode 100644 index 7407e783..00000000 --- a/harness/tools/quality/ratchet_test.go +++ /dev/null @@ -1,96 +0,0 @@ -package main - -import ( - "strings" - "testing" -) - -func TestCompareMeasurementRejectsIncreaseAndUnloweredImprovement(t *testing.T) { - entry := baselineEntry{Rule: ruleFunctionLines, Identity: functionIdentity(ruleFunctionLines, "harness/a.go", "Run"), Path: "harness/a.go", Symbol: "Run", Ceiling: 90} - baseline := validBaselineManifest() - baseline.Entries = []baselineEntry{entry} - measured := baseline - measured.Entries = append([]baselineEntry(nil), baseline.Entries...) - measured.Entries[0].Ceiling = 91 - if err := compareMeasurement(baseline, exceptionManifest{Entries: []exceptionEntry{}}, measured); err == nil || !strings.Contains(err.Error(), "increased") { - t.Fatalf("increase error = %v", err) - } - measured.Entries[0].Ceiling = 89 - if err := compareMeasurement(baseline, exceptionManifest{Entries: []exceptionEntry{}}, measured); err == nil || !strings.Contains(err.Error(), "lower its baseline") { - t.Fatalf("improvement error = %v", err) - } -} - -func TestCompareMeasurementAcceptsExactException(t *testing.T) { - measuredEntry := baselineEntry{Rule: ruleCyclomatic, Identity: functionIdentity(ruleCyclomatic, "harness/new.go", "Run"), Path: "harness/new.go", Symbol: "Run", Ceiling: 24} - exception := exceptionEntry{Rule: measuredEntry.Rule, Identity: measuredEntry.Identity, Path: measuredEntry.Path, Symbol: measuredEntry.Symbol, Ceiling: 24} - if err := compareMeasurement( - baselineManifest{Entries: []baselineEntry{}}, - exceptionManifest{Entries: []exceptionEntry{exception}}, - baselineManifest{Entries: []baselineEntry{measuredEntry}}, - ); err != nil { - t.Fatal(err) - } - measuredEntry.Ceiling = 31 - exception.Ceiling = 30 - if err := compareMeasurement( - baselineManifest{Entries: []baselineEntry{}}, - exceptionManifest{Entries: []exceptionEntry{exception}}, - baselineManifest{Entries: []baselineEntry{measuredEntry}}, - ); err == nil || !strings.Contains(err.Error(), "cannot be waived") { - t.Fatalf("absolute cyclomatic error = %v", err) - } -} - -func TestCompareMeasurementRatchetsExceptionCeilingExactly(t *testing.T) { - entry := baselineEntry{Rule: ruleFunctionLines, Identity: functionIdentity(ruleFunctionLines, "harness/new.go", "Run"), Path: "harness/new.go", Symbol: "Run", Ceiling: 91} - exception := exceptionEntry{Rule: entry.Rule, Identity: entry.Identity, Path: entry.Path, Symbol: entry.Symbol, Ceiling: 90} - compare := func() error { - return compareMeasurement( - baselineManifest{Entries: []baselineEntry{}}, - exceptionManifest{Entries: []exceptionEntry{exception}}, - baselineManifest{Entries: []baselineEntry{entry}}, - ) - } - if err := compare(); err == nil || !strings.Contains(err.Error(), "increased") { - t.Fatalf("exception increase error = %v", err) - } - entry.Ceiling = 89 - if err := compare(); err == nil || !strings.Contains(err.Error(), "lower its ceiling") { - t.Fatalf("exception improvement error = %v", err) - } -} - -func TestMatchDuplicateBaselineRequiresCurrentExactEvidence(t *testing.T) { - owners := []string{"harness/a.go::A", "harness/b.go::B"} - baseline := baselineEntry{Rule: ruleDuplicate, Identity: ruleDuplicate + ":dup-reviewed", DebtID: "dup-reviewed", Owners: owners, Fingerprint: strings.Repeat("a", 64), Ceiling: 160} - current := baseline - current.Identity = ruleDuplicate + ":dup-0001" - current.DebtID = "dup-0001" - current.Fingerprint = strings.Repeat("b", 64) - matched, err := matchDuplicateBaseline(current, []baselineEntry{baseline}, map[string]struct{}{}) - if err != nil || matched != nil { - t.Fatalf("match = %#v, error = %v", matched, err) - } - current.Fingerprint = baseline.Fingerprint - matched, err = matchDuplicateBaseline(current, []baselineEntry{baseline}, map[string]struct{}{}) - if err != nil || matched == nil || matched.DebtID != "dup-reviewed" { - t.Fatalf("exact match = %#v, error = %v", matched, err) - } -} - -func TestMatchDuplicateBaselineRejectsAmbiguousExactEvidence(t *testing.T) { - owners := []string{"harness/a.go::A", "harness/b.go::B"} - fingerprint := strings.Repeat("a", 64) - first := baselineEntry{Rule: ruleDuplicate, Identity: ruleDuplicate + ":dup-first", DebtID: "dup-first", Owners: owners, Fingerprint: fingerprint, Ceiling: 160} - second := first - second.Identity = ruleDuplicate + ":dup-second" - second.DebtID = "dup-second" - current := first - current.Identity = ruleDuplicate + ":dup-0001" - current.DebtID = "dup-0001" - matched, err := matchDuplicateBaseline(current, []baselineEntry{first, second}, map[string]struct{}{}) - if err == nil || !strings.Contains(err.Error(), "ambiguous exact") || matched != nil { - t.Fatalf("ambiguous match = %#v, error = %v", matched, err) - } -} diff --git a/harness/tools/quality/source.go b/harness/tools/quality/source.go deleted file mode 100644 index f08b4b5e..00000000 --- a/harness/tools/quality/source.go +++ /dev/null @@ -1,224 +0,0 @@ -package main - -import ( - "bytes" - "fmt" - "go/ast" - "go/format" - "go/parser" - "go/token" - "io/fs" - "os" - "path/filepath" - "regexp" - "sort" - "strings" -) - -const modulePath = "github.com/mnemon-dev/mnemon" - -var ( - generatedDirectivePattern = regexp.MustCompile(`^// Code generated .+ DO NOT EDIT\.$`) - nolintDirectivePattern = regexp.MustCompile(`^//nolint:([A-Za-z0-9_,]+)(?: -- | // )\S.*$`) -) - -type sourceFile struct { - Path string - Absolute string - Data []byte - AST *ast.File - FileSet *token.FileSet - IsTest bool - MetricExcluded bool - LineCount int -} - -type architectureFinding struct { - Rule string - Identity string - Path string - Component string - Evidence string -} - -func loadHarnessSources(root string) ([]sourceFile, error) { - root, err := filepath.Abs(root) - if err != nil { - return nil, fmt.Errorf("resolve root: %w", err) - } - harnessRoot := filepath.Join(root, "harness") - if info, statErr := os.Stat(harnessRoot); statErr != nil || !info.IsDir() { - return nil, fmt.Errorf("root %q does not contain a harness directory", root) - } - exclusions, err := loadQualityExclusions(root, false) - if err != nil { - return nil, err - } - if err := validateExclusionEvidence(root, exclusions); err != nil { - return nil, err - } - excludedKinds := exclusionKinds(exclusions) - scope := loadRepositoryGoScope(root) - var files []sourceFile - err = filepath.WalkDir(harnessRoot, func(path string, entry fs.DirEntry, walkErr error) error { - if walkErr != nil { - return walkErr - } - if entry.IsDir() { - if path != harnessRoot && skipRepositoryDirectory(root, path, entry.Name(), scope) { - return filepath.SkipDir - } - return nil - } - if entry.Type()&os.ModeSymlink != 0 || !strings.HasSuffix(entry.Name(), ".go") { - return nil - } - relative, relErr := filepath.Rel(root, path) - if relErr != nil { - return fmt.Errorf("relativize %s: %w", path, relErr) - } - relative = filepath.ToSlash(relative) - if scope.gitScoped { - if _, included := scope.paths[relative]; !included { - return nil - } - } - data, readErr := os.ReadFile(path) - if readErr != nil { - return fmt.Errorf("read %s: %w", path, readErr) - } - fileSet := token.NewFileSet() - parsed, parseErr := parser.ParseFile(fileSet, path, data, parser.ParseComments|parser.SkipObjectResolution) - if parseErr != nil { - return fmt.Errorf("parse %s: %w", filepath.ToSlash(relative), parseErr) - } - files = append(files, sourceFile{ - Path: relative, Absolute: path, Data: data, AST: parsed, FileSet: fileSet, - IsTest: strings.HasSuffix(entry.Name(), "_test.go"), MetricExcluded: excludedKinds[relative] != "", - LineCount: physicalLineCount(data), - }) - return nil - }) - if err != nil { - return nil, fmt.Errorf("walk harness sources: %w", err) - } - sort.Slice(files, func(i, j int) bool { return files[i].Path < files[j].Path }) - return files, nil -} - -func metricEligibleSources(files []sourceFile) []sourceFile { - eligible := make([]sourceFile, 0, len(files)) - for _, file := range files { - if !file.MetricExcluded { - eligible = append(eligible, file) - } - } - return eligible -} - -func isGeneratedSource(data []byte) bool { - parsed, err := parser.ParseFile(token.NewFileSet(), "generated.go", data, parser.ParseComments|parser.SkipObjectResolution) - if err != nil { - return false - } - for _, group := range parsed.Comments { - if group.Pos() > parsed.Package { - break - } - for _, comment := range group.List { - if generatedDirectivePattern.MatchString(strings.TrimSuffix(comment.Text, "\r")) { - return true - } - } - } - return false -} - -func physicalLineCount(data []byte) int { - if len(data) == 0 { - return 0 - } - lines := bytes.Count(data, []byte{'\n'}) - if data[len(data)-1] != '\n' { - lines++ - } - return lines -} - -func gofmtDrift(files []sourceFile) ([]string, error) { - var drift []string - for _, file := range files { - formatted, err := format.Source(file.Data) - if err != nil { - return nil, fmt.Errorf("gofmt %s: %w", file.Path, err) - } - if !bytes.Equal(formatted, file.Data) { - drift = append(drift, file.Path) - } - } - return drift, nil -} - -func nolintDiagnostics(files []sourceFile) []string { - var diagnostics []string - for _, file := range files { - for _, group := range file.AST.Comments { - for _, comment := range group.List { - text := strings.TrimSpace(comment.Text) - if !strings.HasPrefix(text, "//nolint") { - continue - } - match := nolintDirectivePattern.FindStringSubmatch(text) - if len(match) != 2 || lintListHasWildcard(match[1]) { - line := file.FileSet.PositionFor(comment.Pos(), false).Line - diagnostics = append(diagnostics, fmt.Sprintf("%s:%d", file.Path, line)) - } - } - } - } - return diagnostics -} - -func lintListHasWildcard(list string) bool { - for _, name := range strings.Split(list, ",") { - if name == "all" || name == "*" || name == "" { - return true - } - } - return false -} - -func declaredSymbols(file sourceFile) map[string]struct{} { - symbols := make(map[string]struct{}) - for _, declaration := range file.AST.Decls { - function, ok := declaration.(*ast.FuncDecl) - if !ok { - continue - } - symbols[functionSymbol(function)] = struct{}{} - } - return symbols -} - -func functionSymbol(function *ast.FuncDecl) string { - if function.Recv == nil || len(function.Recv.List) == 0 { - return function.Name.Name - } - receiver := function.Recv.List[0].Type - return receiverSymbol(receiver) + "." + function.Name.Name -} - -func receiverSymbol(expression ast.Expr) string { - switch value := expression.(type) { - case *ast.Ident: - return value.Name - case *ast.StarExpr: - return "(*" + receiverSymbol(value.X) + ")" - case *ast.IndexExpr: - return receiverSymbol(value.X) - case *ast.IndexListExpr: - return receiverSymbol(value.X) - default: - return "(?)" - } -} diff --git a/harness/tools/quality/source_test.go b/harness/tools/quality/source_test.go deleted file mode 100644 index 6671fd5d..00000000 --- a/harness/tools/quality/source_test.go +++ /dev/null @@ -1,301 +0,0 @@ -package main - -import ( - "os" - "path/filepath" - "strings" - "testing" -) - -func TestLoadHarnessSourcesUsesExplicitMetricExclusions(t *testing.T) { - root := t.TempDir() - writeTestFile(t, root, "harness/kept.go", "package harness\nfunc kept() {}\n") - writeTestFile(t, root, "harness/kept_test.go", "package harness\nfunc TestKept() {}\n") - writeTestFile(t, root, "harness/generated.go", "// Code generated by fixture. DO NOT EDIT.\npackage harness\n") - writeTestFile(t, root, "harness/testdata/ignored.go", "package ignored\n") - writeTestFile(t, root, "harness/.projection/ignored.go", "package ignored\n") - writeCanonicalTestFile(t, root, exclusionsPath, exclusionManifest{SchemaVersion: 1, Entries: []exclusionEntry{ - {Path: "harness/generated.go", Kind: exclusionGenerated, Reason: "generated fixture", Owner: "test"}, - {Path: "harness/testdata/ignored.go", Kind: exclusionTestdata, Reason: "parser fixture", Owner: "test"}, - }}) - - files, err := loadHarnessSources(root) - if err != nil { - t.Fatal(err) - } - if len(files) != 4 { - t.Fatalf("loaded paths = %#v", sourcePaths(files)) - } - eligible := metricEligibleSources(files) - if len(eligible) != 2 || eligible[0].Path != "harness/kept.go" || eligible[1].Path != "harness/kept_test.go" { - t.Fatalf("metric paths = %#v", sourcePaths(eligible)) - } - if eligible[0].LineCount != 2 || !eligible[1].IsTest { - t.Fatalf("source metadata = %#v / %#v", eligible[0], eligible[1]) - } -} - -func TestGeneratedAndTestdataSourcesAreIncludedWithoutManifestEntries(t *testing.T) { - root := t.TempDir() - writeTestFile(t, root, "harness/generated.go", "// Code generated by fixture. DO NOT EDIT.\npackage harness\n") - writeTestFile(t, root, "harness/testdata/fixture.go", "package fixture\n") - files, err := loadHarnessSources(root) - if err != nil { - t.Fatal(err) - } - if len(files) != 2 || len(metricEligibleSources(files)) != 2 { - t.Fatalf("unmanifested sources were hidden: %#v", sourcePaths(files)) - } -} - -func TestGeneratedDetectionRequiresCanonicalHeaderBeforePackage(t *testing.T) { - if !isGeneratedSource([]byte("// Code generated by fixture. DO NOT EDIT.\npackage generated\n")) { - t.Fatal("canonical generated header was not detected") - } - spoof := []byte("package harness\nconst text = `// Code generated by fixture. DO NOT EDIT.`\n") - if isGeneratedSource(spoof) { - t.Fatal("generated phrase in source content was trusted") - } - blockSpoof := []byte("/*\n// Code generated by fixture. DO NOT EDIT.\n*/\npackage harness\n") - if isGeneratedSource(blockSpoof) { - t.Fatal("line-shaped text inside a block comment was trusted as a generated directive") - } -} - -func TestNolintDiagnosticsRequireExactLintAndReason(t *testing.T) { - root := t.TempDir() - writeTestFile(t, root, "harness/a.go", `package harness -//nolint:errcheck -- lock is released again on close -func Good() {} -//nolint:gosec // test fixture uses a fixed credential -func AlsoGood() {} -//nolint -func Bare() {} -//nolint:all -- blanket waiver -func Wildcard() {} -//nolint:errcheck -func Unexplained() {} -`) - files, err := loadHarnessSources(root) - if err != nil { - t.Fatal(err) - } - diagnostics := nolintDiagnostics(files) - if len(diagnostics) != 3 { - t.Fatalf("diagnostics = %#v", diagnostics) - } -} - -func TestDependencyFindingsIgnoreHiddenLocalTrees(t *testing.T) { - root := t.TempDir() - writeTestFile(t, root, ".mnemon-local/bad.go", `package local -import _ "github.com/mnemon-dev/mnemon/harness/internal/store" -`) - writeTestFile(t, root, "harness/.projection/bad.go", `package projection -import _ "github.com/mnemon-dev/mnemon/internal/store" -`) - findings, err := dependencyFindings(root) - if err != nil { - t.Fatal(err) - } - if len(findings) != 0 { - t.Fatalf("hidden local findings = %#v", findings) - } -} - -func TestSourceScansExcludeNonhiddenGitignoredGo(t *testing.T) { - root := initTestRepository(t) - writeTestFile(t, root, ".gitignore", "scratch/\n") - writeTestFile(t, root, "harness/kept.go", "package harness\n") - writeTestFile(t, root, "harness/scratch/ignored.go", `package scratch -import _ "github.com/mnemon-dev/mnemon/internal/store" -`) - files, err := loadHarnessSources(root) - if err != nil { - t.Fatal(err) - } - if len(files) != 1 || files[0].Path != "harness/kept.go" { - t.Fatalf("files = %#v", sourcePaths(files)) - } - findings, err := dependencyFindings(root) - if err != nil { - t.Fatal(err) - } - if len(findings) != 0 { - t.Fatalf("ignored findings = %#v", findings) - } -} - -func TestRepositoryGoScopePrunesIgnoredDirectoryBeforeTraversal(t *testing.T) { - root := initTestRepository(t) - writeTestFile(t, root, ".gitignore", "scratch/\n") - writeTestFile(t, root, "harness/kept.go", "package harness\n") - if _, err := os.Stat(filepath.Join(root, "harness", "scratch")); !os.IsNotExist(err) { - t.Fatalf("scratch precondition: %v", err) - } - scope := loadRepositoryGoScope(root) - ignored := filepath.Join(root, "harness", "scratch") - if !skipRepositoryDirectory(root, ignored, "scratch", scope) { - t.Fatal("gitignored directory was left traversable despite having no candidate Go path") - } -} - -func TestGofmtDriftAndDependencyDirection(t *testing.T) { - root := t.TempDir() - writeTestFile(t, root, "harness/internal/authority/good.go", "package authority\n\nimport \"github.com/mnemon-dev/mnemon/harness/internal/agency\"\n") - writeTestFile(t, root, "harness/internal/authority/good_test.go", "package authority\n") - writeTestFile(t, root, "harness/internal/authority/bad.go", "package authority\nimport \"github.com/mnemon-dev/mnemon/internal/model\"\n") - writeTestFile(t, root, "cmd/bad.go", "package cmd\nimport \"github.com/mnemon-dev/mnemon/harness/internal/agency\"\n") - files, err := loadHarnessSources(root) - if err != nil { - t.Fatal(err) - } - drift, err := gofmtDrift(files) - if err != nil { - t.Fatal(err) - } - if len(drift) != 1 || drift[0] != "harness/internal/authority/bad.go" { - t.Fatalf("gofmt drift = %#v", drift) - } - findings, err := dependencyFindings(root) - if err != nil { - t.Fatal(err) - } - if len(findings) != 2 || !strings.Contains(findings[0].Identity+findings[1].Identity, "root_harness_dependency") { - t.Fatalf("dependency findings = %#v", findings) - } -} - -func TestDependencyFindingsEnforceFrozenPackageMapAndLibp2pFloor(t *testing.T) { - root := t.TempDir() - writeTestFile(t, root, "harness/internal/cli/bad.go", `package cli -import ( - _ "github.com/mnemon-dev/mnemon/harness/internal/authority" - _ "github.com/libp2p/go-libp2p-core/peer" -) -`) - findings, err := dependencyFindings(root) - if err != nil { - t.Fatal(err) - } - if len(findings) != 2 { - t.Fatalf("findings = %#v", findings) - } - if findings[0].Rule != "dependency_direction" || findings[1].Rule != "deprecated_libp2p_core" { - t.Fatalf("finding rules = %#v", findings) - } -} - -func TestDependencyFindingsTrackUnexpectedProductionPackageButNotTestImports(t *testing.T) { - root := t.TempDir() - writeTestFile(t, root, "harness/internal/store/unknown.go", "package store\n") - writeTestFile(t, root, "harness/internal/cli/blackbox_test.go", `package cli_test -import _ "github.com/mnemon-dev/mnemon/harness/internal/store" -`) - findings, err := dependencyFindings(root) - if err != nil { - t.Fatal(err) - } - if len(findings) != 1 || findings[0].Rule != "unexpected_harness_package" { - t.Fatalf("findings = %#v", findings) - } -} - -func TestDependencyFindingsTreatTestdataAsTestComposition(t *testing.T) { - root := t.TempDir() - writeTestFile(t, root, "harness/internal/selector/testdata/network/main.go", `package main -import ( - _ "github.com/mnemon-dev/mnemon/harness/internal/authority" - _ "github.com/mnemon-dev/mnemon/harness/internal/cas" -) -`) - findings, err := dependencyFindings(root) - if err != nil { - t.Fatal(err) - } - if len(findings) != 0 { - t.Fatalf("testdata composition findings = %#v", findings) - } -} - -func TestDependencyFindingsTestdataStillEnforcesLegacyBoundary(t *testing.T) { - root := t.TempDir() - writeTestFile(t, root, "harness/internal/selector/testdata/network/main.go", `package main -import _ "github.com/mnemon-dev/mnemon/internal/model" -`) - findings, err := dependencyFindings(root) - if err != nil { - t.Fatal(err) - } - if len(findings) != 1 || findings[0].Rule != "harness_legacy_dependency" { - t.Fatalf("testdata legacy findings = %#v", findings) - } -} - -func TestDependencyFindingsAllowBottomModelAndUsePackageIdentity(t *testing.T) { - root := t.TempDir() - writeTestFile(t, root, "harness/internal/authority/a.go", `package authority -import _ "github.com/mnemon-dev/mnemon/harness/internal/cas" -`) - writeTestFile(t, root, "harness/internal/authority/b.go", `package authority -import _ "github.com/mnemon-dev/mnemon/harness/internal/cas" -`) - findings, err := dependencyFindings(root) - if err != nil { - t.Fatal(err) - } - if len(findings) != 1 { - t.Fatalf("findings = %#v", findings) - } - if findings[0].Identity != "dependency_direction:harness/internal/authority::cas" || findings[0].Evidence != "harness/internal/authority/a.go" { - t.Fatalf("package-edge finding = %#v", findings[0]) - } -} - -func TestDependencyFindingsAllowNeutralAgencyCLILayer(t *testing.T) { - root := t.TempDir() - writeTestFile(t, root, "harness/cmd/mnemon-harness/main.go", `package main -import _ "github.com/mnemon-dev/mnemon/harness/internal/cli" -`) - writeTestFile(t, root, "harness/internal/cli/app.go", `package cli -import _ "github.com/mnemon-dev/mnemon/harness/internal/agency" -`) - findings, err := dependencyFindings(root) - if err != nil { - t.Fatal(err) - } - if len(findings) != 0 { - t.Fatalf("neutral Agency CLI dependency findings = %#v", findings) - } -} - -func TestFunctionSymbolIncludesReceiver(t *testing.T) { - root := t.TempDir() - writeTestFile(t, root, "harness/a.go", "package harness\ntype T struct{}\nfunc (t *T) Run() {}\n") - files, err := loadHarnessSources(root) - if err != nil { - t.Fatal(err) - } - if _, ok := declaredSymbols(files[0])["(*T).Run"]; !ok { - t.Fatalf("symbols = %#v", declaredSymbols(files[0])) - } -} - -func writeTestFile(t *testing.T, root, relative, content string) { - t.Helper() - path := filepath.Join(root, filepath.FromSlash(relative)) - if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(path, []byte(content), 0o600); err != nil { - t.Fatal(err) - } -} - -func sourcePaths(files []sourceFile) []string { - paths := make([]string, len(files)) - for i := range files { - paths[i] = files[i].Path - } - return paths -} diff --git a/internal/setup/codebuddy_test.go b/internal/setup/codebuddy_test.go deleted file mode 100644 index 0209250c..00000000 --- a/internal/setup/codebuddy_test.go +++ /dev/null @@ -1,153 +0,0 @@ -package setup - -import ( - "os" - "path/filepath" - "strings" - "testing" -) - -func TestCodeBuddyWriteSkill(t *testing.T) { - dir := t.TempDir() - - skillPath, err := CodeBuddyWriteSkill(dir) - if err != nil { - t.Fatalf("write skill: %v", err) - } - if skillPath != filepath.Join(dir, "skills", "mnemon", "SKILL.md") { - t.Fatalf("skill path = %q", skillPath) - } - data, err := os.ReadFile(skillPath) - if err != nil { - t.Fatalf("read skill: %v", err) - } - if !strings.Contains(string(data), "CodeBuddy") { - t.Fatalf("codebuddy skill should mention CodeBuddy: %s", string(data)) - } -} - -func TestCodeBuddyWriteHook(t *testing.T) { - dir := t.TempDir() - - hookPath, err := CodeBuddyWriteHook(dir, "prime.sh", []byte("#!/bin/bash\n")) - if err != nil { - t.Fatalf("write hook: %v", err) - } - if hookPath != filepath.Join(dir, "hooks", "mnemon", "prime.sh") { - t.Fatalf("hook path = %q", hookPath) - } - info, err := os.Stat(hookPath) - if err != nil { - t.Fatalf("stat hook: %v", err) - } - if info.Mode().Perm() != 0755 { - t.Fatalf("hook permissions = %v, want 0755", info.Mode().Perm()) - } -} - -func TestCodeBuddyRegisterHooksPreservesUnrelatedConfig(t *testing.T) { - dir := t.TempDir() - settingsPath := filepath.Join(dir, "settings.json") - if err := os.WriteFile(settingsPath, []byte(`{ - "hooks": { - "SessionStart": [ - {"hooks": [{"type": "command", "command": "/old/mnemon/prime.sh"}]}, - {"hooks": [{"type": "command", "command": "/keep/custom.sh"}]} - ], - "Stop": [ - {"hooks": [{"type": "command", "command": "/old/mnemon/stop.sh"}]} - ] - }, - "other": true -}`), 0644); err != nil { - t.Fatalf("write settings: %v", err) - } - - if _, err := CodeBuddyRegisterHooks(dir); err != nil { - t.Fatalf("register hooks: %v", err) - } - - data, err := ReadJSONFile(settingsPath) - if err != nil { - t.Fatalf("read settings: %v", err) - } - if data["other"] != true { - t.Fatalf("unrelated setting should be preserved: %#v", data) - } - hooks := data["hooks"].(map[string]any) - sessionStart := hooks["SessionStart"].([]any) - if len(sessionStart) != 2 { - t.Fatalf("expected custom hook plus new prime hook: %#v", sessionStart) - } - if !strings.Contains(sessionStart[1].(map[string]any)["hooks"].([]any)[0].(map[string]any)["command"].(string), "hooks/mnemon/prime.sh") { - t.Fatalf("expected new prime hook, got %#v", sessionStart[1]) - } - if _, ok := hooks["UserPromptSubmit"]; !ok { - t.Fatalf("user prompt hook should be registered: %#v", hooks) - } - stop := hooks["Stop"].([]any) - if len(stop) != 1 { - t.Fatalf("expected one stop hook: %#v", stop) - } - if _, ok := stop[0].(map[string]any)["loop_limit"]; ok { - t.Fatalf("codebuddy hook schema should not include loop_limit: %#v", stop[0]) - } -} - -func TestCodeBuddyEjectRemovesOnlyMnemonFilesAndHooks(t *testing.T) { - dir := t.TempDir() - if _, err := CodeBuddyWriteSkill(dir); err != nil { - t.Fatalf("write skill: %v", err) - } - if _, err := CodeBuddyWriteHook(dir, "prime.sh", []byte("#!/bin/bash\n")); err != nil { - t.Fatalf("write hook: %v", err) - } - if _, err := CodeBuddyRegisterHooks(dir); err != nil { - t.Fatalf("register hooks: %v", err) - } - customSkillDir := filepath.Join(dir, "skills", "custom") - if err := os.MkdirAll(customSkillDir, 0755); err != nil { - t.Fatalf("create custom skill: %v", err) - } - settingsPath := filepath.Join(dir, "settings.json") - data, err := ReadJSONFile(settingsPath) - if err != nil { - t.Fatalf("read settings: %v", err) - } - hooks := data["hooks"].(map[string]any) - hooks["SessionStart"] = append(hooks["SessionStart"].([]any), map[string]any{ - "hooks": []any{map[string]any{"type": "command", "command": "/keep/custom.sh"}}, - }) - if err := WriteJSONFile(settingsPath, data); err != nil { - t.Fatalf("write settings: %v", err) - } - - errs := CodeBuddyEject(dir) - if len(errs) > 0 { - t.Fatalf("eject errors: %v", errs) - } - if _, err := os.Stat(filepath.Join(dir, "skills", "mnemon")); !os.IsNotExist(err) { - t.Fatalf("mnemon skill should be removed, err=%v", err) - } - if _, err := os.Stat(customSkillDir); err != nil { - t.Fatalf("custom skill should be preserved: %v", err) - } - if _, err := os.Stat(filepath.Join(dir, "hooks", "mnemon")); !os.IsNotExist(err) { - t.Fatalf("mnemon hooks should be removed, err=%v", err) - } - data, err = ReadJSONFile(settingsPath) - if err != nil { - t.Fatalf("read settings after eject: %v", err) - } - hooks = data["hooks"].(map[string]any) - sessionStart := hooks["SessionStart"].([]any) - if len(sessionStart) != 1 || containsMnemon(sessionStart[0]) { - t.Fatalf("custom hook should be preserved and mnemon removed: %#v", sessionStart) - } - if _, ok := hooks["UserPromptSubmit"]; ok { - t.Fatalf("user prompt hooks should be removed: %#v", hooks) - } - if _, ok := hooks["Stop"]; ok { - t.Fatalf("stop hooks should be removed: %#v", hooks) - } -} diff --git a/internal/setup/cursor_test.go b/internal/setup/cursor_test.go index 203fc6e3..55724928 100644 --- a/internal/setup/cursor_test.go +++ b/internal/setup/cursor_test.go @@ -7,40 +7,6 @@ import ( "testing" ) -func TestCursorWriteSkill(t *testing.T) { - dir := t.TempDir() - - skillPath, err := CursorWriteSkill(dir) - if err != nil { - t.Fatalf("write skill: %v", err) - } - if skillPath != filepath.Join(dir, "skills", "mnemon", "SKILL.md") { - t.Fatalf("skill path = %q", skillPath) - } - if _, err := os.Stat(skillPath); err != nil { - t.Fatalf("stat skill: %v", err) - } -} - -func TestCursorWriteHook(t *testing.T) { - dir := t.TempDir() - - hookPath, err := CursorWriteHook(dir, "prime.sh", []byte("#!/bin/bash\n")) - if err != nil { - t.Fatalf("write hook: %v", err) - } - if hookPath != filepath.Join(dir, "hooks", "mnemon", "prime.sh") { - t.Fatalf("hook path = %q", hookPath) - } - info, err := os.Stat(hookPath) - if err != nil { - t.Fatalf("stat hook: %v", err) - } - if info.Mode().Perm() != 0755 { - t.Fatalf("hook permissions = %v, want 0755", info.Mode().Perm()) - } -} - func TestCursorRegisterHooksPreservesUnrelatedConfig(t *testing.T) { dir := t.TempDir() hooksPath := filepath.Join(dir, "hooks.json") diff --git a/internal/setup/host_artifacts_test.go b/internal/setup/host_artifacts_test.go new file mode 100644 index 00000000..69579ea1 --- /dev/null +++ b/internal/setup/host_artifacts_test.go @@ -0,0 +1,72 @@ +package setup + +import ( + "bytes" + "os" + "path/filepath" + "testing" + + "github.com/mnemon-dev/mnemon/internal/setup/assets" +) + +func TestHostSkillAndHookArtifacts(t *testing.T) { + type writeSkill func(string) (string, error) + type writeHook func(string, string, []byte) (string, error) + + tests := []struct { + name string + skill []byte + writeSkill writeSkill + writeHook writeHook + }{ + {name: "CodeBuddy", skill: assets.CodeBuddySkill, writeSkill: CodeBuddyWriteSkill, writeHook: CodeBuddyWriteHook}, + {name: "Cursor", skill: assets.CursorSkill, writeSkill: CursorWriteSkill, writeHook: CursorWriteHook}, + {name: "Kimi", skill: assets.KimiSkill, writeSkill: KimiWriteSkill, writeHook: KimiWriteHook}, + {name: "Trae", skill: assets.TraeSkill, writeSkill: TraeWriteSkill, writeHook: TraeWriteHook}, + {name: "WorkBuddy", skill: assets.WorkBuddySkill, writeSkill: WorkBuddyWriteSkill, writeHook: WorkBuddyWriteHook}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + dir := t.TempDir() + + skillPath, err := test.writeSkill(dir) + if err != nil { + t.Fatalf("write skill: %v", err) + } + if want := filepath.Join(dir, "skills", "mnemon", "SKILL.md"); skillPath != want { + t.Fatalf("skill path = %q, want %q", skillPath, want) + } + skill, err := os.ReadFile(skillPath) + if err != nil { + t.Fatalf("read skill: %v", err) + } + if !bytes.Equal(skill, test.skill) { + t.Fatalf("skill content differs from embedded asset") + } + + hook := []byte("#!/bin/bash\n") + hookPath, err := test.writeHook(dir, "prime.sh", hook) + if err != nil { + t.Fatalf("write hook: %v", err) + } + if want := filepath.Join(dir, "hooks", "mnemon", "prime.sh"); hookPath != want { + t.Fatalf("hook path = %q, want %q", hookPath, want) + } + info, err := os.Stat(hookPath) + if err != nil { + t.Fatalf("stat hook: %v", err) + } + if info.Mode().Perm() != 0o755 { + t.Fatalf("hook permissions = %v, want 0755", info.Mode().Perm()) + } + writtenHook, err := os.ReadFile(hookPath) + if err != nil { + t.Fatalf("read hook: %v", err) + } + if !bytes.Equal(writtenHook, hook) { + t.Fatalf("hook content changed while writing") + } + }) + } +} diff --git a/internal/setup/kimi_test.go b/internal/setup/kimi_test.go index b8fd62e1..15ed4cc6 100644 --- a/internal/setup/kimi_test.go +++ b/internal/setup/kimi_test.go @@ -7,44 +7,6 @@ import ( "testing" ) -func TestKimiWriteSkill(t *testing.T) { - dir := t.TempDir() - - skillPath, err := KimiWriteSkill(dir) - if err != nil { - t.Fatalf("write skill: %v", err) - } - if skillPath != filepath.Join(dir, "skills", "mnemon", "SKILL.md") { - t.Fatalf("skill path = %q", skillPath) - } - data, err := os.ReadFile(skillPath) - if err != nil { - t.Fatalf("read skill: %v", err) - } - if !strings.Contains(string(data), "Kimi Code") { - t.Fatalf("kimi skill should mention Kimi Code: %s", string(data)) - } -} - -func TestKimiWriteHook(t *testing.T) { - dir := t.TempDir() - - hookPath, err := KimiWriteHook(dir, "prime.sh", []byte("#!/bin/bash\n")) - if err != nil { - t.Fatalf("write hook: %v", err) - } - if hookPath != filepath.Join(dir, "hooks", "mnemon", "prime.sh") { - t.Fatalf("hook path = %q", hookPath) - } - info, err := os.Stat(hookPath) - if err != nil { - t.Fatalf("stat hook: %v", err) - } - if info.Mode().Perm() != 0755 { - t.Fatalf("hook permissions = %v, want 0755", info.Mode().Perm()) - } -} - func TestKimiRegisterHooksPreservesUnrelatedConfig(t *testing.T) { dir := t.TempDir() configPath := filepath.Join(dir, "config.toml") diff --git a/internal/setup/nested_settings_hosts_test.go b/internal/setup/nested_settings_hosts_test.go new file mode 100644 index 00000000..0e7adaef --- /dev/null +++ b/internal/setup/nested_settings_hosts_test.go @@ -0,0 +1,148 @@ +package setup + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +type nestedSettingsHost struct { + name string + writeSkill func(string) (string, error) + writeHook func(string, string, []byte) (string, error) + register func(string) (string, error) + eject func(string) []error +} + +var nestedSettingsHosts = []nestedSettingsHost{ + { + name: "CodeBuddy", + writeSkill: CodeBuddyWriteSkill, + writeHook: CodeBuddyWriteHook, + register: CodeBuddyRegisterHooks, + eject: CodeBuddyEject, + }, + { + name: "WorkBuddy", + writeSkill: WorkBuddyWriteSkill, + writeHook: WorkBuddyWriteHook, + register: WorkBuddyRegisterHooks, + eject: WorkBuddyEject, + }, +} + +func TestNestedSettingsHostsRegisterHooks(t *testing.T) { + for _, host := range nestedSettingsHosts { + t.Run(host.name, func(t *testing.T) { + dir := t.TempDir() + settingsPath := filepath.Join(dir, "settings.json") + if err := os.WriteFile(settingsPath, []byte(`{ + "hooks": { + "SessionStart": [ + {"hooks": [{"type": "command", "command": "/old/mnemon/prime.sh"}]}, + {"hooks": [{"type": "command", "command": "/keep/custom.sh"}]} + ], + "Stop": [ + {"hooks": [{"type": "command", "command": "/old/mnemon/stop.sh"}]} + ] + }, + "other": true +}`), 0o644); err != nil { + t.Fatalf("write settings: %v", err) + } + + if _, err := host.register(dir); err != nil { + t.Fatalf("register hooks: %v", err) + } + + data, err := ReadJSONFile(settingsPath) + if err != nil { + t.Fatalf("read settings: %v", err) + } + if data["other"] != true { + t.Fatalf("unrelated setting should be preserved: %#v", data) + } + hooks := data["hooks"].(map[string]any) + sessionStart := hooks["SessionStart"].([]any) + if len(sessionStart) != 2 { + t.Fatalf("expected custom hook plus new prime hook: %#v", sessionStart) + } + command := sessionStart[1].(map[string]any)["hooks"].([]any)[0].(map[string]any)["command"].(string) + if !strings.Contains(command, "hooks/mnemon/prime.sh") { + t.Fatalf("expected new prime hook, got %#v", sessionStart[1]) + } + if _, ok := hooks["UserPromptSubmit"]; !ok { + t.Fatalf("user prompt hook should be registered: %#v", hooks) + } + stop := hooks["Stop"].([]any) + if len(stop) != 1 { + t.Fatalf("expected one stop hook: %#v", stop) + } + if _, ok := stop[0].(map[string]any)["loop_limit"]; ok { + t.Fatalf("nested settings hook schema should not include loop_limit: %#v", stop[0]) + } + }) + } +} + +func TestNestedSettingsHostsEject(t *testing.T) { + for _, host := range nestedSettingsHosts { + t.Run(host.name, func(t *testing.T) { + dir := t.TempDir() + if _, err := host.writeSkill(dir); err != nil { + t.Fatalf("write skill: %v", err) + } + if _, err := host.writeHook(dir, "prime.sh", []byte("#!/bin/bash\n")); err != nil { + t.Fatalf("write hook: %v", err) + } + if _, err := host.register(dir); err != nil { + t.Fatalf("register hooks: %v", err) + } + customSkillDir := filepath.Join(dir, "skills", "custom") + if err := os.MkdirAll(customSkillDir, 0o755); err != nil { + t.Fatalf("create custom skill: %v", err) + } + settingsPath := filepath.Join(dir, "settings.json") + data, err := ReadJSONFile(settingsPath) + if err != nil { + t.Fatalf("read settings: %v", err) + } + hooks := data["hooks"].(map[string]any) + hooks["SessionStart"] = append(hooks["SessionStart"].([]any), map[string]any{ + "hooks": []any{map[string]any{"type": "command", "command": "/keep/custom.sh"}}, + }) + if err := WriteJSONFile(settingsPath, data); err != nil { + t.Fatalf("write settings: %v", err) + } + + if errs := host.eject(dir); len(errs) > 0 { + t.Fatalf("eject errors: %v", errs) + } + if _, err := os.Stat(filepath.Join(dir, "skills", "mnemon")); !os.IsNotExist(err) { + t.Fatalf("mnemon skill should be removed, err=%v", err) + } + if _, err := os.Stat(customSkillDir); err != nil { + t.Fatalf("custom skill should be preserved: %v", err) + } + if _, err := os.Stat(filepath.Join(dir, "hooks", "mnemon")); !os.IsNotExist(err) { + t.Fatalf("mnemon hooks should be removed, err=%v", err) + } + data, err = ReadJSONFile(settingsPath) + if err != nil { + t.Fatalf("read settings after eject: %v", err) + } + hooks = data["hooks"].(map[string]any) + sessionStart := hooks["SessionStart"].([]any) + if len(sessionStart) != 1 || containsMnemon(sessionStart[0]) { + t.Fatalf("custom hook should be preserved and mnemon removed: %#v", sessionStart) + } + if _, ok := hooks["UserPromptSubmit"]; ok { + t.Fatalf("user prompt hooks should be removed: %#v", hooks) + } + if _, ok := hooks["Stop"]; ok { + t.Fatalf("stop hooks should be removed: %#v", hooks) + } + }) + } +} diff --git a/internal/setup/trae_test.go b/internal/setup/trae_test.go index 40336345..262fb5a3 100644 --- a/internal/setup/trae_test.go +++ b/internal/setup/trae_test.go @@ -7,40 +7,6 @@ import ( "testing" ) -func TestTraeWriteSkill(t *testing.T) { - dir := t.TempDir() - - skillPath, err := TraeWriteSkill(dir) - if err != nil { - t.Fatalf("write skill: %v", err) - } - if skillPath != filepath.Join(dir, "skills", "mnemon", "SKILL.md") { - t.Fatalf("skill path = %q", skillPath) - } - if _, err := os.Stat(skillPath); err != nil { - t.Fatalf("stat skill: %v", err) - } -} - -func TestTraeWriteHook(t *testing.T) { - dir := t.TempDir() - - hookPath, err := TraeWriteHook(dir, "prime.sh", []byte("#!/bin/bash\n")) - if err != nil { - t.Fatalf("write hook: %v", err) - } - if hookPath != filepath.Join(dir, "hooks", "mnemon", "prime.sh") { - t.Fatalf("hook path = %q", hookPath) - } - info, err := os.Stat(hookPath) - if err != nil { - t.Fatalf("stat hook: %v", err) - } - if info.Mode().Perm() != 0755 { - t.Fatalf("hook permissions = %v, want 0755", info.Mode().Perm()) - } -} - func TestTraeRegisterHooksPreservesUnrelatedConfig(t *testing.T) { dir := t.TempDir() hooksPath := filepath.Join(dir, "hooks.json") diff --git a/internal/setup/workbuddy_test.go b/internal/setup/workbuddy_test.go deleted file mode 100644 index 0484b5ef..00000000 --- a/internal/setup/workbuddy_test.go +++ /dev/null @@ -1,153 +0,0 @@ -package setup - -import ( - "os" - "path/filepath" - "strings" - "testing" -) - -func TestWorkBuddyWriteSkill(t *testing.T) { - dir := t.TempDir() - - skillPath, err := WorkBuddyWriteSkill(dir) - if err != nil { - t.Fatalf("write skill: %v", err) - } - if skillPath != filepath.Join(dir, "skills", "mnemon", "SKILL.md") { - t.Fatalf("skill path = %q", skillPath) - } - data, err := os.ReadFile(skillPath) - if err != nil { - t.Fatalf("read skill: %v", err) - } - if !strings.Contains(string(data), "WorkBuddy") { - t.Fatalf("workbuddy skill should mention WorkBuddy: %s", string(data)) - } -} - -func TestWorkBuddyWriteHook(t *testing.T) { - dir := t.TempDir() - - hookPath, err := WorkBuddyWriteHook(dir, "prime.sh", []byte("#!/bin/bash\n")) - if err != nil { - t.Fatalf("write hook: %v", err) - } - if hookPath != filepath.Join(dir, "hooks", "mnemon", "prime.sh") { - t.Fatalf("hook path = %q", hookPath) - } - info, err := os.Stat(hookPath) - if err != nil { - t.Fatalf("stat hook: %v", err) - } - if info.Mode().Perm() != 0755 { - t.Fatalf("hook permissions = %v, want 0755", info.Mode().Perm()) - } -} - -func TestWorkBuddyRegisterHooksPreservesUnrelatedConfig(t *testing.T) { - dir := t.TempDir() - settingsPath := filepath.Join(dir, "settings.json") - if err := os.WriteFile(settingsPath, []byte(`{ - "hooks": { - "SessionStart": [ - {"hooks": [{"type": "command", "command": "/old/mnemon/prime.sh"}]}, - {"hooks": [{"type": "command", "command": "/keep/custom.sh"}]} - ], - "Stop": [ - {"hooks": [{"type": "command", "command": "/old/mnemon/stop.sh"}]} - ] - }, - "other": true -}`), 0644); err != nil { - t.Fatalf("write settings: %v", err) - } - - if _, err := WorkBuddyRegisterHooks(dir); err != nil { - t.Fatalf("register hooks: %v", err) - } - - data, err := ReadJSONFile(settingsPath) - if err != nil { - t.Fatalf("read settings: %v", err) - } - if data["other"] != true { - t.Fatalf("unrelated setting should be preserved: %#v", data) - } - hooks := data["hooks"].(map[string]any) - sessionStart := hooks["SessionStart"].([]any) - if len(sessionStart) != 2 { - t.Fatalf("expected custom hook plus new prime hook: %#v", sessionStart) - } - if !strings.Contains(sessionStart[1].(map[string]any)["hooks"].([]any)[0].(map[string]any)["command"].(string), "hooks/mnemon/prime.sh") { - t.Fatalf("expected new prime hook, got %#v", sessionStart[1]) - } - if _, ok := hooks["UserPromptSubmit"]; !ok { - t.Fatalf("user prompt hook should be registered: %#v", hooks) - } - stop := hooks["Stop"].([]any) - if len(stop) != 1 { - t.Fatalf("expected one stop hook: %#v", stop) - } - if _, ok := stop[0].(map[string]any)["loop_limit"]; ok { - t.Fatalf("workbuddy hook schema should not include loop_limit: %#v", stop[0]) - } -} - -func TestWorkBuddyEjectRemovesOnlyMnemonFilesAndHooks(t *testing.T) { - dir := t.TempDir() - if _, err := WorkBuddyWriteSkill(dir); err != nil { - t.Fatalf("write skill: %v", err) - } - if _, err := WorkBuddyWriteHook(dir, "prime.sh", []byte("#!/bin/bash\n")); err != nil { - t.Fatalf("write hook: %v", err) - } - if _, err := WorkBuddyRegisterHooks(dir); err != nil { - t.Fatalf("register hooks: %v", err) - } - customSkillDir := filepath.Join(dir, "skills", "custom") - if err := os.MkdirAll(customSkillDir, 0755); err != nil { - t.Fatalf("create custom skill: %v", err) - } - settingsPath := filepath.Join(dir, "settings.json") - data, err := ReadJSONFile(settingsPath) - if err != nil { - t.Fatalf("read settings: %v", err) - } - hooks := data["hooks"].(map[string]any) - hooks["SessionStart"] = append(hooks["SessionStart"].([]any), map[string]any{ - "hooks": []any{map[string]any{"type": "command", "command": "/keep/custom.sh"}}, - }) - if err := WriteJSONFile(settingsPath, data); err != nil { - t.Fatalf("write settings: %v", err) - } - - errs := WorkBuddyEject(dir) - if len(errs) > 0 { - t.Fatalf("eject errors: %v", errs) - } - if _, err := os.Stat(filepath.Join(dir, "skills", "mnemon")); !os.IsNotExist(err) { - t.Fatalf("mnemon skill should be removed, err=%v", err) - } - if _, err := os.Stat(customSkillDir); err != nil { - t.Fatalf("custom skill should be preserved: %v", err) - } - if _, err := os.Stat(filepath.Join(dir, "hooks", "mnemon")); !os.IsNotExist(err) { - t.Fatalf("mnemon hooks should be removed, err=%v", err) - } - data, err = ReadJSONFile(settingsPath) - if err != nil { - t.Fatalf("read settings after eject: %v", err) - } - hooks = data["hooks"].(map[string]any) - sessionStart := hooks["SessionStart"].([]any) - if len(sessionStart) != 1 || containsMnemon(sessionStart[0]) { - t.Fatalf("custom hook should be preserved and mnemon removed: %#v", sessionStart) - } - if _, ok := hooks["UserPromptSubmit"]; ok { - t.Fatalf("user prompt hooks should be removed: %#v", hooks) - } - if _, ok := hooks["Stop"]; ok { - t.Fatalf("stop hooks should be removed: %#v", hooks) - } -} diff --git a/scripts/e2e_test.sh b/scripts/e2e_test.sh index 0b02892b..3206ad4d 100755 --- a/scripts/e2e_test.sh +++ b/scripts/e2e_test.sh @@ -109,19 +109,31 @@ extract_id() { # ── Setup ───────────────────────────────────────────────────────────── SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_DIR="$(dirname "$SCRIPT_DIR")" -TESTDATA="$PROJECT_DIR/.testdata" +TESTDATA=$(mktemp -d "${TMPDIR:-/tmp}/mnemon-e2e.XXXXXX") TESTDIR="$TESTDATA/m1" -M="$PROJECT_DIR/mnemon" +M="$TESTDATA/mnemon" + +cleanup() { + if [ "${E2E_KEEP:-0}" = 1 ]; then + echo -e " ${DIM}Test data preserved at: $TESTDATA/${RESET}" + return + fi + if [ -d "$TESTDATA" ] && [ ! -L "$TESTDATA" ]; then + case "$(basename "$TESTDATA")" in + mnemon-e2e.??????) rm -rf -- "$TESTDATA" ;; + *) echo "refusing to remove unexpected E2E directory: $TESTDATA" >&2 ;; + esac + fi +} +trap cleanup EXIT banner "Building mnemon" cd "$PROJECT_DIR" -go build -o mnemon . +go build -o "$M" . echo -e " ${GREEN}✔${RESET} Binary built: $M" -# Clean previous test data -rm -rf "$TESTDATA" mkdir -p "$TESTDIR" -echo -e " ${DIM} Test data: $TESTDATA/${RESET}" +echo -e " ${DIM}Test data: $TESTDATA/${RESET}" # ══════════════════════════════════════════════════════════════════════ banner "Milestone 0: Store Management & Data Isolation" @@ -833,11 +845,6 @@ if [ "$FAIL" -gt 0 ]; then fi echo "" -# Cleanup binary (keep .testdata for inspection) -rm -f "$M" -echo -e " ${DIM}Test DBs preserved at: $TESTDATA/${RESET}" -echo -e " ${DIM}Run 'rm -rf .testdata' to clean up${RESET}" - if [ "$FAIL" -gt 0 ]; then echo -e " ${RED}${BOLD}FAIL${RESET}" exit 1