Skip to content

feat: finish the v0.1 engine — dag, check, state machine, event stream#2

Merged
hyperpolymath merged 2 commits into
mainfrom
claude/modest-hawking-3fd6it
Jun 18, 2026
Merged

feat: finish the v0.1 engine — dag, check, state machine, event stream#2
hyperpolymath merged 2 commits into
mainfrom
claude/modest-hawking-3fd6it

Conversation

@hyperpolymath

Copy link
Copy Markdown
Owner

Phase 1 — finish the v0.1 engine

Implements the spec's first-sprint items 4/5/6 (docs/arghda-spec.adoc) plus a soundness lint. The engine now produces the JSON contract the planned AffineScript visual layer (arghda-studio, see docs/arghda-vision.adoc) will consume. Verified with the real Agda toolchain (2.6.3) and dogfooded against the echo-types corpus, not eyeballed.

New capabilities

  • graph module — the Agda import graph is now first-class (nodes + typed imports edges, deterministic output). Lifted out of the orphan-module rule, which had computed the edges and thrown them away; the rule now reuses it.
  • dag command — emits the dependency-DAG JSON: nodes with lint-derived status (clean/warn/blocked), import edges, and a blocked list (self-blocked by rule + prerequisite-blocked by an unclean import).
  • Workspace state machinetransition/state_of move files between the four triage states, validated against the spec's transition table, each move appended to .arghda/events.jsonl. CLI: claim, promote, reject, requeue, invalidate, events.
  • check command — runs Agda on a file and combines the typecheck verdict with the lint report (proven-eligible / rejected / agda-unavailable); degrades gracefully when agda is absent.
  • event (append-only JSONL log + reader), agda (typecheck invocation, graceful NotFound), timestamp (dependency-free RFC 3339 — keeps the build hermetic against the lockfile).
  • New lint rule unjustified-postulate (hard-block) — a postulate opener with no adjacent -- JUSTIFY: comment.

Verification (the "do it right, not looks-fine" part)

  • cargo fmt --check / clippy -D warnings / build / test all clean — 20 tests (11 unit + 9 integration: dag, transitions, smoke).
  • check against real Agda 2.6.3: clean file → proven-eligible; a file that typechecks but lacks --saferejected by lint (the silent-failure class the spec targets); a type error → rejected (agda exit 42). JSON output verified.
  • dag/scan dogfooded on echo-types (193 modules): 903 import edges; scan independently rediscovers the orphan the 2026-06-16 trust audit found by hand (experimental/echo-additive/VarianceGate.agda) and the three files deliberately outside the --safe --without-K kernel cone (Fidelity.agda, the cubical island, the postulated shadow).

Deferred to the next slice

Remaining lint rules (missing-without-k, unpinned-headline, unused-import, tab-mix), content-hash invalidation of proven, multi-root reachability (echo-types is reachable from a union of CI roots, so single-entry orphan detection over-reports), the Groove service manifest, and the .machine_readable/ RSR retrofit.

Draft for review.

🤖 Generated with Claude Code

https://claude.ai/code/session_019GiSiEfgZCte35dyykgBHs


Generated by Claude Code

claude added 2 commits June 18, 2026 12:49
Implements the spec's first-sprint items 4/5/6 (arghda-spec.adoc) plus a
soundness lint, all verified with the real Agda toolchain (2.6.3) and
dogfooded against the echo-types corpus.

New capabilities
- `graph` module: the Agda import graph is now first-class (nodes + typed
  `imports` edges, deterministic/sorted output). Lifted out of the
  `orphan-module` rule, which had computed the edges and thrown them away;
  the rule now reuses it.
- `dag` command: emits the dependency-DAG JSON contract a visual layer
  consumes — nodes with lint-derived status (clean/warn/blocked), import
  edges, and a `blocked` list (self-blocked by rule + prerequisite-blocked
  by an unclean import).
- Workspace state machine: `transition`/`state_of` move files between the
  four triage states, validated against the spec's transition table, each
  move appended to `.arghda/events.jsonl`. CLI: `claim`, `promote`,
  `reject`, `requeue`, `invalidate`, `events`.
- `check` command: runs Agda on a file and combines the typecheck verdict
  with the lint report (`proven-eligible` / `rejected` / `agda-unavailable`).
  Degrades gracefully when `agda` is not on PATH.
- `event` module: typed append-only JSONL event log + reader.
- `agda` module: typecheck invocation with graceful NotFound handling.
- `timestamp` module: dependency-free RFC 3339 UTC (keeps the build
  hermetic against the committed lockfile).
- New lint rule `unjustified-postulate` (hard-block): a `postulate` opener
  with no adjacent `-- JUSTIFY:` comment.

