linus-dandClaude Opus 4.8 1e50c80627 Compliance hardening: enforce measured behavior, real gates, retained evidence
Addresses the attached findings as blocking compliance failures. Each fix
removes a substitution pattern and adds a negative-control test.

Finding 9 (hash changes != enforced metamorphic expectations):
  ci_reports now enforces a sound metamorphic relation per perturbation — a
  perturbation the program CONSUMES must alter the trace — grounded in
  reachability, with a non-vacuity check. Test:
  consumed_perturbation_must_alter_trace.

Finding 10 (trace counts != causal explanation):
  New causal_explanation gate ablates each recorded causal edge's source lane
  and requires the destination delta to change. Measured: true-source 0.77 vs
  scrambled-source 0.03. Threshold 0.50. New required report
  causal_explanation_report. Test:
  causal_edges_are_intervention_confirmed_not_counted.

Finding 6 (static seed corpus != failure retention):
  replay_corpus::retention adds a committed, append-only counterexample corpus
  (retained_failures.tsv) re-verified every run against the independent runtime.
  Negative control: reintroduced_bug_is_caught_by_retention.

Finding 5 (mini mutation evaluator != real acceptance gate):
  Mutants are now killed by ci_reports' OWN acceptance-gate predicates with
  single-sourced thresholds (TRACE_EDGES_MIN, etc.); evaluate_mutants replaces
  semantic_mutation::run_suite on the acceptance path. Test:
  mutants_killed_by_real_acceptance_gates.

Findings 2 & 3 (generated report != independent attestation; merkle root !=
provenance without leaves):
  ci_reports persists every Merkle leaf (evidence/leaves.tsv) + claims. New
  `attestation` crate + `attest` binary recompute the root from the leaves in a
  SEPARATE process that never reads compliance_report.json; wired as a distinct
  merge-gates step. Negative control: tampered_leaf_breaks_attestation.

Finding 8 (structural indicators != measured behavior):
  Domain gate already requires measured influence/mutation/removal; added an
  explicit reject for "appears structurally but no measured influence".

Finding 1 (workflow != merge enforcement): BLOCKED on server-side branch
  protection. Added .github/rulesets/main-required-checks.json + apply command;
  enforcement still requires a repo admin to activate the ruleset.

Finding 7 (protocol socket E2E != rendered browser E2E): BLOCKED on a CI browser
  runner; rendered-browser E2E remains advisory-only.

Finding 4 (trace summary != full trace evidence): PARTIAL. Each retained leaf
  binds the full trace via canonical_hash over all graph edges, and the root is
  independently recomputed from the leaves; per-execution raw-trace round-trip
  reconstruction by the attestor is not yet implemented.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 21:13:48 -07:00
2026-06-21 20:43:11 -07:00
2026-06-21 20:43:11 -07:00
2026-06-21 19:21:48 -07:00

Magicka VM — Phase 0/1

The deliverable is a Rust engine whose tests make a fake universe fail.

This repository implements the Phase 0/1 specification in plan.md: an adversarial testing framework first, then a reference runtime that passes it, then a runtime under test that matches the reference. No spell content, templates, or cosmetic runes — the value is in the tests that refuse to let the universe collapse into a single score, resource, effect axis, executor, rune, hidden formula, or decorative domain.

Compliance model

No gate may pass from configuration, naming, shared implementation, smoke-scale runs, regenerated expectations, proxy metrics, a default profile, or a locally-runnable binary. A gate passes only from persisted, independently reproducible, full-scale adversarial evidence enforced at merge. Every acceptance obligation has all four of: a measured artifact, a provenance chain to the run that produced it, a merge-blocking enforcement point, and a failure condition if the artifact or provenance is absent.

  • The merge-blocking enforcement point is .github/workflows/merge-gates.yml, whose merge-gates job runs MAGICKA_PROFILE=merge (full gates) and must be a required status check on the protected branch / merge queue. It is not a local binary, and the fast profile is advisory only — it can never stand in for acceptance.
  • compliance_report.json enumerates every obligation, its artifact, its floor, the actual measured value, and whether the artifact is present. A missing required report fails acceptance.
  • The merge floors (50k worlds, 250k programs, 1,000,000 executions, 10 perturbations/exec, 100% reference/runtime comparison over base and perturbations, 500 mutants, 10,000 replay cases) cannot be lowered by environment overrides: a lowering override is recorded as a provenance failure and the floor is kept.

Every gate is built to be able to fail, and a negative-control test proves it does:

Gate How it is made unbypassable Negative control proving it can fail
runtime_equivalence Compares two independent implementations (the reference engine vs. runtime_under_test::native, which never calls the reference engine) buggy_runtime_is_rejected — a runtime with one dropped causal edge is caught
compression_resistance Attacks operate on the real serialized trace (causal influence, info-flow, access, temporal, deltas), not a hash proxy; info loss is genuine unexplained variance single_factor_corpus_is_rejected — a rank-1 universe is rejected
mutation_survivor Each mutant must fail the named gate it targets, not merely differ from the reference reference_passes_every_named_gate + no_mutant_survives_its_named_gate
replay Expectations are loaded from a committed file, not regenerated in the same run corrupted_expectation_is_detected
domain_participation Decorative/redundant domains are flagged directly decorative_domain_is_rejected
merge scale floor Env overrides may only raise merge counts; a lowering attempt is recorded and the floor kept; executions actually performed are counted merge_floor_cannot_be_lowered_by_override, merge_profile_at_smoke_scale_is_rejected
100% comparison Reference vs. runtime-under-test compared for every execution — base and all perturbations, never base only runtime_equivalence gate fails unless equiv_total == base + perturbations
provenance A Merkle root over per-execution records, plus independent engine identities, binds reported numbers to executed work merkle_root_binds_to_leaves

Workspace layout

Built in the mandatory order from the spec:

# Crate Role
1 world_model 8 independent domains, world snapshot, perturbation axes, deltas, deterministic primitives (ids, stable hash, RNG)
rune_ir Rune token / program model (no stream is ever rejected)
2 trace_model Execution trace + all graphs, behavior fingerprint, replay record, fault log, trace metrics
3 generators Worlds, programs, executors, contracts, perturbations; rejects flat cases
4 collapse_analysis The 11 compression attacks over real trace structure + collapse gates
5 semantic_mutation Structurally generated mutant runtimes; proves every one fails its named gate
6 replay_corpus Permanent, bit-exact replay cases persisted to corpus/replay_corpus.tsv
7 reference_runtime The executable spec engine (Runtime trait, resolve)
8 runtime_under_test An independent interpreter (native) proven equivalent to the reference
ci_reports Orchestrator + ci binary; emits 8 gate reports + a provenance report

The runtime under test does not call the reference engine. It re-derives the canonical behavior from the spec in a different code organization, so 100% agreement is evidence the spec is implemented correctly rather than a tautology. (native_matches_reference_bit_for_bit checks this over a 2000-seed sweep.)

The engine in one paragraph

A world is 8 domains, each with 4 observed + 2 hidden integer lanes, a dense 8×8 coupling matrix, partial observability, and pending scheduled effects. A rune program is interpreted under ≥3 executors; each opcode reads several domains, mixes them through a nonlinear avalanche keyed by per-domain constants, the world coupling, and the executor's salt, then writes back — recording causal/read/write/information-flow/temporal edges as it goes. Scheduled effects and coupling diffusion propagate changes 3 turns into the future.

Running CI

cargo test                                          # unit tests + negative controls
MAGICKA_PROFILE=fast  cargo run --release -p ci_reports --bin ci   # advisory PR slice
MAGICKA_PROFILE=merge cargo run --release -p ci_reports --bin ci   # acceptance (full gates)

Reports are written to the output dir (8 gate reports + provenance_report.json

  • compliance_report.json + ci_summary.md). The binary exits non-zero if any gate fails or any required artifact is absent.

Profiles

MAGICKA_PROFILE (or MAGICKA_SCALE) selects the run profile.

Profile executions replay mutants role
fast (default) 600 10,000 (committed) 520 advisory only — never acceptance
tiny 120 10,000 520 smoke
merge (MAGICKA_SCALE=full) 1,000,000 10,000 600 acceptance — hard floors

The fast/tiny profiles print ADVISORY … NOT a merge-blocking acceptance run and are labelled non-acceptance in compliance_report.json. Acceptance comes only from the merge profile, run by the merge-gates workflow. The merge floors cannot be lowered by environment overrides (a lowering override is recorded as a provenance failure and the floor kept).

Merge-blocking enforcement (required check)

.github/workflows/merge-gates.yml defines the enforcement point. Configure branch protection / the merge queue to require the merge-gates job. That job runs the full merge profile, verifies the committed corpus has ≥10,000 cases, and fails if any required artifact is missing. The full run executes ~1M base executions × (1 base + 10 perturbations) with 100% reference/runtime comparison; it completes in minutes on a CI runner.

Replay corpus

The replay corpus is committed at crates/replay_corpus/corpus/replay_corpus.tsv (10,000 cases). Replay loads those expectations and re-executes the reference, so any engine change that alters a hash makes the committed file and the fresh run disagree and CI fails. Regenerate it only as a deliberate, reviewed migration:

cargo run --release -p replay_corpus --bin freeze -- 10000

Determinism

Everything is seed-derived and integer-only (SplitMix64 RNG, FNV-1a content hashing, wrapping/guarded arithmetic). No floating point enters a canonical hash, so replay is bit-exact across machines and runs. No external crates.

The web game (plan2.md)

A browser game is built around the existing runtime — it is a playable window into the Rust universe, never a second simulation. The browser sends only intent; the server is the sole authority; every rune program executes through the independent interpreter (runtime_under_test::native_resolve) against the shared world. The game deliberately does not call the reference engine — the interpreter it uses is the one the runtime-equivalence gate proves correct (with a negative control proving that gate can fail). Same constraints as the rest of the repo: pure std, no external crates (the WebSocket server hand-rolls SHA-1, base64, and RFC 6455 framing; JSON is hand-rolled with a total parser).

Audit note: the hand-rolled SHA-1 / base64 / RFC-6455 framing and JSON parser are checked against published test vectors (RFC 6455 §1.3 accept key, SHA-1 "abc", base64 length cases) and a fuzz gate, but they are bespoke cryptographic/parsing code and carry audit risk relative to a reviewed library. They exist to honor the repo's no-external-crates rule; a future hardening pass could swap in vetted implementations behind the same interface.

Rust runtime → game_runtime (authority) → protocol (WS messages) → server → browser
Crate Role
protocol Versioned, hashable, total-decode client/server messages + JSON value/parser. A malformed packet yields Err, never a panic.
game_runtime Authoritative match state. Resolves turns through the independent interpreter (runtime_under_test, not the reference engine), filters visibility/knowledge, records + regenerates replays. A match is a pure function of (seed, roster, ordered inputs).
web_assets The embedded browser client (HTML/CSS/JS): arena, rune editor, domain/knowledge panels, replay viewer.
web_client Static-asset HTTP delivery (keeps raw assets separate from framing).
server std::net HTTP + WebSocket server: turn timer, action collection, disconnect handling, panic-proof dispatch.
web_tests A dependency-free WebSocket test client + the Phase H gates.

Running it

cargo run --release -p server --bin magicka-server   # serve on 127.0.0.1:8080
# then open http://127.0.0.1:8080 in a browser
MAGICKA_ADDR=0.0.0.0:9000 MAGICKA_TURN_MS=8000 cargo run --release -p server --bin magicka-server

Join is immediate (1 player + a training dummy). A duel shares a match by id: two browsers that JoinMatch the same match_id take slots 1 and 2.

Web CI gates (Phase H)

These gates are merge-blocking: they run inside the merge-required job in .github/workflows/merge-gates.yml (and as fast PR feedback in web-gates.yml). They are the Rust suite in crates/web_tests, run with cargo test -p web_tests:

Gate Test Minimum Status
Replay determinism determinism.rs 1,000 simulated matches, 0 hash mismatches merge-blocking
Protocol fuzz fuzz.rs 10,000 fuzz cases, 0 panics (+ a live server survives a malformed-packet burst) merge-blocking
End-to-end matches e2e.rs 100 full matches over real sockets; recorded replay reproduces every live per-turn hash merge-blocking
Hidden-state leaks visibility.rs 0 leaks — no client-bound frame carries a hidden key; redaction counts every withheld value merge-blocking
Disconnect / timer edges resilience.rs mid-match disconnect does not corrupt the match; wrong-turn / late submits are rejected deterministically merge-blocking
Rendered-browser E2E e2e/specs/play.spec.js a real browser joins, casts, and replays a match external-blocked (advisory only)

Scope honesty — two distinct things, not conflated:

  • The "100 E2E matches" merge-blocking gate drives the full HTTP→WebSocket→protocol→runtime path headlessly over real sockets. This is protocol-level coverage. It is not rendered-browser coverage and is not claimed as such.
  • Rendered-browser coverage is blocked on CI infrastructure: this CI has no real browser, so the Playwright suite under crates/web_tests/e2e/ cannot be merge-blocking yet. It runs advisory-only (continue-on-error) in the rendered-browser-e2e job and uploads its report as an artifact. Until a CI runner with a browser exists, rendered-browser E2E is treated as unsatisfied, not green. Run it locally with:
cd crates/web_tests/e2e && npm install && npx playwright install chromium && npm test

Acceptance criteria mapping (plan2.md)

Criterion Where it holds
A player can join a browser match server join + web_assets client; e2e.rs::single_match_full_playthrough
A turn timer runs server timer thread; client header countdown
Inspect / move / attack / cast Action in protocol; game_runtime::apply_action
Rune programs execute only on the server game_runtime is the only caller of the interpreter (runtime_under_test::native_resolve); client never imports EngineConfig (asserted in web_assets)
Results return as filtered observations VisibleWorldSnapshot; visibility.rs
Replay can reproduce the match game_runtime::replay; determinism.rs, e2e.rs
Browser cannot alter hidden truth intent-only protocol; visibility.rs leak gate
CI proves protocol, replay, visibility, authority merge-blocking gates in merge-gates.yml (+ web-gates.yml); rendered-browser E2E remains external-blocked
S
Description
Worst magicka
Readme
1,005 KiB
Languages
Rust 95.2%
JavaScript 3.2%
CSS 1%
HTML 0.6%