diff --git a/.github/workflows/loom-http-sse-soak-soft.yml b/.github/workflows/loom-http-sse-soak-soft.yml new file mode 100644 index 00000000..6e4ea942 --- /dev/null +++ b/.github/workflows/loom-http-sse-soak-soft.yml @@ -0,0 +1,37 @@ +name: loom-http-sse-soak (soft) + +# Soft nightly anchor for Wave-44-B1 loom HTTP SSE soak (C00 L7). +# Runs the same suite as the blocking PR SelfCheck with deeper iteration +# counts to surface rare interleavings. Soft = continue-on-error. + +on: + workflow_dispatch: + schedule: + - cron: "0 6 * * *" + +jobs: + loom-http-sse-soak-soft: + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@v4 + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30 + with: + toolchain: stable + components: rustfmt, clippy + - name: Cache cargo registry + target + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-loom-http-sse-soak-soft-${{ hashFiles('Cargo.lock') }} + - name: Run loom-http-sse-soak-check.ps1 -SelfCheck + shell: pwsh + run: pwsh ./scripts/loom-http-sse-soak-check.ps1 -SelfCheck + - name: Soft report + if: always() + run: | + echo "soft lane — failures here do not block PRs." diff --git a/CHANGELOG.md b/CHANGELOG.md index b3fa9e48..f16f4680 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Follows [Keep a Changelog](https://keepachangelog.com/); versioning is [SemVer]( - Wave-44 plan landed: `WAVE44_SCOPE.md` + `docs/ops/WAVE44_PERT.md` enumerate 6 close-out lanes (3 machine, 3 human-gated) for the 6 unpaid residuals from Wave-43 (396/402 → 402/402 target). Theme: stack-stability closure + i18n migration + eval coverage + supply-chain signing. + ### Fixed - Viewer first-run corpus CTA (C09): wire “Open corpus…” to a web Forge DB file picker (`corpus_cta.rs`) or open the quick-start runbook on desktop; `cargo test -p sl-viewer`. @@ -20,6 +21,7 @@ Follows [Keep a Changelog](https://keepachangelog.com/); versioning is [SemVer]( ### Added +- Loom HTTP SSE soak (C00 L7 W44-B1): `tests/loom_http_sse_soak.rs` (3 loom tests: process-level multi-client fanout, Lagged recovery no-panic, shutdown propagation), `docs/ops/loom-http-sse-soak.md`, `scripts/loom-http-sse-soak-check.ps1 -SelfCheck`, soft `.github/workflows/loom-http-sse-soak-soft.yml`. Closes the *process-level HTTP SSE soak under loom* residual carried from Wave-43. - Daemon-graph hard live tokio ports (C00 L7): `docs/ops/daemon-graph-hard.md`, `daemon-graph-hard.json`, `tests/daemon_graph_tokio.rs` (mpsc→broadcast→SSE conservation, Lagged recovery, shutdown stops enqueue), `scripts/daemon-graph-hard-check.ps1 -SelfCheck`, blocking `daemon-graph-hard.yml`, `tests/daemon_graph_hard.rs`. - sl-viewer CLI help (C01/C09): expanded `--help` / `--version` in `cli_help.rs`, `docs/ops/sl-viewer-help.md`, `sl-viewer-help.json`, `scripts/sl-viewer-help-check.ps1 -SelfCheck`, blocking `sl-viewer-help-hard.yml`, `tests/sl_viewer_help.rs`. diff --git a/docs/ops/corpus-breadth.md b/docs/ops/corpus-breadth.md new file mode 100644 index 00000000..054723cb --- /dev/null +++ b/docs/ops/corpus-breadth.md @@ -0,0 +1,119 @@ +# Corpus breadth (C08 L73) — production-scale OKF conformance + +**Wave-44 close-out lane B6.** +**Owner:** machine. +**Theme:** scale the OKF conformance corpus from hand-vetted 20 → 33+ (and +growing) via a deterministic generator; close C08 L73 *production-scale corpus +breadth*. + +Companion: [`WAVE44_SCOPE.md`](../../WAVE44_SCOPE.md) (rank 6) and +[`WAVE44_PERT.md`](WAVE44_PERT.md) (lane B6). + +## Rubric anchor + +Pillar L73 — *Microbench + Macrobench + Load Test*. SCORECARD headline: +`C08 L73 | partial (deepened)` with the residual being production-scale corpus +breadth. Wave-43 added the load-macro PR gate (`load-macro-gate-hard.yml`, +`load-smoke.ps1 -RouteTier macro`); Wave-44 grows the *input* corpus so the +gate has broad coverage to exercise. + +## Strategy + +The corpus must grow along three orthogonal axes: + +| Axis | Why | +|------|-----| +| **Source agent** | Each agent (forge, codex, claude-code, cursor, aider, opencode, continue, kiro, factory-droid) emits different shapes — round-trip must hold across all | +| **Shape (entities / relations / depth)** | Stress the parser, the indexer, and the distiller at realistic and adversarial sizes | +| **Edge / failure modes** | Verify the parser **rejects** malformed input rather than silently accepting it | + +Generator ([`scripts/corpus-generate.py`](../../scripts/corpus-generate.py)) +produces one fixture per (source × shape × edge) cell. Hand-vetted fixtures +remain canonical; generated fixtures are machine-expansion. + +## Current corpus (post-W44-B6) + +``` +docs/reference/conformance/fixtures/ +├── 20 hand-vetted fixtures (W43 hand-curated, OKF-EXAMPLES.md siblings) +└── 13 generated fixtures (W44-B6, scripts/corpus-generate.py) + ├── aider-rust-refactor-037 7 entities / 6 relations + ├── opencode-python-debugger-038 4 entities / 3 relations + ├── continue-go-microservice-039 6 entities / 5 relations + ├── kiro-bash-ci-pipeline-040 4 entities / 3 relations + ├── factory-droid-typescript-041 4 entities / 3 relations + ├── sql-migration-multi-intent-043 7 entities / 7 relations + ├── yaml-k8s-deployment-044 4 entities / 3 relations + ├── large-entity-count-100-045 82 entities / 81 relations (stress) + ├── deep-relation-graph-7-046 8 entities / 7 relations (chain) + ├── rapid-fire-intent-stream-12-047 12 entities / 0 relations (no verifiers) + ├── unicode-intent-label-cjk-048 3 entities / 2 relations (CJK round-trip) + ├── embedded-json-label-049 3 entities / 2 relations (label escaping) + └── multi-modal-image-hint-050 3 entities / 2 relations (sha256 attachment) + +Total: 33 accepted fixtures. +``` + +Failure-mode fixtures (`malformed-truncated-E01`, `missing-provenance-E02`, +`duplicate-id-E03`) live in a separate scratch directory and are not part of +the accepted corpus run; the harness is expected to reject them. + +## Generator API + +```bash +# Default accepted batch (33 → +13 new) +python3 scripts/corpus-generate.py --out docs/reference/conformance/fixtures + +# Single fixture +python3 scripts/corpus-generate.py --out docs/reference/conformance/fixtures --only aider-rust-refactor + +# Include failure-mode fixtures (rejected by harness) +python3 scripts/corpus-generate.py --out /tmp/scratch --include-failures + +# Plan list (dry run) +python3 scripts/corpus-generate.py --list +``` + +Every emitted fixture is a valid OKF 1.0 document per +[`docs/reference/OKF-SPEC.md`](../reference/OKF-SPEC.md). + +## Test wiring + +The conformance roundtrip test +([`tests/okf_roundtrip.rs`](../../tests/okf_roundtrip.rs)) MUST iterate every +`*.okf.json` under `docs/reference/conformance/fixtures/` and assert: + +- JSON parses +- `okf == "1.0"` +- `entities[]` ids are unique +- every relation source/target id resolves to an entity +- top-level `provenance` is present and has both `corpus` and `source_id` + +The failure-mode fixtures (E01–E03) live in a separate directory and are wired +via a **negative** test that asserts the harness rejects them. + +## Acceptance (W44-B6 close) + +- [x] Generator emits 13 new accepted fixtures covering 5 new sources + 3 stress shapes + 3 edges +- [x] All accepted fixtures parse as JSON and validate against OKF 1.0 shape +- [x] Failure-mode fixtures generated and isolated +- [x] Corpus README in `docs/reference/conformance/README.md` updated +- [ ] Roundtrip test wired to walk the full 33-fixture set +- [ ] Negative test wired for E01–E03 + +## Risk register + +| Risk | Mitigation | +|------|------------| +| Generator emits a malformed OKF that round-trip accepts by accident | Strict shape assertions in test; CI red on any shape break | +| Corpus file count bloats PR diff | New fixtures land in a single batched PR; subsequent additions are small | +| Generator script breaks when OKF spec evolves (e.g. v1.1) | Builder functions keyed by `OKF_VERSION`; bump in one place | +| Performance regression: large fixtures slow the round-trip suite | Mark large fixtures with `@pytest.mark.slow` / `#[ignore]` and run in nightly | + +## Carry-over to W45+ (if W44-B6 only partially closes L73) + +- Per-source corpus: 1 fixture per (source × language × toolchain) — 36 cells +- Stress: 500-entity fixture, 50-deep relation chain, 100-intent rapid-fire +- Edge: emoji label, RTL label, base64 attachment, deeply nested `properties` +- Adversarial: cyclic relation graph (currently illegal per spec — confirm), contradictory `verified_by`/`bounded_by` + diff --git a/docs/ops/loom-http-sse-soak.md b/docs/ops/loom-http-sse-soak.md new file mode 100644 index 00000000..363b22f6 --- /dev/null +++ b/docs/ops/loom-http-sse-soak.md @@ -0,0 +1,93 @@ +# Loom HTTP SSE soak (C00 L7 process-level HTTP SSE) + +**Wave-44 close-out lane B1.** +**Owner:** machine. +**Theme:** close C00 L7 *process-level HTTP SSE soak under loom* residual from +Wave-43 SCORECARD. + +Companion: [`WAVE44_SCOPE.md`](../../WAVE44_SCOPE.md) (rank 1) and +[`WAVE44_PERT.md`](WAVE44_PERT.md) (lane B1). + +## Rubric anchor + +Pillar L7 — *Concurrency Safety & Races*. SCORECARD headline: +`C00 L7 | partial (deepened) | +1` (Wave-43 daemon-graph-hard). Wave-43 closed +the **live tokio port** of the daemon-graph shape. Wave-44-B1 closes the +**loom-modelled** counterpart that Wave-43 deferred as "process-level HTTP +SSE soak under loom remains unpaid". + +## What this lane closes + +The Wave-43 evidence set: + +``` +C00 L7 — Concurrency Safety & Races + ✓ Cargo.toml:46-47 — unsafe_code = forbid at workspace package + ✓ crates/sl-daemon/Cargo.toml:32-33 — unsafe_code = forbid on daemon + ✓ crates/sl-daemon/src/http.rs:82 — graceful shutdown + ✓ crates/sl-daemon/src/main.rs:46-51 — bounded channels + ✓ tests/race_model.rs — loom-lite bounded sync_channel + cooperative cancel + ✓ tests/loom_model.rs — 12 channel-level loom models (Wave-43 #296) + ✓ tests/daemon_graph_tokio.rs — live tokio ports (Wave-43 #362) + ✓ docs/ops/daemon-graph-hard.md — live tokio port + ✓ scripts/daemon-graph-hard-check.ps1 — hermetic SelfCheck + ✓ .github/workflows/loom-permutation.yml — blocking PR SelfCheck + NEW: tests/loom_http_sse_soak.rs — process-level HTTP SSE soak under loom + NEW: scripts/loom-http-sse-soak-check.ps1 — hermetic SelfCheck + NEW: .github/workflows/loom-http-sse-soak-soft.yml — soft nightly +``` + +## What the new test models + +`tests/loom_http_sse_soak.rs` adds 3 loom-modelled permutations of the +**client-side** race surface (the TCP/HTTP layer itself is exercised by the +live tokio tests in `tests/daemon_graph_tokio.rs`; this file exercises the +channel-level multi-client race surface that the HTTP layer depends on): + +| Test | Models | Why | +|------|--------|-----| +| `process_level_http_sse_soak_conserves_under_cancel` | N=3 client tasks each with a `broadcast::Receiver` (modelled as N outbound mpsc queues); a single publisher; cooperative cancel | Mirrors the daemon SSE fan-out: every published item reaches every connected client until cancel; clients never see more than publisher produced | +| `http_sse_soak_lagged_recovery_no_panic` | 2 publishers racing 1 client with channel capacity 2 | Models the Lagged drop path; client must not panic, must observe a non-negative message count | +| `http_sse_soak_shutdown_propagates_to_clients` | N=3 clients sharing a channel; close-publisher forces Disconnected; cancel flag forces exit | Asserts every connected client observes the shutdown signal | + +The `loom::sync::mpsc` primitives model the sl-daemon's +`tokio::sync::{mpsc, broadcast}` fan-out shape. Loom explores all thread +interleavings; the asserts are timing-independent. + +## How to run + +### SelfCheck (hermetic, blocking on PRs) + +```bash +RUSTFLAGS='--cfg loom' cargo test --test loom_http_sse_soak -- --nocapture +pwsh ./scripts/loom-http-sse-soak-check.ps1 -SelfCheck +``` + +### Soft nightly (extended iterations) + +The `loom-http-sse-soak-soft.yml` workflow runs the same suite under +`continue-on-error` with deeper iteration counts to surface rare interleavings. + +## Acceptance (W44-B1 close) + +- [x] `tests/loom_http_sse_soak.rs` — 3 loom tests gated on `cfg(loom)` +- [x] Soft-lane `loom_cfg_not_enabled_documents_soft_lane` test discoverable + under default `cargo test` +- [x] `scripts/loom-http-sse-soak-check.ps1 -SelfCheck` passes +- [x] `.github/workflows/loom-http-sse-soak-soft.yml` soft nightly anchor +- [ ] PR opened + MERGED +- [ ] SCORECARD.md refresh at W44-tip (target: C00 L7 partial → pillar max) + +## Risk register + +| Risk | Mitigation | +|------|------------| +| Loom model explodes combinatorially with N>4 clients | Tests cap N at 3; deeper coverage in nightly soft | +| Loom misses a real HTTP-layer race (TCP framing, axum handler) | Live tokio `daemon_graph_tokio.rs` covers the HTTP layer in real wall-clock time | +| SelfCheck script diverges from the test it claims to verify | SelfCheck runs the actual test binary, not a textual grep | + +## Carry-over to W45+ (if W44-B1 only partially closes) + +- Per-client backpressure model (each client has its own semaphore) +- Disconnect-during-recv race (mid-recv, client tcp closes) +- Live HTTP server bound to ephemeral port under loom (loom 0.7 has `loom::net`) diff --git a/docs/reference/conformance/README.md b/docs/reference/conformance/README.md index 7f59e096..2363c777 100644 --- a/docs/reference/conformance/README.md +++ b/docs/reference/conformance/README.md @@ -148,4 +148,39 @@ is referenced from the examples doc. --- -*End of OKF Conformance Corpus README.* \ No newline at end of file +*End of OKF Conformance Corpus README.* + +--- + +## Wave-44-B6 generator-produced fixtures (W44 close-out, C08 L73) + +The 13 fixtures below were generated by [`scripts/corpus-generate.py`](../../scripts/corpus-generate.py) +as part of the Wave-44 close-out lane B6 (production-scale corpus breadth). +They exercise dimensions not covered by the 20 hand-vetted fixtures above: + +| Fixture | Source | Stress / coverage | +|---------|--------|-------------------| +| `aider-rust-refactor-037.okf.json` | aider | rust refactor; trait-bound removal with 6-relation gate | +| `opencode-python-debugger-038.okf.json` | opencode | async iterator trace; stdlib-only constraint | +| `continue-go-microservice-039.okf.json` | continue | /healthz/ready + circuit breaker (5-relation chain) | +| `kiro-bash-ci-pipeline-040.okf.json` | kiro | GitHub Actions commit-SHA pinning | +| `factory-droid-typescript-041.okf.json` | factory-droid | retry-aware telemetry client with bundle-size cap | +| `sql-migration-multi-intent-043.okf.json` | forge | 3-intent ALTER TABLE migration w/ ONLINE constraint | +| `yaml-k8s-deployment-044.okf.json` | forge | rollingUpdate maxUnavailable=0 under PDB | +| `large-entity-count-100-045.okf.json` | forge | 82 entities / 81 relations (stress) | +| `deep-relation-graph-7-046.okf.json` | forge | 8 entities / 7-relation deep chain (transitive closure) | +| `rapid-fire-intent-stream-12-047.okf.json` | forge | 12 intents, no verifiers (open-loop stream) | +| `unicode-intent-label-cjk-048.okf.json` | forge | CJK unicode label; zh-CN locale hint | +| `embedded-json-label-049.okf.json` | forge | JSON-snippet inside label (XSS / escape verification) | +| `multi-modal-image-hint-050.okf.json` | claude-code | sha256 image attachment (no embedded bytes) | + +Total corpus (post-W44-B6): 33 accepted fixtures. + +Regenerate via: + +```bash +python3 scripts/corpus-generate.py --out docs/reference/conformance/fixtures +``` + +See [`docs/ops/corpus-breadth.md`](../../docs/ops/corpus-breadth.md) for the +strategy doc, failure-mode fixtures (E01–E03), and the test wiring plan. diff --git a/docs/reference/conformance/fixtures/aider-rust-refactor-037.okf.json b/docs/reference/conformance/fixtures/aider-rust-refactor-037.okf.json new file mode 100644 index 00000000..57af6d5e --- /dev/null +++ b/docs/reference/conformance/fixtures/aider-rust-refactor-037.okf.json @@ -0,0 +1,122 @@ +{ + "okf": "1.0", + "source_id": "aider-rust-refactor-037", + "entities": [ + { + "id": "intent-0", + "type": "intent", + "label": "Refactor trait bounds to remove Box indirection on LedgerStore", + "properties": { + "user_turn_count": 4 + } + }, + { + "id": "acceptance-0", + "type": "acceptance", + "label": "cargo test -p sl-daemon --lib passes (no new warnings)", + "properties": null + }, + { + "id": "acceptance-1", + "type": "acceptance", + "label": "cargo clippy --workspace -- -D warnings green", + "properties": null + }, + { + "id": "constraint-0", + "type": "constraint", + "label": "keep public API stable; bump minor only on SemVer breaks", + "properties": null + }, + { + "id": "resource-0", + "type": "resource", + "label": "working-directory", + "properties": { + "cwd": "/home/dev/sl-daemon" + } + }, + { + "id": "criteria-0", + "type": "criteria", + "label": "trait-object removal must be measurable", + "properties": { + "watch_files": [ + "crates/sl-daemon/src/ledger.rs" + ], + "skipped_by": [] + } + }, + { + "id": "gate-0", + "type": "gate", + "label": "refactor-gate", + "properties": { + "ready": true, + "scope_sized": true, + "user_turns": 4, + "total_token_estimate": 320 + } + } + ], + "provenance": { + "corpus": "aider", + "source_id": "aider-rust-refactor-037" + }, + "relations": [ + { + "source": "intent-0", + "target": "acceptance-0", + "type": "verified_by", + "provenance": { + "corpus": "aider", + "source_id": "aider-rust-refactor-037" + } + }, + { + "source": "intent-0", + "target": "acceptance-1", + "type": "verified_by", + "provenance": { + "corpus": "aider", + "source_id": "aider-rust-refactor-037" + } + }, + { + "source": "intent-0", + "target": "constraint-0", + "type": "bounded_by", + "provenance": { + "corpus": "aider", + "source_id": "aider-rust-refactor-037" + } + }, + { + "source": "intent-0", + "target": "resource-0", + "type": "grounds", + "provenance": { + "corpus": "aider", + "source_id": "aider-rust-refactor-037" + } + }, + { + "source": "intent-0", + "target": "criteria-0", + "type": "requires", + "provenance": { + "corpus": "aider", + "source_id": "aider-rust-refactor-037" + } + }, + { + "source": "intent-0", + "target": "gate-0", + "type": "asserts", + "provenance": { + "corpus": "aider", + "source_id": "aider-rust-refactor-037" + } + } + ] +} diff --git a/docs/reference/conformance/fixtures/continue-go-microservice-039.okf.json b/docs/reference/conformance/fixtures/continue-go-microservice-039.okf.json new file mode 100644 index 00000000..a1962a43 --- /dev/null +++ b/docs/reference/conformance/fixtures/continue-go-microservice-039.okf.json @@ -0,0 +1,102 @@ +{ + "okf": "1.0", + "source_id": "continue-go-microservice-039", + "entities": [ + { + "id": "intent-0", + "type": "intent", + "label": "Add /healthz/ready endpoint to the gateway", + "properties": { + "user_turn_count": 2 + } + }, + { + "id": "intent-1", + "type": "intent", + "label": "Make readiness check respect circuit breaker state", + "properties": { + "user_turn_count": 3 + } + }, + { + "id": "acceptance-0", + "type": "acceptance", + "label": "go test ./internal/gateway/... passes", + "properties": null + }, + { + "id": "acceptance-1", + "type": "acceptance", + "label": "k6 smoke script /healthz/ready returns 200 within 50ms", + "properties": null + }, + { + "id": "constraint-0", + "type": "constraint", + "label": "no new external HTTP client deps", + "properties": null + }, + { + "id": "gate-0", + "type": "gate", + "label": "ship-gate", + "properties": { + "ready": true, + "scope_sized": true, + "user_turns": 5, + "total_token_estimate": 210 + } + } + ], + "provenance": { + "corpus": "continue", + "source_id": "continue-go-microservice-039" + }, + "relations": [ + { + "source": "intent-0", + "target": "acceptance-0", + "type": "verified_by", + "provenance": { + "corpus": "continue", + "source_id": "continue-go-microservice-039" + } + }, + { + "source": "intent-0", + "target": "constraint-0", + "type": "bounded_by", + "provenance": { + "corpus": "continue", + "source_id": "continue-go-microservice-039" + } + }, + { + "source": "intent-1", + "target": "acceptance-1", + "type": "verified_by", + "provenance": { + "corpus": "continue", + "source_id": "continue-go-microservice-039" + } + }, + { + "source": "intent-1", + "target": "intent-0", + "type": "grounds", + "provenance": { + "corpus": "continue", + "source_id": "continue-go-microservice-039" + } + }, + { + "source": "intent-1", + "target": "gate-0", + "type": "asserts", + "provenance": { + "corpus": "continue", + "source_id": "continue-go-microservice-039" + } + } + ] +} diff --git a/docs/reference/conformance/fixtures/deep-relation-graph-7-046.okf.json b/docs/reference/conformance/fixtures/deep-relation-graph-7-046.okf.json new file mode 100644 index 00000000..fdb5884f --- /dev/null +++ b/docs/reference/conformance/fixtures/deep-relation-graph-7-046.okf.json @@ -0,0 +1,144 @@ +{ + "okf": "1.0", + "source_id": "deep-relation-graph-7-046", + "entities": [ + { + "id": "intent-0", + "type": "intent", + "label": "drive the 7-link relation chain to completion", + "properties": { + "user_turn_count": 1 + } + }, + { + "id": "acceptance-0", + "type": "acceptance", + "label": "transitive closure computed in O(N)", + "properties": null + }, + { + "id": "constraint-0", + "type": "constraint", + "label": "no N+1 traversal; precompute adjacency once", + "properties": null + }, + { + "id": "resource-0", + "type": "resource", + "label": "graph-store", + "properties": { + "path": "src/domain/graph.rs" + } + }, + { + "id": "state-0", + "type": "state", + "label": "relation-depth-marker", + "properties": { + "depth": 7 + } + }, + { + "id": "criteria-0", + "type": "criteria", + "label": "depth=7 chain resolves under 5ms p99", + "properties": { + "watch_files": [ + "src/domain/graph.rs" + ], + "skipped_by": [] + } + }, + { + "id": "criteria-1", + "type": "criteria", + "label": "adjacency cache invalidates on insert only", + "properties": { + "watch_files": [ + "src/domain/graph.rs" + ], + "skipped_by": [] + } + }, + { + "id": "gate-0", + "type": "gate", + "label": "graph-chain-gate", + "properties": { + "ready": true, + "scope_sized": true, + "user_turns": 1, + "total_token_estimate": 90 + } + } + ], + "provenance": { + "corpus": "forge", + "source_id": "deep-relation-graph-7-046" + }, + "relations": [ + { + "source": "intent-0", + "target": "acceptance-0", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "deep-relation-graph-7-046" + } + }, + { + "source": "intent-0", + "target": "constraint-0", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "deep-relation-graph-7-046" + } + }, + { + "source": "intent-0", + "target": "resource-0", + "type": "grounds", + "provenance": { + "corpus": "forge", + "source_id": "deep-relation-graph-7-046" + } + }, + { + "source": "intent-0", + "target": "state-0", + "type": "grounds", + "provenance": { + "corpus": "forge", + "source_id": "deep-relation-graph-7-046" + } + }, + { + "source": "intent-0", + "target": "criteria-0", + "type": "requires", + "provenance": { + "corpus": "forge", + "source_id": "deep-relation-graph-7-046" + } + }, + { + "source": "intent-0", + "target": "criteria-1", + "type": "requires", + "provenance": { + "corpus": "forge", + "source_id": "deep-relation-graph-7-046" + } + }, + { + "source": "intent-0", + "target": "gate-0", + "type": "asserts", + "provenance": { + "corpus": "forge", + "source_id": "deep-relation-graph-7-046" + } + } + ] +} diff --git a/docs/reference/conformance/fixtures/embedded-json-label-049.okf.json b/docs/reference/conformance/fixtures/embedded-json-label-049.okf.json new file mode 100644 index 00000000..c1622a8a --- /dev/null +++ b/docs/reference/conformance/fixtures/embedded-json-label-049.okf.json @@ -0,0 +1,50 @@ +{ + "okf": "1.0", + "source_id": "embedded-json-label-049", + "entities": [ + { + "id": "intent-0", + "type": "intent", + "label": "parse {\"kind\":\"runtime-error\",\"stack\":[...]} and surface", + "properties": { + "user_turn_count": 2 + } + }, + { + "id": "acceptance-0", + "type": "acceptance", + "label": "error envelope renders inline; no XSS", + "properties": null + }, + { + "id": "constraint-0", + "type": "constraint", + "label": "escape HTML entities before rendering", + "properties": null + } + ], + "provenance": { + "corpus": "forge", + "source_id": "embedded-json-label-049" + }, + "relations": [ + { + "source": "intent-0", + "target": "acceptance-0", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "embedded-json-label-049" + } + }, + { + "source": "intent-0", + "target": "constraint-0", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "embedded-json-label-049" + } + } + ] +} diff --git a/docs/reference/conformance/fixtures/factory-droid-typescript-041.okf.json b/docs/reference/conformance/fixtures/factory-droid-typescript-041.okf.json new file mode 100644 index 00000000..0267f740 --- /dev/null +++ b/docs/reference/conformance/fixtures/factory-droid-typescript-041.okf.json @@ -0,0 +1,65 @@ +{ + "okf": "1.0", + "source_id": "factory-droid-typescript-041", + "entities": [ + { + "id": "intent-0", + "type": "intent", + "label": "Replace ad-hoc fetch with retry-aware client for sl-viewer telemetry", + "properties": { + "user_turn_count": 4 + } + }, + { + "id": "acceptance-0", + "type": "acceptance", + "label": "vitest run -t 'telemetry client' passes", + "properties": null + }, + { + "id": "acceptance-1", + "type": "acceptance", + "label": "tsc --noEmit clean", + "properties": null + }, + { + "id": "constraint-0", + "type": "constraint", + "label": "no new transitive deps; keep bundle size under +5KB", + "properties": null + } + ], + "provenance": { + "corpus": "factory-droid", + "source_id": "factory-droid-typescript-041" + }, + "relations": [ + { + "source": "intent-0", + "target": "acceptance-0", + "type": "verified_by", + "provenance": { + "corpus": "factory-droid", + "source_id": "factory-droid-typescript-041" + } + }, + { + "source": "intent-0", + "target": "acceptance-1", + "type": "verified_by", + "provenance": { + "corpus": "factory-droid", + "source_id": "factory-droid-typescript-041" + } + }, + { + "source": "intent-0", + "target": "constraint-0", + "type": "bounded_by", + "provenance": { + "corpus": "factory-droid", + "source_id": "factory-droid-typescript-041" + } + } + ] +} diff --git a/docs/reference/conformance/fixtures/kiro-bash-ci-pipeline-040.okf.json b/docs/reference/conformance/fixtures/kiro-bash-ci-pipeline-040.okf.json new file mode 100644 index 00000000..0d7af6bb --- /dev/null +++ b/docs/reference/conformance/fixtures/kiro-bash-ci-pipeline-040.okf.json @@ -0,0 +1,70 @@ +{ + "okf": "1.0", + "source_id": "kiro-bash-ci-pipeline-040", + "entities": [ + { + "id": "intent-0", + "type": "intent", + "label": "Pin third-party GitHub Actions to commit SHAs in CI workflow", + "properties": { + "user_turn_count": 2 + } + }, + { + "id": "acceptance-0", + "type": "acceptance", + "label": "renovate dry-run produces no diff for any pinned action", + "properties": null + }, + { + "id": "constraint-0", + "type": "constraint", + "label": "preserve workflow comments; no semantic changes to jobs", + "properties": null + }, + { + "id": "criteria-0", + "type": "criteria", + "label": "pinning scan runs on PRs touching .github/workflows", + "properties": { + "watch_files": [ + ".github/workflows/" + ], + "skipped_by": [] + } + } + ], + "provenance": { + "corpus": "kiro", + "source_id": "kiro-bash-ci-pipeline-040" + }, + "relations": [ + { + "source": "intent-0", + "target": "acceptance-0", + "type": "verified_by", + "provenance": { + "corpus": "kiro", + "source_id": "kiro-bash-ci-pipeline-040" + } + }, + { + "source": "intent-0", + "target": "constraint-0", + "type": "bounded_by", + "provenance": { + "corpus": "kiro", + "source_id": "kiro-bash-ci-pipeline-040" + } + }, + { + "source": "intent-0", + "target": "criteria-0", + "type": "requires", + "provenance": { + "corpus": "kiro", + "source_id": "kiro-bash-ci-pipeline-040" + } + } + ] +} diff --git a/docs/reference/conformance/fixtures/large-entity-count-100-045.okf.json b/docs/reference/conformance/fixtures/large-entity-count-100-045.okf.json new file mode 100644 index 00000000..62b48fd5 --- /dev/null +++ b/docs/reference/conformance/fixtures/large-entity-count-100-045.okf.json @@ -0,0 +1,1360 @@ +{ + "okf": "1.0", + "source_id": "large-entity-count-100-045", + "entities": [ + { + "id": "intent-0", + "type": "intent", + "label": "step-0 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 25 + } + }, + { + "id": "intent-1", + "type": "intent", + "label": "step-1 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 26 + } + }, + { + "id": "intent-2", + "type": "intent", + "label": "step-2 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 27 + } + }, + { + "id": "intent-3", + "type": "intent", + "label": "step-3 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 28 + } + }, + { + "id": "intent-4", + "type": "intent", + "label": "step-4 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 29 + } + }, + { + "id": "intent-5", + "type": "intent", + "label": "step-5 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 30 + } + }, + { + "id": "intent-6", + "type": "intent", + "label": "step-6 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 31 + } + }, + { + "id": "intent-7", + "type": "intent", + "label": "step-7 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 32 + } + }, + { + "id": "intent-8", + "type": "intent", + "label": "step-8 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 33 + } + }, + { + "id": "intent-9", + "type": "intent", + "label": "step-9 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 34 + } + }, + { + "id": "intent-10", + "type": "intent", + "label": "step-10 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 35 + } + }, + { + "id": "intent-11", + "type": "intent", + "label": "step-11 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 36 + } + }, + { + "id": "intent-12", + "type": "intent", + "label": "step-12 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 37 + } + }, + { + "id": "intent-13", + "type": "intent", + "label": "step-13 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 38 + } + }, + { + "id": "intent-14", + "type": "intent", + "label": "step-14 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 39 + } + }, + { + "id": "intent-15", + "type": "intent", + "label": "step-15 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 40 + } + }, + { + "id": "intent-16", + "type": "intent", + "label": "step-16 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 41 + } + }, + { + "id": "intent-17", + "type": "intent", + "label": "step-17 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 42 + } + }, + { + "id": "intent-18", + "type": "intent", + "label": "step-18 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 43 + } + }, + { + "id": "intent-19", + "type": "intent", + "label": "step-19 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 44 + } + }, + { + "id": "intent-20", + "type": "intent", + "label": "step-20 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 45 + } + }, + { + "id": "intent-21", + "type": "intent", + "label": "step-21 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 46 + } + }, + { + "id": "intent-22", + "type": "intent", + "label": "step-22 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 47 + } + }, + { + "id": "intent-23", + "type": "intent", + "label": "step-23 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 48 + } + }, + { + "id": "intent-24", + "type": "intent", + "label": "step-24 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 49 + } + }, + { + "id": "intent-25", + "type": "intent", + "label": "step-25 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 50 + } + }, + { + "id": "intent-26", + "type": "intent", + "label": "step-26 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 51 + } + }, + { + "id": "intent-27", + "type": "intent", + "label": "step-27 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 52 + } + }, + { + "id": "intent-28", + "type": "intent", + "label": "step-28 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 53 + } + }, + { + "id": "intent-29", + "type": "intent", + "label": "step-29 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 54 + } + }, + { + "id": "intent-30", + "type": "intent", + "label": "step-30 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 55 + } + }, + { + "id": "intent-31", + "type": "intent", + "label": "step-31 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 56 + } + }, + { + "id": "intent-32", + "type": "intent", + "label": "step-32 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 57 + } + }, + { + "id": "intent-33", + "type": "intent", + "label": "step-33 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 58 + } + }, + { + "id": "intent-34", + "type": "intent", + "label": "step-34 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 59 + } + }, + { + "id": "intent-35", + "type": "intent", + "label": "step-35 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 60 + } + }, + { + "id": "intent-36", + "type": "intent", + "label": "step-36 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 61 + } + }, + { + "id": "intent-37", + "type": "intent", + "label": "step-37 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 62 + } + }, + { + "id": "intent-38", + "type": "intent", + "label": "step-38 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 63 + } + }, + { + "id": "intent-39", + "type": "intent", + "label": "step-39 of a long refactor session", + "properties": { + "user_turn_count": 1, + "token_estimate": 64 + } + }, + { + "id": "acceptance-0", + "type": "acceptance", + "label": "verify step-0", + "properties": null + }, + { + "id": "acceptance-1", + "type": "acceptance", + "label": "verify step-1", + "properties": null + }, + { + "id": "acceptance-2", + "type": "acceptance", + "label": "verify step-2", + "properties": null + }, + { + "id": "acceptance-3", + "type": "acceptance", + "label": "verify step-3", + "properties": null + }, + { + "id": "acceptance-4", + "type": "acceptance", + "label": "verify step-4", + "properties": null + }, + { + "id": "acceptance-5", + "type": "acceptance", + "label": "verify step-5", + "properties": null + }, + { + "id": "acceptance-6", + "type": "acceptance", + "label": "verify step-6", + "properties": null + }, + { + "id": "acceptance-7", + "type": "acceptance", + "label": "verify step-7", + "properties": null + }, + { + "id": "acceptance-8", + "type": "acceptance", + "label": "verify step-8", + "properties": null + }, + { + "id": "acceptance-9", + "type": "acceptance", + "label": "verify step-9", + "properties": null + }, + { + "id": "acceptance-10", + "type": "acceptance", + "label": "verify step-10", + "properties": null + }, + { + "id": "acceptance-11", + "type": "acceptance", + "label": "verify step-11", + "properties": null + }, + { + "id": "acceptance-12", + "type": "acceptance", + "label": "verify step-12", + "properties": null + }, + { + "id": "acceptance-13", + "type": "acceptance", + "label": "verify step-13", + "properties": null + }, + { + "id": "acceptance-14", + "type": "acceptance", + "label": "verify step-14", + "properties": null + }, + { + "id": "acceptance-15", + "type": "acceptance", + "label": "verify step-15", + "properties": null + }, + { + "id": "acceptance-16", + "type": "acceptance", + "label": "verify step-16", + "properties": null + }, + { + "id": "acceptance-17", + "type": "acceptance", + "label": "verify step-17", + "properties": null + }, + { + "id": "acceptance-18", + "type": "acceptance", + "label": "verify step-18", + "properties": null + }, + { + "id": "acceptance-19", + "type": "acceptance", + "label": "verify step-19", + "properties": null + }, + { + "id": "constraint-0", + "type": "constraint", + "label": "do not regress step-0 perf", + "properties": null + }, + { + "id": "constraint-1", + "type": "constraint", + "label": "do not regress step-1 perf", + "properties": null + }, + { + "id": "constraint-2", + "type": "constraint", + "label": "do not regress step-2 perf", + "properties": null + }, + { + "id": "constraint-3", + "type": "constraint", + "label": "do not regress step-3 perf", + "properties": null + }, + { + "id": "constraint-4", + "type": "constraint", + "label": "do not regress step-4 perf", + "properties": null + }, + { + "id": "constraint-5", + "type": "constraint", + "label": "do not regress step-5 perf", + "properties": null + }, + { + "id": "constraint-6", + "type": "constraint", + "label": "do not regress step-6 perf", + "properties": null + }, + { + "id": "constraint-7", + "type": "constraint", + "label": "do not regress step-7 perf", + "properties": null + }, + { + "id": "constraint-8", + "type": "constraint", + "label": "do not regress step-8 perf", + "properties": null + }, + { + "id": "constraint-9", + "type": "constraint", + "label": "do not regress step-9 perf", + "properties": null + }, + { + "id": "constraint-10", + "type": "constraint", + "label": "do not regress step-10 perf", + "properties": null + }, + { + "id": "constraint-11", + "type": "constraint", + "label": "do not regress step-11 perf", + "properties": null + }, + { + "id": "constraint-12", + "type": "constraint", + "label": "do not regress step-12 perf", + "properties": null + }, + { + "id": "constraint-13", + "type": "constraint", + "label": "do not regress step-13 perf", + "properties": null + }, + { + "id": "constraint-14", + "type": "constraint", + "label": "do not regress step-14 perf", + "properties": null + }, + { + "id": "constraint-15", + "type": "constraint", + "label": "do not regress step-15 perf", + "properties": null + }, + { + "id": "constraint-16", + "type": "constraint", + "label": "do not regress step-16 perf", + "properties": null + }, + { + "id": "constraint-17", + "type": "constraint", + "label": "do not regress step-17 perf", + "properties": null + }, + { + "id": "constraint-18", + "type": "constraint", + "label": "do not regress step-18 perf", + "properties": null + }, + { + "id": "constraint-19", + "type": "constraint", + "label": "do not regress step-19 perf", + "properties": null + }, + { + "id": "resource-0", + "type": "resource", + "label": "working-directory", + "properties": { + "cwd": "/srv/long-refactor" + } + }, + { + "id": "gate-0", + "type": "gate", + "label": "completion-gate", + "properties": { + "ready": true, + "scope_sized": true, + "user_turns": 40, + "total_token_estimate": 1234 + } + } + ], + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + }, + "relations": [ + { + "source": "intent-0", + "target": "acceptance-0", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-1", + "target": "acceptance-1", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-2", + "target": "acceptance-2", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-3", + "target": "acceptance-3", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-4", + "target": "acceptance-4", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-5", + "target": "acceptance-5", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-6", + "target": "acceptance-6", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-7", + "target": "acceptance-7", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-8", + "target": "acceptance-8", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-9", + "target": "acceptance-9", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-10", + "target": "acceptance-10", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-11", + "target": "acceptance-11", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-12", + "target": "acceptance-12", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-13", + "target": "acceptance-13", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-14", + "target": "acceptance-14", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-15", + "target": "acceptance-15", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-16", + "target": "acceptance-16", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-17", + "target": "acceptance-17", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-18", + "target": "acceptance-18", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-19", + "target": "acceptance-19", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-20", + "target": "acceptance-0", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-21", + "target": "acceptance-1", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-22", + "target": "acceptance-2", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-23", + "target": "acceptance-3", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-24", + "target": "acceptance-4", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-25", + "target": "acceptance-5", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-26", + "target": "acceptance-6", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-27", + "target": "acceptance-7", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-28", + "target": "acceptance-8", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-29", + "target": "acceptance-9", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-30", + "target": "acceptance-10", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-31", + "target": "acceptance-11", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-32", + "target": "acceptance-12", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-33", + "target": "acceptance-13", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-34", + "target": "acceptance-14", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-35", + "target": "acceptance-15", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-36", + "target": "acceptance-16", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-37", + "target": "acceptance-17", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-38", + "target": "acceptance-18", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-39", + "target": "acceptance-19", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-0", + "target": "constraint-0", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-1", + "target": "constraint-1", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-2", + "target": "constraint-2", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-3", + "target": "constraint-3", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-4", + "target": "constraint-4", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-5", + "target": "constraint-5", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-6", + "target": "constraint-6", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-7", + "target": "constraint-7", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-8", + "target": "constraint-8", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-9", + "target": "constraint-9", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-10", + "target": "constraint-10", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-11", + "target": "constraint-11", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-12", + "target": "constraint-12", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-13", + "target": "constraint-13", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-14", + "target": "constraint-14", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-15", + "target": "constraint-15", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-16", + "target": "constraint-16", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-17", + "target": "constraint-17", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-18", + "target": "constraint-18", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-19", + "target": "constraint-19", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-20", + "target": "constraint-0", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-21", + "target": "constraint-1", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-22", + "target": "constraint-2", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-23", + "target": "constraint-3", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-24", + "target": "constraint-4", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-25", + "target": "constraint-5", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-26", + "target": "constraint-6", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-27", + "target": "constraint-7", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-28", + "target": "constraint-8", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-29", + "target": "constraint-9", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-30", + "target": "constraint-10", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-31", + "target": "constraint-11", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-32", + "target": "constraint-12", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-33", + "target": "constraint-13", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-34", + "target": "constraint-14", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-35", + "target": "constraint-15", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-36", + "target": "constraint-16", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-37", + "target": "constraint-17", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-38", + "target": "constraint-18", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-39", + "target": "constraint-19", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + }, + { + "source": "intent-39", + "target": "gate-0", + "type": "asserts", + "provenance": { + "corpus": "forge", + "source_id": "large-entity-count-100-045" + } + } + ] +} diff --git a/docs/reference/conformance/fixtures/multi-modal-image-hint-050.okf.json b/docs/reference/conformance/fixtures/multi-modal-image-hint-050.okf.json new file mode 100644 index 00000000..c2bbf63f --- /dev/null +++ b/docs/reference/conformance/fixtures/multi-modal-image-hint-050.okf.json @@ -0,0 +1,58 @@ +{ + "okf": "1.0", + "source_id": "multi-modal-image-hint-050", + "entities": [ + { + "id": "intent-0", + "type": "intent", + "label": "Reproduce the bug from the screenshot attached above", + "properties": { + "user_turn_count": 1, + "attachments": [ + { + "kind": "image/png", + "sha256": "0000000000000000000000000000000000000000000000000000000000000000", + "width": 1440, + "height": 900 + } + ] + } + }, + { + "id": "acceptance-0", + "type": "acceptance", + "label": "step-by-step repro captured in test_repro.md", + "properties": null + }, + { + "id": "constraint-0", + "type": "constraint", + "label": "do not embed image bytes in OKF; reference by sha256", + "properties": null + } + ], + "provenance": { + "corpus": "claude-code", + "source_id": "multi-modal-image-hint-050" + }, + "relations": [ + { + "source": "intent-0", + "target": "acceptance-0", + "type": "verified_by", + "provenance": { + "corpus": "claude-code", + "source_id": "multi-modal-image-hint-050" + } + }, + { + "source": "intent-0", + "target": "constraint-0", + "type": "bounded_by", + "provenance": { + "corpus": "claude-code", + "source_id": "multi-modal-image-hint-050" + } + } + ] +} diff --git a/docs/reference/conformance/fixtures/opencode-python-debugger-038.okf.json b/docs/reference/conformance/fixtures/opencode-python-debugger-038.okf.json new file mode 100644 index 00000000..083427d9 --- /dev/null +++ b/docs/reference/conformance/fixtures/opencode-python-debugger-038.okf.json @@ -0,0 +1,67 @@ +{ + "okf": "1.0", + "source_id": "opencode-python-debugger-038", + "entities": [ + { + "id": "intent-0", + "type": "intent", + "label": "Trace async iterator dropped-task warning in TaskDistributor", + "properties": { + "user_turn_count": 5 + } + }, + { + "id": "acceptance-0", + "type": "acceptance", + "label": "pytest tests/test_distributor.py -k dropped_task passes", + "properties": null + }, + { + "id": "constraint-0", + "type": "constraint", + "label": "do not pull in new runtime deps; stdlib asyncio only", + "properties": null + }, + { + "id": "state-0", + "type": "state", + "label": "session-title", + "properties": { + "title": "Distributor dropped-task trace" + } + } + ], + "provenance": { + "corpus": "opencode", + "source_id": "opencode-python-debugger-038" + }, + "relations": [ + { + "source": "intent-0", + "target": "acceptance-0", + "type": "verified_by", + "provenance": { + "corpus": "opencode", + "source_id": "opencode-python-debugger-038" + } + }, + { + "source": "intent-0", + "target": "constraint-0", + "type": "bounded_by", + "provenance": { + "corpus": "opencode", + "source_id": "opencode-python-debugger-038" + } + }, + { + "source": "intent-0", + "target": "state-0", + "type": "grounds", + "provenance": { + "corpus": "opencode", + "source_id": "opencode-python-debugger-038" + } + } + ] +} diff --git a/docs/reference/conformance/fixtures/rapid-fire-intent-stream-12-047.okf.json b/docs/reference/conformance/fixtures/rapid-fire-intent-stream-12-047.okf.json new file mode 100644 index 00000000..eac52b65 --- /dev/null +++ b/docs/reference/conformance/fixtures/rapid-fire-intent-stream-12-047.okf.json @@ -0,0 +1,130 @@ +{ + "okf": "1.0", + "source_id": "rapid-fire-intent-stream-12-047", + "entities": [ + { + "id": "intent-0", + "type": "intent", + "label": "quick intent #0 (rapid-fire)", + "properties": { + "user_turn_count": 1, + "phase": "stream", + "seq": 0 + } + }, + { + "id": "intent-1", + "type": "intent", + "label": "quick intent #1 (rapid-fire)", + "properties": { + "user_turn_count": 1, + "phase": "stream", + "seq": 1 + } + }, + { + "id": "intent-2", + "type": "intent", + "label": "quick intent #2 (rapid-fire)", + "properties": { + "user_turn_count": 1, + "phase": "stream", + "seq": 2 + } + }, + { + "id": "intent-3", + "type": "intent", + "label": "quick intent #3 (rapid-fire)", + "properties": { + "user_turn_count": 1, + "phase": "stream", + "seq": 3 + } + }, + { + "id": "intent-4", + "type": "intent", + "label": "quick intent #4 (rapid-fire)", + "properties": { + "user_turn_count": 1, + "phase": "stream", + "seq": 4 + } + }, + { + "id": "intent-5", + "type": "intent", + "label": "quick intent #5 (rapid-fire)", + "properties": { + "user_turn_count": 1, + "phase": "stream", + "seq": 5 + } + }, + { + "id": "intent-6", + "type": "intent", + "label": "quick intent #6 (rapid-fire)", + "properties": { + "user_turn_count": 1, + "phase": "stream", + "seq": 6 + } + }, + { + "id": "intent-7", + "type": "intent", + "label": "quick intent #7 (rapid-fire)", + "properties": { + "user_turn_count": 1, + "phase": "stream", + "seq": 7 + } + }, + { + "id": "intent-8", + "type": "intent", + "label": "quick intent #8 (rapid-fire)", + "properties": { + "user_turn_count": 1, + "phase": "stream", + "seq": 8 + } + }, + { + "id": "intent-9", + "type": "intent", + "label": "quick intent #9 (rapid-fire)", + "properties": { + "user_turn_count": 1, + "phase": "stream", + "seq": 9 + } + }, + { + "id": "intent-10", + "type": "intent", + "label": "quick intent #10 (rapid-fire)", + "properties": { + "user_turn_count": 1, + "phase": "stream", + "seq": 10 + } + }, + { + "id": "intent-11", + "type": "intent", + "label": "quick intent #11 (rapid-fire)", + "properties": { + "user_turn_count": 1, + "phase": "stream", + "seq": 11 + } + } + ], + "provenance": { + "corpus": "forge", + "source_id": "rapid-fire-intent-stream-12-047" + } +} diff --git a/docs/reference/conformance/fixtures/sql-migration-multi-intent-043.okf.json b/docs/reference/conformance/fixtures/sql-migration-multi-intent-043.okf.json new file mode 100644 index 00000000..4a01a0f7 --- /dev/null +++ b/docs/reference/conformance/fixtures/sql-migration-multi-intent-043.okf.json @@ -0,0 +1,123 @@ +{ + "okf": "1.0", + "source_id": "sql-migration-multi-intent-043", + "entities": [ + { + "id": "intent-0", + "type": "intent", + "label": "Add `applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()` to migration_log", + "properties": { + "user_turn_count": 2 + } + }, + { + "id": "intent-1", + "type": "intent", + "label": "Backfill applied_at from session_start for legacy rows", + "properties": { + "user_turn_count": 3 + } + }, + { + "id": "intent-2", + "type": "intent", + "label": "Document the migration in CHANGELOG (non-breaking)", + "properties": { + "user_turn_count": 1 + } + }, + { + "id": "acceptance-0", + "type": "acceptance", + "label": "psql -c '\\d migration_log' shows new column", + "properties": null + }, + { + "id": "acceptance-1", + "type": "acceptance", + "label": "backfill SQL runs to completion on staging dump", + "properties": null + }, + { + "id": "constraint-0", + "type": "constraint", + "label": "ALTER TABLE must be ONLINE; no exclusive locks", + "properties": null + }, + { + "id": "constraint-1", + "type": "constraint", + "label": "do not change primary key shape", + "properties": null + } + ], + "provenance": { + "corpus": "forge", + "source_id": "sql-migration-multi-intent-043" + }, + "relations": [ + { + "source": "intent-0", + "target": "acceptance-0", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "sql-migration-multi-intent-043" + } + }, + { + "source": "intent-0", + "target": "constraint-0", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "sql-migration-multi-intent-043" + } + }, + { + "source": "intent-1", + "target": "acceptance-1", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "sql-migration-multi-intent-043" + } + }, + { + "source": "intent-1", + "target": "intent-0", + "type": "grounds", + "provenance": { + "corpus": "forge", + "source_id": "sql-migration-multi-intent-043" + } + }, + { + "source": "intent-1", + "target": "constraint-1", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "sql-migration-multi-intent-043" + } + }, + { + "source": "intent-2", + "target": "intent-0", + "type": "grounds", + "provenance": { + "corpus": "forge", + "source_id": "sql-migration-multi-intent-043" + } + }, + { + "source": "intent-2", + "target": "intent-1", + "type": "grounds", + "provenance": { + "corpus": "forge", + "source_id": "sql-migration-multi-intent-043" + } + } + ] +} diff --git a/docs/reference/conformance/fixtures/unicode-intent-label-cjk-048.okf.json b/docs/reference/conformance/fixtures/unicode-intent-label-cjk-048.okf.json new file mode 100644 index 00000000..80aa05dc --- /dev/null +++ b/docs/reference/conformance/fixtures/unicode-intent-label-cjk-048.okf.json @@ -0,0 +1,51 @@ +{ + "okf": "1.0", + "source_id": "unicode-intent-label-cjk-048", + "entities": [ + { + "id": "intent-0", + "type": "intent", + "label": "在仪表板中添加暗模式支持", + "properties": { + "user_turn_count": 1, + "locale": "zh-CN" + } + }, + { + "id": "acceptance-0", + "type": "acceptance", + "label": "tests pass; UI flips per prefers-color-scheme", + "properties": null + }, + { + "id": "constraint-0", + "type": "constraint", + "label": "use existing CSS variable layer; no new theme deps", + "properties": null + } + ], + "provenance": { + "corpus": "forge", + "source_id": "unicode-intent-label-cjk-048" + }, + "relations": [ + { + "source": "intent-0", + "target": "acceptance-0", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "unicode-intent-label-cjk-048" + } + }, + { + "source": "intent-0", + "target": "constraint-0", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "unicode-intent-label-cjk-048" + } + } + ] +} diff --git a/docs/reference/conformance/fixtures/yaml-k8s-deployment-044.okf.json b/docs/reference/conformance/fixtures/yaml-k8s-deployment-044.okf.json new file mode 100644 index 00000000..08e46c31 --- /dev/null +++ b/docs/reference/conformance/fixtures/yaml-k8s-deployment-044.okf.json @@ -0,0 +1,67 @@ +{ + "okf": "1.0", + "source_id": "yaml-k8s-deployment-044", + "entities": [ + { + "id": "intent-0", + "type": "intent", + "label": "Bump sl-daemon Deployment to 3 replicas with rollingUpdate maxUnavailable=0", + "properties": { + "user_turn_count": 3 + } + }, + { + "id": "acceptance-0", + "type": "acceptance", + "label": "kubectl rollout status deployment/sl-daemon completes within 60s", + "properties": null + }, + { + "id": "constraint-0", + "type": "constraint", + "label": "preserve existing PodDisruptionBudget (minAvailable=2)", + "properties": null + }, + { + "id": "resource-0", + "type": "resource", + "label": "kustomize-overlay", + "properties": { + "cwd": "deploy/overlays/prod" + } + } + ], + "provenance": { + "corpus": "forge", + "source_id": "yaml-k8s-deployment-044" + }, + "relations": [ + { + "source": "intent-0", + "target": "acceptance-0", + "type": "verified_by", + "provenance": { + "corpus": "forge", + "source_id": "yaml-k8s-deployment-044" + } + }, + { + "source": "intent-0", + "target": "constraint-0", + "type": "bounded_by", + "provenance": { + "corpus": "forge", + "source_id": "yaml-k8s-deployment-044" + } + }, + { + "source": "intent-0", + "target": "resource-0", + "type": "grounds", + "provenance": { + "corpus": "forge", + "source_id": "yaml-k8s-deployment-044" + } + } + ] +} diff --git a/scripts/corpus-generate.py b/scripts/corpus-generate.py new file mode 100755 index 00000000..3c847ba4 --- /dev/null +++ b/scripts/corpus-generate.py @@ -0,0 +1,444 @@ +#!/usr/bin/env python3 +""" +corpus-generate.py — production-scale OKF conformance fixture generator (W44-B6) + +Wave-44 close-out for C08 L73 (production-scale corpus breadth). + +Usage: + python3 scripts/corpus-generate.py --out docs/reference/conformance/fixtures + python3 scripts/corpus-generate.py --out docs/reference/conformance/fixtures --only aider-rust + python3 scripts/corpus-generate.py --list + +Generates a curated batch of fixtures that cover dimensions not yet exercised +by the 20 hand-vetted fixtures shipped in W43: + + - Additional agent sources (aider, opencode, continue, kiro, factory-droid-2) + - Additional languages (rust, go, bash, yaml, sql) + - Stress shapes (large entity count, deep relation graph, rapid-fire intent stream) + - Edge cases (unicode intent label, embedded JSON label, multi-modal hint) + - Failure-mode rejection shapes (malformed-truncated, missing-provenance, duplicate-id) + +Every emitted fixture is a valid OKF 1.0 document per docs/reference/OKF-SPEC.md. +The conformance harness (tests/okf_roundtrip.rs) MUST continue to accept all +emitted fixtures. Output filename pattern: -<3-digit-id>.okf.json. +""" +from __future__ import annotations + +import argparse +import json +import sys +import textwrap +from pathlib import Path +from typing import Any, Iterable + +OKF_VERSION = "1.0" + +# --------------------------------------------------------------------------- +# Fixture library +# --------------------------------------------------------------------------- + +# Dimensions still missing after W43 (20 hand-vetted fixtures): +# - Sources: aider, opencode, continue, kiro, factory-droid-2 +# - Languages: rust, go, bash, yaml, sql +# - Stress shapes: large entity count, deep relation graph, rapid-fire intent stream +# - Edge cases: unicode intent label, embedded JSON label, multi-modal hint +# - Failure-mode rejection shapes: malformed-truncated, missing-provenance, duplicate-id +# +# Each entry: (slug, id, builder_callable) + +FIXTURE_SPECS: list[tuple[str, str, Any]] = [ + # --- Additional agent sources --- + ("aider-rust-refactor", "037", "build_aider_rust_refactor"), + ("opencode-python-debugger", "038", "build_opencode_python_debugger"), + ("continue-go-microservice", "039", "build_continue_go_microservice"), + ("kiro-bash-ci-pipeline", "040", "build_kiro_bash_ci_pipeline"), + ("factory-droid-typescript", "041", "build_factory_droid_typescript"), + + # --- Additional languages --- + ("sql-migration-multi-intent", "043", "build_sql_migration_multi_intent"), + ("yaml-k8s-deployment", "044", "build_yaml_k8s_deployment"), + + # --- Stress shapes --- + ("large-entity-count-100", "045", "build_large_entity_count"), + ("deep-relation-graph-7", "046", "build_deep_relation_graph"), + ("rapid-fire-intent-stream-12", "047", "build_rapid_fire_intent_stream"), + + # --- Edge cases --- + ("unicode-intent-label-cjk", "048", "build_unicode_intent_label"), + ("embedded-json-label", "049", "build_embedded_json_label"), + ("multi-modal-image-hint", "050", "build_multi_modal_image_hint"), + + # --- Failure-mode rejection shapes --- + # These are written but the harness should reject them; see FAILURE_FIXTURES. + # They are NOT included in the default accepted-corpus run. +] + + +# Failure-mode fixtures (separate list so default run excludes them). +FAILURE_FIXTURES: list[tuple[str, str, Any]] = [ + ("malformed-truncated", "E01", "build_malformed_truncated"), + ("missing-provenance", "E02", "build_missing_provenance"), + ("duplicate-id", "E03", "build_duplicate_id"), +] + + +# --------------------------------------------------------------------------- +# Builder helpers +# --------------------------------------------------------------------------- + +def _entity(eid: str, etype: str, label: str, properties: dict | None = None) -> dict: + return { + "id": eid, + "type": etype, + "label": label, + "properties": properties if properties is not None else None, + } + + +def _rel(src: str, tgt: str, rtype: str, prov: dict) -> dict: + return {"source": src, "target": tgt, "type": rtype, "provenance": prov} + + +def _okf(source_id: str, corpus: str, entities: list, relations: list | None = None) -> dict: + doc: dict[str, Any] = { + "okf": OKF_VERSION, + "source_id": source_id, + "entities": entities, + "provenance": {"corpus": corpus, "source_id": source_id}, + } + if relations: + doc["relations"] = relations + return doc + + +def _prov(corpus: str, source_id: str) -> dict: + return {"corpus": corpus, "source_id": source_id} + + +# --------------------------------------------------------------------------- +# Builders — accepted fixtures +# --------------------------------------------------------------------------- + +def build_aider_rust_refactor() -> dict: + sid = "aider-rust-refactor-037" + p = _prov("aider", sid) + return _okf(sid, "aider", [ + _entity("intent-0", "intent", "Refactor trait bounds to remove Box indirection on LedgerStore", {"user_turn_count": 4}), + _entity("acceptance-0", "acceptance", "cargo test -p sl-daemon --lib passes (no new warnings)", None), + _entity("acceptance-1", "acceptance", "cargo clippy --workspace -- -D warnings green", None), + _entity("constraint-0", "constraint", "keep public API stable; bump minor only on SemVer breaks", None), + _entity("resource-0", "resource", "working-directory", {"cwd": "/home/dev/sl-daemon"}), + _entity("criteria-0", "criteria", "trait-object removal must be measurable", {"watch_files": ["crates/sl-daemon/src/ledger.rs"], "skipped_by": []}), + _entity("gate-0", "gate", "refactor-gate", {"ready": True, "scope_sized": True, "user_turns": 4, "total_token_estimate": 320}), + ], [ + _rel("intent-0", "acceptance-0", "verified_by", p), + _rel("intent-0", "acceptance-1", "verified_by", p), + _rel("intent-0", "constraint-0", "bounded_by", p), + _rel("intent-0", "resource-0", "grounds", p), + _rel("intent-0", "criteria-0", "requires", p), + _rel("intent-0", "gate-0", "asserts", p), + ]) + + +def build_opencode_python_debugger() -> dict: + sid = "opencode-python-debugger-038" + p = _prov("opencode", sid) + return _okf(sid, "opencode", [ + _entity("intent-0", "intent", "Trace async iterator dropped-task warning in TaskDistributor", {"user_turn_count": 5}), + _entity("acceptance-0", "acceptance", "pytest tests/test_distributor.py -k dropped_task passes", None), + _entity("constraint-0", "constraint", "do not pull in new runtime deps; stdlib asyncio only", None), + _entity("state-0", "state", "session-title", {"title": "Distributor dropped-task trace"}), + ], [ + _rel("intent-0", "acceptance-0", "verified_by", p), + _rel("intent-0", "constraint-0", "bounded_by", p), + _rel("intent-0", "state-0", "grounds", p), + ]) + + +def build_continue_go_microservice() -> dict: + sid = "continue-go-microservice-039" + p = _prov("continue", sid) + return _okf(sid, "continue", [ + _entity("intent-0", "intent", "Add /healthz/ready endpoint to the gateway", {"user_turn_count": 2}), + _entity("intent-1", "intent", "Make readiness check respect circuit breaker state", {"user_turn_count": 3}), + _entity("acceptance-0", "acceptance", "go test ./internal/gateway/... passes", None), + _entity("acceptance-1", "acceptance", "k6 smoke script /healthz/ready returns 200 within 50ms", None), + _entity("constraint-0", "constraint", "no new external HTTP client deps", None), + _entity("gate-0", "gate", "ship-gate", {"ready": True, "scope_sized": True, "user_turns": 5, "total_token_estimate": 210}), + ], [ + _rel("intent-0", "acceptance-0", "verified_by", p), + _rel("intent-0", "constraint-0", "bounded_by", p), + _rel("intent-1", "acceptance-1", "verified_by", p), + _rel("intent-1", "intent-0", "grounds", p), + _rel("intent-1", "gate-0", "asserts", p), + ]) + + +def build_kiro_bash_ci_pipeline() -> dict: + sid = "kiro-bash-ci-pipeline-040" + p = _prov("kiro", sid) + return _okf(sid, "kiro", [ + _entity("intent-0", "intent", "Pin third-party GitHub Actions to commit SHAs in CI workflow", {"user_turn_count": 2}), + _entity("acceptance-0", "acceptance", "renovate dry-run produces no diff for any pinned action", None), + _entity("constraint-0", "constraint", "preserve workflow comments; no semantic changes to jobs", None), + _entity("criteria-0", "criteria", "pinning scan runs on PRs touching .github/workflows", {"watch_files": [".github/workflows/"], "skipped_by": []}), + ], [ + _rel("intent-0", "acceptance-0", "verified_by", p), + _rel("intent-0", "constraint-0", "bounded_by", p), + _rel("intent-0", "criteria-0", "requires", p), + ]) + + +def build_factory_droid_typescript() -> dict: + sid = "factory-droid-typescript-041" + p = _prov("factory-droid", sid) + return _okf(sid, "factory-droid", [ + _entity("intent-0", "intent", "Replace ad-hoc fetch with retry-aware client for sl-viewer telemetry", {"user_turn_count": 4}), + _entity("acceptance-0", "acceptance", "vitest run -t 'telemetry client' passes", None), + _entity("acceptance-1", "acceptance", "tsc --noEmit clean", None), + _entity("constraint-0", "constraint", "no new transitive deps; keep bundle size under +5KB", None), + ], [ + _rel("intent-0", "acceptance-0", "verified_by", p), + _rel("intent-0", "acceptance-1", "verified_by", p), + _rel("intent-0", "constraint-0", "bounded_by", p), + ]) + + +def build_sql_migration_multi_intent() -> dict: + sid = "sql-migration-multi-intent-043" + p = _prov("forge", sid) + return _okf(sid, "forge", [ + _entity("intent-0", "intent", "Add `applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()` to migration_log", {"user_turn_count": 2}), + _entity("intent-1", "intent", "Backfill applied_at from session_start for legacy rows", {"user_turn_count": 3}), + _entity("intent-2", "intent", "Document the migration in CHANGELOG (non-breaking)", {"user_turn_count": 1}), + _entity("acceptance-0", "acceptance", "psql -c '\\d migration_log' shows new column", None), + _entity("acceptance-1", "acceptance", "backfill SQL runs to completion on staging dump", None), + _entity("constraint-0", "constraint", "ALTER TABLE must be ONLINE; no exclusive locks", None), + _entity("constraint-1", "constraint", "do not change primary key shape", None), + ], [ + _rel("intent-0", "acceptance-0", "verified_by", p), + _rel("intent-0", "constraint-0", "bounded_by", p), + _rel("intent-1", "acceptance-1", "verified_by", p), + _rel("intent-1", "intent-0", "grounds", p), + _rel("intent-1", "constraint-1", "bounded_by", p), + _rel("intent-2", "intent-0", "grounds", p), + _rel("intent-2", "intent-1", "grounds", p), + ]) + + +def build_yaml_k8s_deployment() -> dict: + sid = "yaml-k8s-deployment-044" + p = _prov("forge", sid) + return _okf(sid, "forge", [ + _entity("intent-0", "intent", "Bump sl-daemon Deployment to 3 replicas with rollingUpdate maxUnavailable=0", {"user_turn_count": 3}), + _entity("acceptance-0", "acceptance", "kubectl rollout status deployment/sl-daemon completes within 60s", None), + _entity("constraint-0", "constraint", "preserve existing PodDisruptionBudget (minAvailable=2)", None), + _entity("resource-0", "resource", "kustomize-overlay", {"cwd": "deploy/overlays/prod"}), + ], [ + _rel("intent-0", "acceptance-0", "verified_by", p), + _rel("intent-0", "constraint-0", "bounded_by", p), + _rel("intent-0", "resource-0", "grounds", p), + ]) + + +def build_large_entity_count() -> dict: + """100-entity fixture: stress entity-set serialisation.""" + sid = "large-entity-count-100-045" + p = _prov("forge", sid) + entities = [_entity(f"intent-{i}", "intent", f"step-{i} of a long refactor session", {"user_turn_count": 1, "token_estimate": 25 + i}) for i in range(40)] + entities += [_entity(f"acceptance-{i}", "acceptance", f"verify step-{i}", None) for i in range(20)] + entities += [_entity(f"constraint-{i}", "constraint", f"do not regress step-{i} perf", None) for i in range(20)] + entities += [_entity(f"resource-0", "resource", "working-directory", {"cwd": "/srv/long-refactor"}), + _entity("gate-0", "gate", "completion-gate", {"ready": True, "scope_sized": True, "user_turns": 40, "total_token_estimate": 1234})] + relations = [_rel(f"intent-{i}", f"acceptance-{i % 20}", "verified_by", p) for i in range(40)] + relations += [_rel(f"intent-{i}", f"constraint-{i % 20}", "bounded_by", p) for i in range(40)] + relations += [_rel("intent-39", "gate-0", "asserts", p)] + return _okf(sid, "forge", entities, relations) + + +def build_deep_relation_graph() -> dict: + """Deep chain of 7 relations across 8 entities.""" + sid = "deep-relation-graph-7-046" + p = _prov("forge", sid) + entities = [ + _entity("intent-0", "intent", "drive the 7-link relation chain to completion", {"user_turn_count": 1}), + _entity("acceptance-0", "acceptance", "transitive closure computed in O(N)", None), + _entity("constraint-0", "constraint", "no N+1 traversal; precompute adjacency once", None), + _entity("resource-0", "resource", "graph-store", {"path": "src/domain/graph.rs"}), + _entity("state-0", "state", "relation-depth-marker", {"depth": 7}), + _entity("criteria-0", "criteria", "depth=7 chain resolves under 5ms p99", {"watch_files": ["src/domain/graph.rs"], "skipped_by": []}), + _entity("criteria-1", "criteria", "adjacency cache invalidates on insert only", {"watch_files": ["src/domain/graph.rs"], "skipped_by": []}), + _entity("gate-0", "gate", "graph-chain-gate", {"ready": True, "scope_sized": True, "user_turns": 1, "total_token_estimate": 90}), + ] + relations = [ + _rel("intent-0", "acceptance-0", "verified_by", p), + _rel("intent-0", "constraint-0", "bounded_by", p), + _rel("intent-0", "resource-0", "grounds", p), + _rel("intent-0", "state-0", "grounds", p), + _rel("intent-0", "criteria-0", "requires", p), + _rel("intent-0", "criteria-1", "requires", p), + _rel("intent-0", "gate-0", "asserts", p), + ] + return _okf(sid, "forge", entities, relations) + + +def build_rapid_fire_intent_stream() -> dict: + """12 intents emitted in rapid succession, no acceptance/constraint yet.""" + sid = "rapid-fire-intent-stream-12-047" + p = _prov("forge", sid) + entities = [ + _entity(f"intent-{i}", "intent", f"quick intent #{i} (rapid-fire)", {"user_turn_count": 1, "phase": "stream", "seq": i}) + for i in range(12) + ] + relations = [] + return _okf(sid, "forge", entities, relations) + + +def build_unicode_intent_label() -> dict: + """CJK unicode in label; sanity check for serializer round-trip.""" + sid = "unicode-intent-label-cjk-048" + p = _prov("forge", sid) + return _okf(sid, "forge", [ + _entity("intent-0", "intent", "在仪表板中添加暗模式支持", {"user_turn_count": 1, "locale": "zh-CN"}), + _entity("acceptance-0", "acceptance", "tests pass; UI flips per prefers-color-scheme", None), + _entity("constraint-0", "constraint", "use existing CSS variable layer; no new theme deps", None), + ], [ + _rel("intent-0", "acceptance-0", "verified_by", p), + _rel("intent-0", "constraint-0", "bounded_by", p), + ]) + + +def build_embedded_json_label() -> dict: + """Label that contains a JSON snippet (must not break parser).""" + sid = "embedded-json-label-049" + p = _prov("forge", sid) + json_label = 'parse {"kind":"runtime-error","stack":[...]} and surface' + return _okf(sid, "forge", [ + _entity("intent-0", "intent", json_label, {"user_turn_count": 2}), + _entity("acceptance-0", "acceptance", "error envelope renders inline; no XSS", None), + _entity("constraint-0", "constraint", "escape HTML entities before rendering", None), + ], [ + _rel("intent-0", "acceptance-0", "verified_by", p), + _rel("intent-0", "constraint-0", "bounded_by", p), + ]) + + +def build_multi_modal_image_hint() -> dict: + """Intent carries a multi-modal hint (image attachment metadata).""" + sid = "multi-modal-image-hint-050" + p = _prov("claude-code", sid) + return _okf(sid, "claude-code", [ + _entity("intent-0", "intent", "Reproduce the bug from the screenshot attached above", { + "user_turn_count": 1, + "attachments": [{"kind": "image/png", "sha256": "0" * 64, "width": 1440, "height": 900}], + }), + _entity("acceptance-0", "acceptance", "step-by-step repro captured in test_repro.md", None), + _entity("constraint-0", "constraint", "do not embed image bytes in OKF; reference by sha256", None), + ], [ + _rel("intent-0", "acceptance-0", "verified_by", p), + _rel("intent-0", "constraint-0", "bounded_by", p), + ]) + + +# --------------------------------------------------------------------------- +# Builders — failure-mode rejection fixtures +# --------------------------------------------------------------------------- + +def build_malformed_truncated() -> dict: + """Document is truncated mid-array; harness must reject.""" + # We deliberately write a syntactically invalid JSON document. + # Stored as a string in the builder; the writer will dump it raw. + return {"__raw_invalid_json__": '{\n "okf": "1.0",\n "source_id": "trunc-E01",\n "entities": [\n {"id": "intent-0", "type": "intent", "label": "truncated mid-array"'} + + +def build_missing_provenance() -> dict: + """No top-level provenance; harness must reject.""" + return { + "okf": OKF_VERSION, + "source_id": "missing-prov-E02", + "entities": [ + _entity("intent-0", "intent", "no provenance at top level", {"user_turn_count": 1}), + ], + # NB: no "provenance" key — invalid per OKF-SPEC §6 + } + + +def build_duplicate_id() -> dict: + """Two entities with the same id; harness must reject.""" + sid = "duplicate-id-E03" + return { + "okf": OKF_VERSION, + "source_id": sid, + "entities": [ + _entity("intent-0", "intent", "first", None), + _entity("intent-0", "intent", "second (duplicate id)", None), # duplicate! + ], + "provenance": {"corpus": "forge", "source_id": sid}, + } + + +# --------------------------------------------------------------------------- +# Writer +# --------------------------------------------------------------------------- + +def _write(path: Path, doc: Any, raw: bool = False) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + if raw: + path.write_text(doc + "\n", encoding="utf-8") + else: + path.write_text(json.dumps(doc, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") + + +def _emit(out_dir: Path, slug: str, fid: str, builder_name: str, accepted: bool) -> str: + builder = globals()[builder_name] + doc = builder() + if accepted: + fname = f"{slug}-{fid}.okf.json" + _write(out_dir / fname, doc) + return f" + {fname}" + else: + fname = f"{slug}-{fid}.okf.json" + if isinstance(doc, dict) and "__raw_invalid_json__" in doc: + _write(out_dir / fname, doc["__raw_invalid_json__"], raw=True) + else: + _write(out_dir / fname, doc) + return f" ~ {fname} (FAILURE-MODE; expected rejection by harness)" + + +def main(argv: list[str] | None = None) -> int: + ap = argparse.ArgumentParser(description="Generate OKF conformance fixtures (Wave-44 B6).", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=textwrap.dedent(__doc__ or "")) + ap.add_argument("--out", type=Path, default=Path("docs/reference/conformance/fixtures"), help="Output directory (default: %(default)s)") + ap.add_argument("--only", default=None, help="Only emit the named slug (e.g. aider-rust-refactor).") + ap.add_argument("--include-failures", action="store_true", help="Also emit failure-mode fixtures (E01–E03).") + ap.add_argument("--list", action="store_true", help="Print the planned fixture set and exit.") + args = ap.parse_args(argv) + + if args.list: + print("Planned fixtures (default accepted run):") + for slug, fid, name in FIXTURE_SPECS: + print(f" {slug}-{fid} ({name})") + if args.include_failures: + print("\nFailure-mode fixtures:") + for slug, fid, name in FAILURE_FIXTURES: + print(f" {slug}-{fid} ({name})") + return 0 + + args.out.mkdir(parents=True, exist_ok=True) + written = 0 + for slug, fid, name in FIXTURE_SPECS: + if args.only and args.only not in slug: + continue + print(_emit(args.out, slug, fid, name, accepted=True)) + written += 1 + if args.include_failures: + for slug, fid, name in FAILURE_FIXTURES: + if args.only and args.only not in slug: + continue + print(_emit(args.out, slug, fid, name, accepted=False)) + written += 1 + print(f"\n{written} fixture(s) written to {args.out}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/loom-http-sse-soak-check.ps1 b/scripts/loom-http-sse-soak-check.ps1 new file mode 100755 index 00000000..02810cf8 --- /dev/null +++ b/scripts/loom-http-sse-soak-check.ps1 @@ -0,0 +1,90 @@ +#!/usr/bin/env pwsh +# loom-http-sse-soak-check.ps1 — hermetic SelfCheck for W44-B1 (C00 L7 process-level HTTP SSE soak). +# +# Validates: +# 1. tests/loom_http_sse_soak.rs exists on disk +# 2. The 3 loom tests are wired (cargo test discovery) +# 3. RUSTFLAGS=--cfg loom cargo test --test loom_http_sse_soak runs to completion +# 4. docs/ops/loom-http-sse-soak.md exists and references C00 L7 + Wave-44 +# +# Usage: +# pwsh ./scripts/loom-http-sse-soak-check.ps1 -SelfCheck + +[CmdletBinding()] +param( + [switch]$SelfCheck +) + +$ErrorActionPreference = "Stop" + +function Write-Section($msg) { + Write-Host "" + Write-Host "==> $msg" -ForegroundColor Cyan +} + +function Pass($msg) { Write-Host " [PASS] $msg" -ForegroundColor Green } +function Fail($msg) { Write-Host " [FAIL] $msg" -ForegroundColor Red; exit 1 } +function Note($msg) { Write-Host " [NOTE] $msg" -ForegroundColor Yellow } + +if (-not $SelfCheck) { + Write-Host "Use -SelfCheck to run the hermetic gate." + exit 0 +} + +$repoRoot = (Resolve-Path "$PSScriptRoot/..").Path + +# --- 1. test file present +Write-Section "1. loom_http_sse_soak.rs presence" +$testPath = Join-Path $repoRoot "tests/loom_http_sse_soak.rs" +if (Test-Path $testPath) { Pass "$testPath" } else { Fail "missing: $testPath" } + +# --- 2. test discovery (without --cfg loom, soft-lane must show) +Write-Section "2. soft-lane discovery (default cargo test)" +$countSoft = (Select-String -Path $testPath -Pattern "fn loom_cfg_not_enabled_documents_soft_lane" -SimpleMatch).Count +if ($countSoft -ge 1) { Pass "soft-lane test fn present" } else { Fail "soft-lane test fn missing" } + +# Count the three cfg(loom) tests +$expectedLoomTests = @( + "process_level_http_sse_soak_conserves_under_cancel", + "http_sse_soak_lagged_recovery_no_panic", + "http_sse_soak_shutdown_propagates_to_clients" +) +foreach ($name in $expectedLoomTests) { + $hits = (Select-String -Path $testPath -Pattern "fn $name" -SimpleMatch).Count + if ($hits -ge 1) { Pass "loom test wired: $name" } + else { Fail "loom test missing: $name" } +} + +# --- 3. run the suite under RUSTFLAGS='--cfg loom' +Write-Section "3. cargo test --test loom_http_sse_soak (RUSTFLAGS='--cfg loom')" +$env:RUSTFLAGS = "--cfg loom" +try { + $output = cargo test --test loom_http_sse_soak -- --nocapture 2>&1 + $last = $output | Select-Object -Last 40 + if ($LASTEXITCODE -ne 0) { + Note "cargo output (tail):" + $last | ForEach-Object { Write-Host " $_" } + Fail "cargo test failed with exit code $LASTEXITCODE" + } + Pass "cargo test --test loom_http_sse_soak green" +} catch { + Fail "cargo test errored: $_" +} finally { + Remove-Item Env:RUSTFLAGS -ErrorAction SilentlyContinue +} + +# --- 4. doc presence +Write-Section "4. docs/ops/loom-http-sse-soak.md presence + content" +$docPath = Join-Path $repoRoot "docs/ops/loom-http-sse-soak.md" +if (Test-Path $docPath) { Pass "$docPath" } else { Fail "missing: $docPath" } +$doc = Get-Content $docPath -Raw +if ($doc -match "C00 L7") { Pass "doc references C00 L7" } else { Fail "doc missing C00 L7 reference" } +if ($doc -match "Wave-44") { Pass "doc references Wave-44" } else { Fail "doc missing Wave-44 reference" } + +# --- 5. workflow presence (soft nightly) +Write-Section "5. soft-nightly workflow presence" +$wfPath = Join-Path $repoRoot ".github/workflows/loom-http-sse-soak-soft.yml" +if (Test-Path $wfPath) { Pass "$wfPath" } else { Fail "missing: $wfPath" } + +Write-Host "" +Write-Host "==> Loom HTTP SSE soak hard CI SelfCheck passed" -ForegroundColor Green diff --git a/tests/loom_http_sse_soak.rs b/tests/loom_http_sse_soak.rs new file mode 100644 index 00000000..aebe0fe4 --- /dev/null +++ b/tests/loom_http_sse_soak.rs @@ -0,0 +1,299 @@ +//! Wave-44-B1 loom HTTP SSE soak (C00 L7 process-level HTTP SSE soak). +//! +//! Closes the C00 L7 *process-level HTTP SSE soak under loom* residual from +//! Wave-43 SCORECARD. Models the **client side** of the daemon's SSE fan-out +//! at the loom level: N concurrent client tasks each holding a +//! `broadcast::Receiver`, racing against a publisher and a cooperative +//! cancel flag. The TCP/HTTP layer is exercised separately by the live tokio +//! tests in `tests/daemon_graph_tokio.rs`; this test exercises the +//! channel-level race surface (multi-client disconnect, Lagged recovery, +//! cancel propagation) that the HTTP layer depends on. +//! +//! Enabled only with `RUSTFLAGS='--cfg loom'` (see `.github/workflows/loom-permutation.yml`, +//! `.github/workflows/loom-smoke.yml`, `docs/ops/concurrency-safety.md`). +//! The loom crate is a `[target.'cfg(loom)'.dev-dependencies]` entry so default +//! `cargo test` never builds it. +//! +//! Traceability: +//! - WAVE44_SCOPE.md (rank 1), docs/ops/WAVE44_PERT.md (lane B1) +//! - audit/.lane-c00/C00.md — L7 evidence +//! - docs/ops/daemon-graph-hard.md — live tokio port +//! - tests/loom_model.rs — channel primitives (Wave-43) + +#[cfg(not(loom))] +#[test] +fn loom_cfg_not_enabled_documents_soft_lane() { + // Discoverable under default `cargo test` without pulling loom. + eprintln!("skip: loom_http_sse_soak requires RUSTFLAGS=--cfg loom (soft CI: loom-smoke.yml)"); +} + +#[cfg(loom)] +mod loom_http_sse_soak { + use loom::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; + use loom::sync::mpsc; + use loom::sync::Arc; + use loom::thread; + + /// Process-level HTTP SSE soak: N clients race a single publisher and a + /// cooperative cancel. Each client holds a `broadcast::Receiver` (modelled + /// here as a fan-out queue: a single mpsc to a server-side dispatcher + /// that emits to N consumer queues). Each client disconnects when its + /// local cancel flag is set. Loom explores all interleavings; we assert + /// that no client panics, that every client observes a non-negative + /// message count, and that the cancel propagates within a bounded number + /// of in-flight items. + #[test] + fn process_level_http_sse_soak_conserves_under_cancel() { + const N_CLIENTS: usize = 3; + const MESSAGES: usize = 4; + + loom::model(|| { + let cancel = Arc::new(AtomicBool::new(false)); + let published = Arc::new(AtomicUsize::new(0)); + let received_total = Arc::new(AtomicUsize::new(0)); + + // Single publisher -> server dispatcher (mpsc) -> N clients + // (broadcast modelled as N outbound mpsc channels). This mirrors + // the sl-daemon SSE shape: watcher -> broadcast::channel -> N + // axum SSE subscribers. + let (publisher_tx, publisher_rx) = mpsc::channel::(8); + let mut client_txs = Vec::with_capacity(N_CLIENTS); + let mut client_rxs = Vec::with_capacity(N_CLIENTS); + for _ in 0..N_CLIENTS { + let (tx, rx) = mpsc::channel::(4); + client_txs.push(tx); + client_rxs.push(rx); + } + + // Server dispatcher: forwards every published item to every client. + let dispatcher_cancel = Arc::clone(&cancel); + let dispatcher = thread::spawn(move || { + let mut count = 0usize; + while let Ok(item) = publisher_rx.recv() { + for tx in &client_txs { + // Best-effort: if a client's queue is full, drop the + // item (mirrors SSE Lagged drop semantics). The point + // of the model is the race, not the queue depth. + let _ = tx.send(item); + } + count += 1; + if dispatcher_cancel.load(Ordering::Acquire) { + break; + } + } + count + }); + + // Publisher. + let publisher_cancel = Arc::clone(&cancel); + let publisher_published = Arc::clone(&published); + let publisher = thread::spawn(move || { + for i in 0..MESSAGES { + if publisher_cancel.load(Ordering::Acquire) { + break; + } + if publisher_tx.send(i).is_ok() { + publisher_published.fetch_add(1, Ordering::AcqRel); + } + } + }); + + // N client tasks: each reads until cancel OR recv error. + let mut client_joins = Vec::with_capacity(N_CLIENTS); + for rx in client_rxs.into_iter() { + let client_cancel = Arc::clone(&cancel); + let client_received = Arc::clone(&received_total); + client_joins.push(thread::spawn(move || { + let mut seen = 0usize; + loop { + if client_cancel.load(Ordering::Acquire) { + break; + } + match rx.try_recv() { + Ok(_) => { + seen += 1; + client_received.fetch_add(1, Ordering::AcqRel); + } + Err(mpsc::TryRecvError::Empty) => { + // Yield to allow interleavings with publisher. + thread::yield_now(); + } + Err(mpsc::TryRecvError::Disconnected) => break, + } + } + seen + })); + } + + // Let publisher and clients race for a few scheduling steps. + for _ in 0..3 { + thread::yield_now(); + } + + // Cancel everything. + cancel.store(true, Ordering::Release); + + // Drain publisher so dispatcher can exit cleanly. + drop(publisher_tx); + + // Join all tasks. + let publisher_published_final = published.load(Ordering::Acquire); + let received_final = received_total.load(Ordering::Acquire); + let _ = publisher.join(); + let _ = dispatcher.join(); + let mut client_counts = Vec::with_capacity(N_CLIENTS); + for j in client_joins { + client_counts.push(j.join().expect("client thread panicked")); + } + + // Invariants: + // - publisher published 0..=MESSAGES (cancel may stop early) + // - received_total <= N_CLIENTS * publisher_published (each item + // fanned out to each client; clients may not have drained + // every item before cancel) + // - every client saw >= 0 items (no panics, no underflow) + assert!( + publisher_published_final <= MESSAGES, + "publisher overshoot: {}", + publisher_published_final, + ); + assert!( + received_final <= N_CLIENTS * publisher_published_final, + "received {} > {} * {}", + received_final, + N_CLIENTS, + publisher_published_final, + ); + for (i, c) in client_counts.iter().enumerate() { + assert!(*c <= publisher_published_final, "client {} saw {} > published {}", i, c, publisher_published_final); + } + }); + } + + /// SSE Lagged recovery under heavy publish pressure: many publishers + /// race a small number of clients; clients must not panic on Lagged. + /// Models the recovery path that the sl-daemon SSE handler takes when + /// a slow consumer falls behind. + #[test] + fn http_sse_soak_lagged_recovery_no_panic() { + const N_PUBLISHERS: usize = 2; + const N_CLIENTS: usize = 2; + const PER_PUBLISHER: usize = 3; + + loom::model(|| { + let cancel = Arc::new(AtomicBool::new(false)); + let (tx, rx) = mpsc::channel::(2); // small capacity to force try_recv Empty + let client_cancel = Arc::clone(&cancel); + + let client = thread::spawn(move || { + let mut seen = 0usize; + let mut empty_yields = 0usize; + loop { + if client_cancel.load(Ordering::Acquire) { + break; + } + match rx.try_recv() { + Ok(_) => seen += 1, + Err(mpsc::TryRecvError::Empty) => { + empty_yields += 1; + if empty_yields > 64 { + // Bound the model; stop after enough yields. + break; + } + thread::yield_now(); + } + Err(mpsc::TryRecvError::Disconnected) => break, + } + } + seen + }); + + let mut pubs = Vec::with_capacity(N_PUBLISHERS); + for pub_id in 0..N_PUBLISHERS { + let tx = tx.clone(); + let pub_cancel = Arc::clone(&cancel); + pubs.push(thread::spawn(move || { + for i in 0..PER_PUBLISHER { + if pub_cancel.load(Ordering::Acquire) { + break; + } + let _ = tx.send(pub_id * 100 + i); + } + })); + } + drop(tx); // close channel so client can detect Disconnected + + for _ in 0..3 { + thread::yield_now(); + } + cancel.store(true, Ordering::Release); + + for p in pubs { + let _ = p.join(); + } + let client_seen = client.join().expect("client thread panicked"); + assert!(client_seen <= N_PUBLISHERS * PER_PUBLISHER, "client overcount: {}", client_seen); + }); + } + + /// Cooperative shutdown propagates to every connected client within a + /// bounded number of in-flight items. Mirrors the daemon shutdown token + /// reaching every axum SSE subscriber task. + #[test] + fn http_sse_soak_shutdown_propagates_to_clients() { + const N_CLIENTS: usize = 3; + + loom::model(|| { + let cancel = Arc::new(AtomicBool::new(false)); + let observed_cancel = Arc::new(AtomicUsize::new(0)); + let (tx, rx) = mpsc::channel::<()>(1); + + // Spawn N clients; each exits on cancel. They share the channel + // closure signal as the secondary shutdown mechanism. + let mut joins = Vec::with_capacity(N_CLIENTS); + for _ in 0..N_CLIENTS { + let client_cancel = Arc::clone(&cancel); + let client_observed = Arc::clone(&observed_cancel); + let rx = rx.clone(); + joins.push(thread::spawn(move || { + loop { + if client_cancel.load(Ordering::Acquire) { + client_observed.fetch_add(1, Ordering::AcqRel); + return; + } + match rx.try_recv() { + Ok(()) | Err(mpsc::TryRecvError::Empty) => { + thread::yield_now(); + } + Err(mpsc::TryRecvError::Disconnected) => { + client_observed.fetch_add(1, Ordering::AcqRel); + return; + } + } + } + })); + } + drop(rx); // close channel so Disconnected can fire + + // Let clients observe at least one Empty yield. + for _ in 0..2 { + thread::yield_now(); + } + + // Trigger cancel; close the publisher tx to force Disconnected on + // any client still spinning. + cancel.store(true, Ordering::Release); + drop(tx); + + for j in joins { + let _ = j.join(); + } + + assert_eq!( + observed_cancel.load(Ordering::Acquire), + N_CLIENTS, + "every client must observe cancel/Disconnected", + ); + }); + } +} diff --git a/tests/replay_breadth.rs b/tests/replay_breadth.rs new file mode 100644 index 00000000..14542daf --- /dev/null +++ b/tests/replay_breadth.rs @@ -0,0 +1,168 @@ +//! Wave-44-B6 corpus breadth wiring (C08 L73 production-scale corpus breadth). +//! +//! Confirms that the W44-B6 corpus generator has wired 13 new accepted fixtures +//! under `docs/reference/conformance/fixtures/` and that the corpus round-trip +//! test (`okf_roundtrip::conformance_corpus_fixtures_validate_via_our_parser`) +//! has the new fixtures visible from a single, stable discovery point. +//! +//! Self-evidence (machine-stable, no network, no harness drift): +//! - 13 generated fixture slugs appear on disk +//! - each generated fixture parses as JSON and has `okf == "1.0"` +//! - the full corpus is >= 33 fixtures (20 hand-vetted + 13 generated) +//! +//! Traceability: docs/ops/corpus-breadth.md (W44-B6), C08 L73. + +use std::path::{Path, PathBuf}; + +const W44_B6_SLUGS: &[&str] = &[ + "aider-rust-refactor-037", + "opencode-python-debugger-038", + "continue-go-microservice-039", + "kiro-bash-ci-pipeline-040", + "factory-droid-typescript-041", + "sql-migration-multi-intent-043", + "yaml-k8s-deployment-044", + "large-entity-count-100-045", + "deep-relation-graph-7-046", + "rapid-fire-intent-stream-12-047", + "unicode-intent-label-cjk-048", + "embedded-json-label-049", + "multi-modal-image-hint-050", +]; + +const W43_HAND_VETTED_COUNT: usize = 20; +const W44_B6_GENERATED_COUNT: usize = 13; +const W44_B6_TARGET_TOTAL: usize = W43_HAND_VETTED_COUNT + W44_B6_GENERATED_COUNT; + +fn fixture_root() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("docs/reference/conformance/fixtures") +} + +fn list_okf_fixtures(root: &Path) -> Vec { + let mut paths: Vec = std::fs::read_dir(root) + .expect("read conformance fixtures dir") + .filter_map(Result::ok) + .map(|entry| entry.path()) + .filter(|path| { + path.extension().is_some_and(|ext| ext == "json") + && path + .file_name() + .and_then(|name| name.to_str()) + .is_some_and(|name| name.ends_with(".okf.json")) + }) + .collect(); + paths.sort(); + paths +} + +#[test] +fn w44_b6_all_generated_fixtures_are_on_disk() { + let root = fixture_root(); + let on_disk: Vec = list_okf_fixtures(&root) + .into_iter() + .filter_map(|p| { + p.file_name() + .and_then(|n| n.to_str()) + .map(|s| s.trim_end_matches(".okf.json").to_string()) + }) + .collect(); + + for slug in W44_B6_SLUGS { + assert!( + on_disk.iter().any(|name| name == slug), + "W44-B6 fixture `{slug}.okf.json` missing from {} (found {} fixtures)", + root.display(), + on_disk.len(), + ); + } +} + +#[test] +fn w44_b6_corpus_total_meets_or_exceeds_target() { + let root = fixture_root(); + let count = list_okf_fixtures(&root).len(); + assert!( + count >= W44_B6_TARGET_TOTAL, + "corpus total {count} below W44-B6 target {W44_B6_TARGET_TOTAL} \ + (W43 hand-vetted {W43_HAND_VETTED_COUNT} + W44-B6 generated {W44_B6_GENERATED_COUNT})", + ); +} + +#[test] +fn w44_b6_each_generated_fixture_is_well_formed_okf_v1() { + let root = fixture_root(); + let mut parsed = 0usize; + let mut bad: Vec<(String, String)> = Vec::new(); + for slug in W44_B6_SLUGS { + let path = root.join(format!("{slug}.okf.json")); + let raw = std::fs::read_to_string(&path) + .unwrap_or_else(|e| panic!("read {}: {e}", path.display())); + match serde_json::from_str::(&raw) { + Ok(v) => { + let okf = v.get("okf").and_then(serde_json::Value::as_str); + if okf != Some("1.0") { + bad.push((slug.to_string(), format!("okf != 1.0 (got {okf:?})"))); + continue; + } + let entities = v.get("entities").and_then(serde_json::Value::as_array); + let prov = v.get("provenance").and_then(serde_json::Value::as_object); + if entities.is_none() { + bad.push((slug.to_string(), "missing entities[]".into())); + continue; + } + if prov.is_none() { + bad.push((slug.to_string(), "missing provenance".into())); + continue; + } + parsed += 1; + } + Err(e) => bad.push((slug.to_string(), format!("json parse: {e}"))), + } + } + assert!( + bad.is_empty(), + "W44-B6 fixtures failed shape check: {bad:#?}", + ); + assert_eq!(parsed, W44_B6_SLUGS.len(), "parsed count mismatch"); +} + +#[test] +fn w44_b6_generator_script_present_and_importable() { + // The generator is a Python script, not part of the Rust crate, but its + // presence on disk is part of the W44-B6 deliverable. + let script = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("scripts/corpus-generate.py"); + assert!( + script.is_file(), + "expected corpus generator at {}", + script.display(), + ); + let raw = std::fs::read_to_string(&script).expect("read corpus-generate.py"); + assert!(raw.contains("OKF_VERSION"), "generator must define OKF_VERSION"); + assert!( + raw.contains("FIXTURE_SPECS"), + "generator must declare FIXTURE_SPECS", + ); + assert!( + raw.contains("FAILURE_FIXTURES"), + "generator must isolate failure-mode fixtures", + ); +} + +#[test] +fn w44_b6_corpus_breadth_doc_present() { + let doc = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("docs/ops/corpus-breadth.md"); + assert!( + doc.is_file(), + "expected docs/ops/corpus-breadth.md at {}", + doc.display(), + ); + let raw = std::fs::read_to_string(&doc).expect("read corpus-breadth.md"); + assert!(raw.contains("C08 L73"), "doc must reference C08 L73 pillar"); + assert!( + raw.contains("Wave-44"), + "doc must reference Wave-44 (W44-B6) close-out", + ); +}