Verification
- cargo fmt/clippy(-D warnings)/build/test all clean; 20 tests
  (11 unit + 9 integration: dag, transitions, smoke).
- `check` exercised against real Agda: clean file -> proven-eligible;
  typechecking file missing --safe -> rejected by lint; type error ->
  rejected by agda (exit 42); JSON output verified.
- `dag`/`scan` dogfooded on echo-types (193 modules): 903 import edges;
  `scan` independently rediscovers the orphan the 2026-06-16 trust audit
  found by hand (VarianceGate.agda) and the files outside the
  --safe --without-K kernel cone (Fidelity.agda, the cubical island, the
  postulated shadow).

Deferred (next slice): remaining lint rules, content-hash invalidation of
`proven`, the Groove service manifest, and the `.machine_readable/` RSR
retrofit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019GiSiEfgZCte35dyykgBHs
The orphan rule previously computed reachability from a single entry
module, so on a corpus with several CI roots it over-reported: every
module reachable only from a non-`All.agda` root looked orphaned. The
echo-types dogfood made this concrete — 38 orphan hits, most of them
false.

Changes
- `graph::discover_roots` auto-discovers CI roots (`All.agda`/`Smoke.agda`
  at any depth); `graph::reachable_from_roots` unions reachability across
  them.
- `LintContext.entry_module: &Path` -> `entry_modules: &[PathBuf]`; the
  orphan rule flags a module only if it is unreachable from *every* root
  and is not itself a root.
- `scan` and `dag` share `resolve_roots_and_rules`: explicit `--entry`
  (now repeatable) wins, else auto-discover; if no roots are found the
  orphan rule is dropped (with a note) rather than flagging everything.
- `dag` document now carries `entry_modules` (the roots it used).

Verification
- cargo fmt/clippy(-D warnings)/test clean; 21 tests (added a graph unit
  test proving union reachability: Used via All + Other via Smoke are
  reachable, Orphan via neither is not).
- Re-dogfooded on echo-types (193 modules, 5 discovered roots): orphan
  reports drop 38 -> 17 genuine orphans (the experimental/echo-additive
  tree incl. VarianceGate.agda, plus standalone scratch files); the
  `dag` clean count rises 155 -> 176. postulate/pragma hits unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019GiSiEfgZCte35dyykgBHs
@hyperpolymath hyperpolymath marked this pull request as ready for review June 18, 2026 13:35
@hyperpolymath hyperpolymath merged commit ace54f3 into main Jun 18, 2026
1 check passed
@hyperpolymath hyperpolymath deleted the claude/modest-hawking-3fd6it branch June 18, 2026 13:36
hyperpolymath added a commit that referenced this pull request Jun 18, 2026
## Phase 1 (cont.) — round out the v0.1 lint pack

Follows the merged engine (#2). Adds the two cheap, high-value rules
from the spec/plan that need no external tooling, on top of multi-root
reachability.

### New rules
- **`escape-hatch`** (warn) — surfaces soundness escape hatches:
termination-checker overrides (`TERMINATING`, `NON_TERMINATING`,
`NO_TERMINATION_CHECK`) and trust primitives
(`believe_me`/`primTrustMe`). **Warn, not hard-block** — these are
sometimes legitimately budgeted (echo-types' quarantine discipline), so
the rule makes them *visible* without blocking promotion. Precise:
ignores the words inside `--` comments and only matches whole tokens (so
`believe_me_helper` doesn't trip it).
- **`tab-mix`** (warn) — a tab in leading whitespace (Agda style is
spaces).

`missing-without-k` is intentionally **not** added —
`missing-safe-pragma` already reports a missing `--without-K`.

### Verification (real toolchain, dogfooded)
- `cargo fmt --check` / `clippy -D warnings` / `test` clean — **28
tests** (+7 unit: 2 `tab-mix`, 5 `escape-hatch`).
- Re-dogfooded on echo-types (193 modules): **0** escape-hatch/tab-mix
hits — confirmed a *true negative* by ground-truth grep (the only two
`.agda` files containing the word "TERMINATING" carry it in `--
TERMINATING CONDITION` **comments**, which the rule correctly skips). A
synthetic file with a real `{-# TERMINATING #-}` + `believe_me` produces
exactly two warns. So the rule is both precise (no comment
false-positives) and correct (fires on real escapes).

### Still deferred
`unpinned-headline` (needs `Smoke.agda` parsing + a configurable regex),
`unused-import` (shells out to `agda-unused`), content-hash invalidation
of `proven`, the Groove service manifest, and the `.machine_readable/`
RSR retrofit (its own PR next).

Draft for review.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_019GiSiEfgZCte35dyykgBHs

---
_Generated by [Claude
Code](https://claude.ai/code/session_019GiSiEfgZCte35dyykgBHs)_
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants