Skip to content

feat: port Phase 3/4/5 crates (context/fleet/graph/mcp/media/pipeline/tui) from the monorepo - #3

Merged
macanderson merged 2 commits into
mainfrom
feat/port-phase345-crates
Jul 12, 2026
Merged

feat: port Phase 3/4/5 crates (context/fleet/graph/mcp/media/pipeline/tui) from the monorepo#3
macanderson merged 2 commits into
mainfrom
feat/port-phase345-crates

Conversation

@macanderson

Copy link
Copy Markdown
Owner

Summary

Completes the Stella ejection: ports the Phase 3/4/5 cratescontext, fleet, graph, mcp, media, pipeline, tui — from the oxagen monorepo into this workspace (renamed oxagen-*stella-*). These crates originated in the private monorepo and were never ejected; the original eject captured only the hardened core. After this, stella-cli is the canonical, complete Stella.

Why this was a real merge, not a copy

The two streams diverged. The eject took the hardened core (parallel tools, StepUsage, goal loop, DuckDB store, ToolSchema.read_only). The monorepo kept Phase 3/4/5 built against the older protocol. So the crates needed reconciling against this repo's stella-protocol/stella-core.

stella-protocol unioned

The ported crates required event/type vocabulary this repo's protocol lacked. Added (this repo's own StepUsage/GoalVerdict preserved):

  • AgentEvent: FileChange, ContextRecall, ContextWrite, JudgeVerdict, ScopeReview, AskUser, MediaProgress, MediaComplete, Commit, Pr
  • StageKind: Reflect
  • types: MediaKind, MediaJobState, MediaArtifactRef, PrStatus, ContextFrameRef, FileChangeKind, JudgeEvidence, ProviderShare, ScopeProposal

Consumers reconciled

  • stella-mcp now sets the ToolSchema.read_only field the hardened core added — false, so external MCP tools are treated as mutating (the safe direction).
  • stella-cli's text renderer folds the 10 new events; the TUI model no-ops StepUsage/GoalVerdict (metering-only, already budgeted). A goal-verdict transcript row is a tracked follow-up.
  • Workspace deps add base64, crossterm, notify, ratatui, roxmltree, rusqlite, sha2, tempfile, and the tree-sitter parsers; tokio gains the sync feature.
  • Config/state paths reconciled .oxagen/.stella/; MCP client name, TUI title, doc examples de-oxagen'd.

Verification

Run locally against rustup run stable:

  • cargo fmt --check — clean
  • cargo clippy --workspace --all-targets -- -D warnings — clean
  • cargo check --workspace — clean
  • cargo test across stella-protocol + all 7 ported crates — all green (incl. stella-pipeline golden-replay and stella-protocol serialization roundtrips)

The duckdb-dependent crates (store, tools, stella-cli binary) were validated via check/clippy; store/tools are unchanged and stella-cli's change is render-only. CI's cargo test --workspace is the authoritative gate.

Follow-ups

  • Wire CLI verbs for context/graph/gen/fleet (crates land here; verbs are separate).
  • Optional goal-verdict transcript row in the TUI.

macanderson and others added 2 commits July 11, 2026 17:57
Brings context, fleet, graph, mcp, media, pipeline, and tui into the
stella-cli workspace (renamed oxagen-*->stella-*), completing the
ejection so stella-cli is the canonical, complete Stella. These crates
originated in the oxagen monorepo (Phase 3/4/5) and were never ejected;
the eject captured only the hardened core.

Unions the two diverged protocol streams into stella-protocol:
- AgentEvent gains FileChange, ContextRecall, ContextWrite, JudgeVerdict,
  ScopeReview, AskUser, MediaProgress, MediaComplete, Commit, Pr.
- StageKind gains Reflect.
- New types: MediaKind, MediaJobState, MediaArtifactRef, PrStatus,
  ContextFrameRef, FileChangeKind, JudgeEvidence, ProviderShare,
  ScopeProposal. The core's own StepUsage/GoalVerdict are preserved.

Consumers reconciled:
- stella-mcp sets the ToolSchema.read_only field the hardened core added
  (false = external MCP tools treated as mutating, the safe direction).
- stella-cli's text renderer folds the 10 new events; the TUI model
  no-ops StepUsage/GoalVerdict (metering-only, already budgeted) — a
  goal-verdict transcript row is a tracked follow-up.
- Workspace deps add base64, crossterm, notify, ratatui, roxmltree,
  rusqlite, sha2, tempfile, and the tree-sitter parsers; tokio gains the
  "sync" feature. Config/state paths reconciled .oxagen/ -> .stella/.

Verified: cargo fmt --check, clippy --workspace --all-targets -D warnings,
and cargo test across protocol + all 7 ported crates all green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174oZDGkzet8P2ZBLQeFc6d
@macanderson
macanderson merged commit 191ca85 into main Jul 12, 2026
@macanderson
macanderson deleted the feat/port-phase345-crates branch July 12, 2026 01:25
macanderson added a commit that referenced this pull request Jul 25, 2026
…#466) (#526)

Invariant #3 ("Zero telemetry egress by default") held only by construction:
the hub `telemetry` schema had no content columns and every egress encoder
enumerated its fields by hand, but nothing prevented a future column, struct
field, or OTLP attribute from quietly carrying content. A regression there is
a privacy incident, not a bug.

`stella-store/src/content_free.rs` makes it a red gate, in the shape of
`stella-model`'s provider-parity matrix — a declared table plus tests that
enforce it from both sides:

- HUB_TELEMETRY_COLUMNS is the reviewed column set of the hub `telemetry`
  table, compared against the live PRAGMA table_info. Adding a column fails
  the build until the allowlist is edited in the same PR, and a second check
  flags any allowlisted name that reads content-bearing, so the edit cannot
  be a one-line diff nobody read.
- ContentFreeEncoder is the encoder seam. The harness owns the poisoned
  fixtures, stamping prompts, project names, the workspace path, tool
  identities, the excluded call_role, and the local installation/store UUIDs
  with sentinels; audit_encoder then serializes and reports a leak, an
  unreviewed key, or a fixture the encoder never consumed. Format-agnostic by
  design: bytes are substring-searched and keys are supplied separately, so a
  protobuf OTLP encoder is guarded the same way.
- Two real encoders register today: the native `format = "stella"` drain
  payload and the enterprise operational spool (whose source row genuinely
  carries a prompt preview and a filesystem path, so the sentinels prove
  suppression rather than the absence of a field).
- DRAIN_FORMATS makes the unbuilt `otel` encoder a declared gap in source,
  not a silent omission: building it means moving the entry to Guarded and
  registering an encoder, or the drain-format test fails.

The fixtures are exhaustive struct literals on purpose: adding a field to
CloudTelemetryEvent or ExecutionRollupRow fails to compile here until an
author decides whether the new field is content — the compile-time half of
the same forcing function.

Witness (each injected, observed red, reverted):
- adding a `prompt_preview` column to the hub telemetry table fails
  hub_telemetry_schema_matches_the_reviewed_allowlist; allowlisting it anyway
  then fails no_allowlisted_hub_column_reads_as_content
- adding `call_role` to DrainRow fails every_registered_encoder_is_content_free
  with both a SentinelLeaked (naming the field) and an UnallowedKey
- adding a `workspace_root` attribute to StellaOperationalEventV1 fails the
  same test on the local-filesystem-path sentinel

The harness is also witness-tested against itself: three fake encoders prove
it catches a leak, an unreviewed key, and an encoder that ignores the fixture.

Deferred until #404/#427 land: no OTLP encoder exists to guard yet, and the
drain config's `format` discriminator has no enum to cross-check DRAIN_FORMATS
against.

Refs #466, #403, #404, #427

Signed-off-by: macanderson <mac@oxagen.sh>
macanderson added a commit that referenced this pull request Jul 27, 2026
…citations that survive an edit (#760)

## What this is

The P2 label covers 21 open issues. They are not one kind of thing, and
only
some of them are code. This PR lands the ones that are, and reports the
rest
rather than guessing at decisions that are not mine to make.

**Shipped here — closes #630 and #689, advances #561.**

## Closes #689 — the MCP tool-truncation signal now has a consumer

`stella-mcp` has recorded per-server tool truncation since #551. Nothing
read
it: `rg dropped_tool_count|over_advertising_servers` returned zero hits
outside
that crate. So a server advertising past `MAX_TOOLS_PER_SERVER` (256)
lost every
tool beyond the cap and **the operator was never told** — the model
silently had
less surface than the server offered, which is invisible because a
missing tool
looks exactly like a tool the model chose not to call.

Rendered in both surfaces the issue names:

- **Deck MCP tab** — per-row `· N dropped past cap` in `WARNING_BRIGHT`,
plus a
session total in the tab title, so an operator scanning tabs sees it
without
  opening this one.
- **Text mode** — a sibling notice next to, deliberately *not* inside,
the
  `failed_servers()` loop.

Kept strictly apart from `failed_servers()` throughout, which is the
entire
reason the signal exists separately: a truncated server is connected,
healthy,
and routing its kept tools. Calling it "unavailable" would be false, and
a test
asserts that word never appears. The cap is interpolated from the const
rather
than spelled out (per that const's own doc), and "at least" is
load-bearing —
discovery stops on the page where the cap bites, so every count is a
floor.

### A constraint worth flagging for review

`agent.rs`, `command_deck.rs` and `deck_ui.rs` were each sitting
**exactly** on
their file-size ratchet ceiling — zero headroom. Rather than raise three
baselines, the message-building moved into `mcp_cmd.rs`: **agent.rs −12
lines,
command_deck.rs −4**. That also fixed why none of this was testable —
`connect_mcp` prints through bare `eprintln!` with no injectable sink,
so
anything built inline there is unreachable from a test. Deck and text
mode now
share the same pure functions and cannot drift apart.

## Closes #630 — the invariants have one normative home

**The issue's premise was wrong and I did not build to it.** It says
AGENTS.md
states the invariants three times. AGENTS.md states them *once*. The
three full
copies were spread across `AGENTS.md`, `CONTRIBUTING.md` and `README.md`
— and
they had already diverged:

- CONTRIBUTING.md carried **seven of the eight**. #8 (provider feature
parity)
was missing outright, and #3 had lost the paragraph explaining it is
*enforced*
  by `content_free.rs` rather than merely asserted.
- README.md **renamed two** — "Typed errors, no panics" appeared as
"Fail loud,
  recover gracefully" — and dropped two others.

AGENTS.md is now the single source (it is the strict superset, and what
every
existing citation already points at). The other two reduce to pointers.

### The part that was not in the issue

**The numbering is load-bearing and was completely unguarded.** Eight
Rust doc
comments, two runtime error strings in `content_free.rs`, and four crate
READMEs
cite these by number (`AGENTS.md invariant #3`). Nothing checked them,
so
inserting an entry mid-list would have silently repointed every one at
the wrong
rule — invisibly, because a stale `#3` still renders as a plausible
`#3`.

`scripts/check-invariants.sh` enforces both halves. Both were
**negative-controlled**: a planted duplicate and a planted `#99` each
fail it.

## Advances #561 — cite by section, not by line number

Five markdown-to-markdown citations addressed their target by line
number.
**Every one had already drifted**, which is the argument in miniature:

| Citation | Claimed | Actually |
|---|---|---|
| `context-frame-spec.md:96` | portable Origin values | an ASCII diagram
— the values moved to `:123`, out of §3 and into §4 |
| `directive-schema.md:37` + "line 318" | the four-value list | each
slid 16 lines |
| `stella-defensible-position.md:590-592` | a trade-off note | drifted |
| `context-prs-spec.md:304` | the `observe` row | a table separator |

The issue named three; there were five. All re-anchored to sections, and
`check-doc-citations.sh` gained a second pass that fails the gate on any
`path.md:N` citation in tracked markdown. Also negative-controlled.

The prose rule is in `docs/README.md`, written with a `file.md:LINE`
placeholder
on purpose — a literal line number there would trip the guard the
paragraph
describes, the same footgun that file already documents for
`NORMATIVE-HOME`.

Separately: the README command index called itself "the full subcommand
surface"
and listed 25 of 27. `doctor` and `proposals` are added.

## Why #561 does not close

Its remaining item is a **production deploy job for the docs site**,
which needs
a `VERCEL_TOKEN` repository secret I cannot create. It is a decision
plus a
credential, not code. #546 tracks #561, so it stays open too.

## What I did not touch, and why

Per instruction, the **cloud telemetry epic is out of scope** (#403 and
its
children #404, #405, #406, #408, #427, #464, #465).

**Four issues are decisions, not defects** — #620, #618, #616, #612.
Together
they are ~100 rows that each explicitly ask the owner to *rule*:
sanction a
refactor, take the one coordinated protocol break, name a byte cap, draw
the
async boundary. Every row was deferred by an earlier PR for that same
reason.
Implementing them would mean inventing your policy, so they need a
sitting, not
a patch.

**The rest are multi-PR features**: #715, #443, #462, #442, #611.

## One thing worth acting on separately

**#715 is already shipped and still open.** PR #751 implemented Phase 4
and
merged, but titled itself `(#715)` instead of carrying a `Closes`
trailer — the
exact failure `AGENTS.md § Closing the issue on merge` calls a hard
rule. I have
not closed it for you, but it needs no work, only closing.

I also checked whether the remaining feature P2s had quietly shipped.
**They had
not** — #442, #443, #462 and the cloud children are genuinely unstarted
or
partial. No stale-close candidates there.

## Verification

- `cargo test --workspace` — **3,872 passed, 0 failed**, 62 suites,
cargo exit 0
- `make check` — exit 0 (fmt, clippy `-D warnings`, file-size,
action-pins, no-scratch, invariants)
- `make doc-citations` — exit 0
- Rebased onto `origin/main` @ `4e1f7d8d` and re-verified after;
trial-merge clean
- Both new guards negative-controlled in both directions
- The deck render witness **fails against the pre-change render**
(verified by
neutering it) — there was previously no test rendering the MCP tab at
all

Closes #630
Closes #689

---------

Signed-off-by: macanderson <mac@oxagen.sh>
vercel Bot added a commit that referenced this pull request Aug 1, 2026
…_check` test aborts at verify iteration 1 (before any revise turn runs) instead of at iteration 2 after the revise turn edits the witness, so it never exercises the mid-revise seal it documents.

This commit fixes the issue reported at stella-pipeline/src/pipeline/tests/witness_isolation.rs:1097

## Bug

The test `a_revise_turn_that_edits_the_witness_hard_fails_at_the_next_check` (witness_isolation.rs:1097) is intended to prove that the witness tamper seal holds **between** revise turns: iteration 1 sees the witness at its pinned identity (`w1`), the red test drives a revise turn that rewrites the witness, and iteration 2's tamper check catches the mutation (`w2`) and hard-fails the candidate *after* the revision.

The candidate's `artifact_identity()` is consumed three times against the candidate workspace, and `SeqRepoStatus::artifact_identity` serves them FIFO via `pop_front` (`tests.rs:188`):

1. **Graft re-pin** — `witness_stage.rs:328` (`candidate.repo_status.artifact_identity(path)`), pops entry #1.
2. **Verify iteration 1 tamper check** — `pipeline.rs:1920`, pops entry #2.
3. **Verify iteration 2 tamper check** — same line, pops entry #3.

The queue was declared with only two entries `[w1, w2]`, so the actual sequence was:

- graft pops `w1` → accepted,
- iteration-1 tamper check pops `w2` → mismatch → **ABORT at iteration 1**.

The revise turn (`text_result("adjusted the implementation")`) and the second `false` observation (`vec![false, false]`) were therefore never reached. The abort happened *before* any revision, making the test an exact behavioral duplicate of the sibling `a_tampered_witness_file_hard_fails_before_judge_evaluation` (line 922).

The assertions — aborted with the tamper reason, no `Judge` stage, no `adopt:` — all still passed, so the test stayed green while silently collapsing to a single-iteration scenario. A regression in mid-revise tamper detection (iteration 2) would not be caught.

The untracked snapshot queue `[[], [w1], [w2]]` (three entries) and the two-element `vec![false, false]` results were already sized for a two-iteration run; only the `with_artifact_identities` queue was short by one, which is what collapsed the scenario.

## Fix

Insert a second `Some(ArtifactIdentity { fingerprint: "w1", ... })` before the `w2` entry, making the queue `[w1, w1, w2]`:

- graft pops `w1` → accepted,
- iteration-1 tamper check pops `w1` → matches → red test drives the revise turn (consumes `"adjusted the implementation"`),
- iteration-2 tamper check pops `w2` → mismatch → abort **after** the revise turn.

The test now genuinely exercises the mid-revise seal. I also added a comment documenting the three consumption points so the queue length is not silently mis-sized again.

Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: macanderson <mac@macanderson.com>
macanderson added a commit that referenced this pull request Aug 3, 2026
…the regulated tier (#994) (#1239)

## What & why

#894 shipped the solo/owner-routed half of Context PRs. #1059 shipped
the regulated
tier's machinery — `stella context govern`, `stella context promote`,
the hash-chained
ledger, proposer/approver separation. What was never done is the part
that proves any
of it: **this repository had never published a context record.**

That was not an oversight anyone could have merged past, because it was
not possible.
`.gitignore` ignored `/.stella/*` with an exception only for `skills/`,
so
`.stella/rules/` — the one directory a Context PR's diff is allowed to
touch — could
not be committed at all. The `stella context validate` step #1059 added
to `ci.yml` is
guarded by `if [ -d .stella/rules ]`, a condition that had never once
been true. The
check has been passing since the day it merged by never running.

This is that first Context PR. It publishes two records, sets the
repository to
`regulated`, records the first enforcement grant in the ledger, and
thereby turns the
CI check from a vacuous green into one that actually re-verifies policy
on every PR.

Closes #994

## What is in the diff

| Path | What it is |
| --- | --- |
| `.gitignore` | Re-includes `/.stella/rules/`. Without this the payload
of every future Context PR is unstageable. `.stella/private/` stays
ignored, so evidence never follows a statement into Git (§10). |
| `.stella/rules/ctx.stella.dependency-license-allowlist.toml` |
**Blocking.** Transcribes AGENTS.md:110-115 — "If `cargo deny` rejects a
new dependency, drop the dependency — do not widen the allow-list in
`deny.toml` without a licensing decision." |
| `.stella/rules/ctx.stella.rust-toolchain-pin.toml` | **Advisory.**
Transcribes AGENTS.md:19-23 — the toolchain is pinned to a concrete
version, never the floating `stable` channel. Carries a probe that
genuinely falsifies it. |
| `.stella/rules/governance.toml` | `mode = "regulated"`, written by
`stella context govern regulated`. |
| `.stella/rules/promotions.jsonl` | Policy v1: the enforcement grant,
hash-chained, naming its approver and reason. |
| `stella-cli/src/rules.rs` | Bug fix + witness (below). |
| `stella-cli/src/context_cmd/propose.rs` | Two bug fixes + witness
(below). |
| `docs/context-pr.md`, `AGENTS.md` | §5.3 and the `.stella/` table now
describe a partly-tracked directory. |

Both records are decrees an owner already wrote in a tracked file, which
is the one
evidence class §7 lets open a Context PR immediately. Deliberately two,
not ten: every
`must` record rides the cached system prefix, so the set should earn its
tokens. They
are also a matched pair — one exercises the promotion ledger, one
exercises the
validator — so this PR demonstrates both halves of #994's acceptance
rather than
restating one twice.

## The proposal, as `stella context propose` generated it

```text
Preconditions
  ✓ repository: git@github.com:macanderson/stella
  ✓ schema-valid, single-concern diff
  ✓ scope: repository: this repo — deny.toml
  ✓ owners: @macanderson
  · evidence: no supporting evidence links on the record
  ✓ confidence: 100
  ✓ runtime effect stated
  ✓ no restricted data in the record
```

```markdown
## Proposed steering
A dependency that `cargo deny` rejects must be dropped rather than admitted by widening the license allow-list in `deny.toml`.

## Scope
repository: this repo — deny.toml

## Suggested enforcement
blocking (Tier 2 — matching tool calls are denied) — `enforcement.mode = "hard"`

## Evidence
- AGENTS.md lines 110–115
- extraction confidence 100

## Expected runtime effect
A call from any tool matching `*deny.toml` is denied at the tool boundary and the record's text is returned to the model. Nothing else is affected.
```

The guard names no `guard_tool` on purpose. `Edit` covers `edit_file`
and `apply_edits`
but not `write_file`, and a guard that closes the model's usual path
while leaving the
other one open is worse than no guard — it reads as `enforced` in
`stella context list`
and is not. Matching on path alone covers every tool that names a path,
and leaves
`Bash` (which carries no `path`) alone, because running `cargo deny` is
exactly what
this record wants to happen.

## About the approval

`promotions.jsonl` records `@macanderson` as the approver of policy v1.
**Merging this
PR is the approval act** — that is the whole design (§5.3: "the ledger —
not a private
per-machine approval — is what arms a project record's guard," and the
ledger is
committed precisely so the grant is reviewed through the same pull
request as the rule).
If the grant is not wanted, drop `promotions.jsonl` from the PR and the
record still
lands, as advisory; nothing else changes.

Proposer/approver separation is **off**, and that is a considered choice
rather than a
default left alone. With it on, the promotion is correctly refused:

```text
$ stella context govern regulated --separation --yes
$ stella context promote dependency-license-allowlist --to blocking --reason … --approver @macanderson
stella: proposer/approver separation is on, and ^dependency-license-allowlist's author
could not be established from the decision ledger — record the authorship first, or have
the author's machine publish the record through `stella context keep`
```

That is separation working: authorship is read from the **per-machine**
decision ledger
under `.stella/private/`, a hand-authored record has no author recorded
on any machine,
and an author that cannot be established fails closed. Turning it on for
a repository
whose records were transcribed rather than extracted would make every
promotion
impossible while looking like a policy choice. It becomes meaningful
once records arrive
through `stella context keep` on their authors' machines; that is the
natural follow-up.

## What publishing the first record found

Three real defects, none of which could surface before a repository
actually had records.

**1. `governance.toml` was parsed as a malformed record.** It lives in
`.stella/rules/`
by design, but the record loader globs `*.toml` there, so setting the
governance mode
made every `stella context list` and `validate` print:

```text
Not loaded, or not armed
  .stella/rules/governance.toml  TOML parse error at line 1, column 1
  missing field `schema`
```

The loader is right to treat an unparseable policy file as a governance
failure worth
surfacing loudly — so the fix reserves the filename rather than teaching
it to guess by
shape. A permanent false alarm is the one signal a genuinely broken
record file would
have had to compete with. Filtering in `FsRuleSource` fixes the context
commands and
live sessions together, since both go through that one `RuleSource`.
*Witness:*
`fs_source_skips_the_governance_file_that_lives_beside_the_records`
(2 files read vs 1 expected on old code).

**2. PR titles were cut at the first `.`, including one inside a
filename.** The title
generated for this very PR's record was:

```text
context: … must be dropped rather than admitted by widening the license allow-list in `deny
```

`summary()` split on every `.` to take the first clause. Records name
files and file
names have dots, so a period now only ends a sentence when whitespace or
end-of-input
follows it. A title cut mid-identifier is worse than a long one: it
reads as a different
claim rather than an abbreviated one.
*Witness:* `a_title_is_not_cut_at_a_dot_inside_a_file_name`.

**3. `A \`any tool\` call …`.** A guard with no `guard_tool` had its
tool name rendered
as the literal string `any tool`, which both misread as a name and
produced broken
grammar in the one paragraph a reviewer reads to understand blast
radius. Now "A call
from any tool …", which keeps the information that the guard is not
tool-scoped.
Covered by the same propose tests.

## The witness

- [x] This PR includes a witness test (fails on `main`, passes here)

Three, one per defect. Each was verified to fail before the fix — #1 by
flipping the
reserved list empty and re-running (`left: 2, right: 1`), #2 and #3 by
the truncated and
ungrammatical output quoted above, produced by the real command on the
real record.

The records themselves are verified end to end rather than by test:
`stella context list`
shows `^dependency-license-allowlist … enforced` only after the ledger
grant exists, and
showed the `ProjectNeedsApproval` refusal before it — which is the
acceptance criterion.

## The gate

- [x] `cargo fmt --check`
- [x] `cargo clippy --workspace --all-targets -- -D warnings`
- [x] `cargo test --workspace`
- [x] Docs updated where behavior changed (`docs/context-pr.md`
§5.3/§16, `AGENTS.md`)
- [x] `Closes #994` appears both above and as a commit trailer

Rebased onto `cbf75e33`+. **`main` is currently red on three checks, and
all three
reproduce here unchanged.** None is caused by this PR, and none is
touched by it:

| Check | Failure | Mine? |
| --- | --- | --- |
| `check-file-size` | 6 files over ceiling:
`harbor_adapter/__init__.py`, `harbor_adapter/tests/test_adapter.py`,
`fleet_cmd.rs`, `driver.rs`, `pipeline/tests.rs`, `event.rs` | No — none
is in this diff |
| `cargo doc -D warnings` | `unresolved link to
stella_protocol::completion::FinishReason` | No — `stella-protocol`
untouched |
| `cargo test --workspace` |
`verify::tests::both_judge_prompts_mark_the_diff_as_worker_authored_data`,
`stella-pipeline/src/verify/tests.rs:506` | No — `stella-pipeline`
untouched |

The last three are byte-identical to `main`'s own last CI run (same
test, same line,
same `373 passed; 1 failed`). The file-size baseline bump belongs to
whoever grew those
files, so it is deliberately **not** folded in here — running `make
file-size-update`
would bury six unrelated ceiling increases inside a Context PR.

Everything this PR does touch is green, checked by exit code rather than
by reading
output: `cargo fmt --all --check` (0), `cargo clippy --workspace
--all-targets -D
warnings` (0), `stella-cli` `context_cmd::` (31 passed) and `rules::`
(19 passed), and
`stella context validate` exactly as `ci.yml` invokes it (0).

## Ground-rule check

- [x] No I/O added to `stella-core`; no new deps
- [x] No new outbound network calls — both truth probes are
`file_contains` over tracked
files, the ungated kind; nothing reaches the network or runs a command

## Anything reviewers should know?

- **This changes agent behavior in this repo.** Both records are `must`,
so both are
injected into the cached system prefix for every task here, and the
first one denies
writes to `deny.toml` at the tool boundary. That is the intent — but it
is a real
behavior change, not just data, and it is the part to disagree with if
you are going
  to disagree with any of it.
- **`*deny.toml` would also match a hypothetical `mydeny.toml`.** The
matcher has one
wildcard and no alternation, and there is exactly one `deny.toml` in the
tree, so the
leading `*` (which covers the relative, `./`-prefixed, and absolute
forms a model
actually writes) costs no precision today. Worth remembering if a second
file ever
  ends in those characters.
- **A refused promotion writes a crash-diagnostics file.** The
separation refusal above
left a `.stella/private/crash-*.jsonl`. A fail-closed policy refusal is
a correct
outcome, not a crash, and filing it as one is misleading. Untouched here
— it is
outside this PR's concern and the path is gitignored — but worth its own
issue.
- **The `ci.yml` step is unchanged and now runs for the first time.** If
it is red on
  this PR, that is the check doing its job, not flake.

## Summary by Sourcery

Publish this repository’s first regulated context records and wire them
into CI by making `.stella/rules/` tracked and adding governance and
promotion metadata, while fixing context rule loading and proposal text
generation issues discovered in the process.

New Features:
- Add two repository-scoped context records that formalize the
dependency license allowlist policy and the Rust toolchain pin as
enforced steering for this repo.
- Introduce governance and promotion artifacts under `.stella/rules/` to
run this repository under the regulated governance tier with a
hash-chained enforcement ledger.

Bug Fixes:
- Prevent `governance.toml` from being misparsed as a malformed context
record by excluding reserved filenames from the filesystem rule source.
- Correct generated runtime-effect text so unscoped guards are described
as calls from any tool instead of a fictitious `any tool` tool name.
- Improve context record title generation so sentences are not truncated
at periods that appear inside filenames.

Enhancements:
- Document how this repository uses `.stella/rules/` under the regulated
tier, including how governance and promotions files interact with CI
validation.
- Clarify `.stella/rules/` usage in AGENTS.md as the tracked home of
this repo’s own steering policy and CI-verified promotion ledger.

Documentation:
- Expand context PR documentation to describe this repository’s
regulated setup, tracked `.stella/rules/` directory, and the behavior of
`governance.toml` and `promotions.jsonl`.
- Update AGENTS.md to document `.stella/rules/*.toml` as tracked context
records and explain the role of governance and promotions files in CI
validation.

Tests:
- Add tests ensuring the filesystem rule source skips governance files
beside records and that sentence-clause extraction for titles handles
periods in filenames and real clause breaks correctly.

---------

Co-authored-by: Stella Test <test@stella.local>
macanderson added a commit that referenced this pull request Aug 3, 2026
…e-row state strip (#1273)

## The problem

The Session tab's fixed bands over-subscribe the frame. Deck chrome
takes 11 of 24 rows on an 80×24 terminal, leaving 13 for content — and
the identity header (1), HUD (3), TASKS card (up to 10) and PROOF rail
(7) ask for 19–21 of them. The transcript gets the `Min(1)` leftovers,
so it was squeezed to nothing.

| Terminal | Content band | Fixed panels | Transcript |
|---|---|---|---|
| 80×24 | 13 | 19 | **−6 (over-subscribed)** |
| 120×40 | 29 | 19 | 10 |

Most of that spend bought nothing:

- **The proof rail was up on turns with nothing to prove.**
`band_height` gated on `is_empty()`, which goes false on
`ProofStep::Assurance` — the *first* step triage emits, before any work
exists. On the most common turn shape (triage waives the witness) that
was 7 rows, all turn, saying "nothing was owed here".
- **The approved scope was destroyed when its gate closed.** `model.rs`
set `pending_scope_review = None` and `ScopeProposal::steps` was never
copied anywhere; the scrollback record keeps a summary and two counts.
The steps a user had just consented to became unrecoverable.

## The shape

Three surfaces sized by what there is to say, replacing the two fixed
cards.

**A one-row state strip**, always present:

```
 ⌾ scope ✓5    ☑ 2/5 ▸ collapse tasks + proof in…    ⚖ waived · nothing to prove
```

Cells are budgeted and elided rather than cut at the frame edge — a
clipped cell reads as a *different* phrase, not a truncated one (`⚖
waived · nothing` is a complete and wrong statement).

**The PROOF rail promotes itself** only when `is_notable()` says it
carries news, and then only as tall as the rows that carry it:

```
┌ PROOF — what has been established about this work ───────────────────────────┐
│ warrant  required · 41 changed lines                                         │
│ witness  unavailable · no author independent of the worker                   │
│ verdict  pending                                                             │
└────────────────────────────────────────────────────── ⌃S full rail · 2 quiet ┘
```

The predicate is the same tone scan `border_style` already ran, so "the
frame is warning" and "the rail is up" cannot disagree. The verdict row
always survives the filter — a promoted rail without its conclusion
leaves "so was this accepted?" unanswered.

**A right-hand rail at ≥110 columns** spends columns (abundant) instead
of rows (scarce):

```
┌ transcript · 6 lines · following ────────────────────────────────┐┌ SCOPE ✓ ─────────────────────┐
│⏸ scope  Collapse the tasks and proof panels  ·  5 steps          ││ 5 steps · ~9 files           │
│                                                                  ││ est ~$1.40 · ⌃S opens        │
│── EXECUTE ───────────────────────────────────────────────────────│└──────────────────────────────┘
│                                                                  │┌ TASKS 2/5 ───────────────────┐
│☰  tasks  2/5  ·  collapse tasks + proof into a strip             ││ ▸ #3 collapse tasks + proof …│
│                                                                  ││… +2 more · +2 done           │
└───────────────── ↑ select · ⇞⇟ scroll · ⌃F find · ⌃S state ──────┘└──────────────────────────────┘
```

**`⌃S` opens a STATE overlay** with the approved plan's steps, the whole
board, and all five proof rows. It scrolls (`1–20 of 22 · ↑↓ scroll`),
because a 20-task board does not fit a 24-row terminal and an overlay
that dropped the overflow would rebuild the original complaint inside
its own fix. Dispatched *after* the modal SETTINGS/SKILLS editors so it
cannot steal their save chord.

`SessionModel::approved_scope` retains the proposal on the approval path
only — a turn that died at its gate records no approval, because an
abandoned proposal was never a plan. Cleared when a new turn opens.

**Cost on an 80×24 waived turn: 15 fixed rows → 1. Transcript 2 rows →
17.**

## Trade-off taken on purpose

`views/proof.rs` previously fixed the rail's height so the transcript
could never reflow mid-turn, with a property test pinning it at five
rows. Relevance-gating gives that up: the band now changes height a few
times per turn, each time the proof genuinely changed. A stable panel
nobody reads is worse than one that moves when it has news — but it is a
real invariant, traded knowingly. `ProofState::rows()` is still exactly
five and its property test is untouched; the filtering is a view
concern.

## Verification

- `make gate` green end to end (exit 0): no-scratch, action-pins,
cargo-install-pins, license-allowlist-parity, repro-wiring, shellcheck,
doc-citations, invariants, file-size, wire-schema, doc-warnings,
format-check, clippy `-D warnings`, full test suite.
- 799 lib tests + every integration suite, including the 42-geometry
`render_robustness` sweep extended to cover the new overlay.
- Rendered the real deck at 80×24 and 120×24 and read the frames. That
check caught three defects the tests had missed — the strip clipping at
80 columns, rail rows cut mid-word with no ellipsis, and the overlay
silently dropping rows past the fold. All three are fixed and now have
tests.
- Two golden snapshots re-blessed: `overlay_help_skills` (new `ctrl-s`
help row) and `tab_session` (transcript hint gains `⌃S state`).

`views/session.rs` shrinks 1608 → 1557 as the task card moves to the new
module; `deck_ui.rs` and `deck_render.rs` grow by the field, the binding
and the overlay dispatch — hence the file-size baseline bump.

## Summary by Sourcery

Introduce a unified work rail that summarizes scope, tasks, and proof
while gating and relocating supporting panels to preserve transcript
space, and add a STATE overlay for recalling approved plans and full
verification details.

New Features:
- Add a one-row state strip on the Session tab that summarizes approved
scope, task board progress, and proof status.
- Introduce a right-hand work rail on wide terminals that shows the
approved scope and a collapsed task board without consuming transcript
rows.
- Add a STATE overlay opened with ctrl-s that displays the approved
scope steps, the full task board, and the complete proof rail with
scrolling.
- Persist the approved scope plan for the duration of a turn so it can
be recalled after the scope-review gate closes.

Bug Fixes:
- Prevent approved scope plans from being dropped when the scope-review
gate closes, ensuring users can recall the steps they consented to.
- Avoid promoting a proof rail on turns that have nothing notable to
report, such as waived or clean proofs, eliminating empty panels that
consume space.

Enhancements:
- Rework the proof rail to be relevance-gated and sized to the notable
rows it carries, while keeping the full five-row rail available via the
STATE overlay.
- Replace the multi-row TASKS card with compact strip and rail
representations that summarize current work and counts instead of full
checklists on the main frame.
- Improve layout handling so the transcript retains more rows on small
terminals and proof/task surfaces use horizontal space or overlays
instead of fixed-height bands.
- Refine keyboard handling so the STATE overlay is modal, scrollable,
and does not conflict with ctrl-s save behavior inside modal editors.

Tests:
- Extend render robustness tests and add coverage for the new strip,
work rail, proof gating behavior, STATE overlay interactions, and
approved scope lifecycle.
- Update golden snapshots for skills help and the Session tab to account
for the new ctrl-s hint and state strip UI.

Co-authored-by: Stella Test <test@stella.local>
macanderson added a commit that referenced this pull request Aug 6, 2026
…ving (#1757)

## What & why

One repo-wide rename of the perpetual delivery loop: **`fullauto` →
`self-driving`**.

A literal search-and-replace does not compile — `self-driving.rs`, `let
self-driving = …` — so the rename is case-aware, one convention per
context:

| Context | Replacement |
|---|---|
| Rust identifiers, modules, `.rs` filenames | `self_driving` |
| Type prefix `Fullauto` | `SelfDriving` |
| Env vars `FULLAUTO_*` | `SELF_DRIVING_*` |
| CLI subcommand, paths, CSS/URLs, docs, prose | `self-driving` |

So `stella fullauto` is now `stella self-driving`, `/fullauto` is
`/self-driving`, `make fullauto-test` is `make self-driving-test`, and
`scripts/fullauto.sh` is `scripts/self-driving.sh`. 35 files, 19 paths
moved.

### The one place the old spelling deliberately survives

`self_driving_cmd::state::migrate_legacy_state`. `~/.fullauto/<slug>`
and `<stella home>/fullauto/<slug>` are **facts about bytes already on
users' disks, not names** — rewriting them points the migration at a
directory that has never existed and silently orphans a live ledger, and
losing the seen-set re-files every finding ever triaged. The function
now walks a list of legacy homes newest-first instead of one hardcoded
path, so the rename does not cost anyone their ledger.

Also renamed the observatory's `faRun` state key to `sdRun` — the `fa`
abbreviation was for `fullauto` and would have been left stale.

## The witness

- [x] No witness needed (pure refactor) — the rename is
behaviour-preserving by construction, and the existing suites are the
witness that it is: `tests/self_driving_cli.rs` (6), `stella-core`
(976), `stella-observatory` (72), and the shell harness (53/53) all
drive the renamed surface end to end.

The one **behavioural** change, `migrate_legacy_state` growing from one
legacy home to a list, is not covered by a witness test — called out
under *Nothing left behind* below.

## The gate

- [x] `cargo fmt --check` — clean
- [x] `cargo clippy --all-targets -- -D warnings` — clean on the three
affected crates
- [x] `cargo test` — `stella-cli` 1413/1414, `stella-core` 976/976,
`stella-observatory` 72/72
- [x] `make guards-fast` — all 20 guards pass, incl. `command-docs` (38
subcommands, each with a listed reference page), `god-files`,
`file-size`, `doc-links`
- [x] `make self-driving-test` — 53/53
- [x] Docs updated: `website/content/docs/commands/self-driving.mdx`,
`meta.json`, the commands index, `docs/spec/agent-monitor-protocol.md`,
`crates/stella-observatory/README.md`

### ~~This branch cannot go green until main is unbroken~~ — resolved,
see the comment below

**Update:** #1747, #1749 and #1752 landed; `origin/main` is merged in at
`b660408b` and everything below is re-verified against the merged tree —
`stella-cli` is now **1414/1414**. The original note is kept for the
record.

`main` did not compile at `ad26954c`, independently of this PR:

1. `cli.rs:674` references `fleet_verbs::FleetCmd` with no import —
`E0433`
2. `main.rs:806` is a **duplicate, unreachable `Command::Fullauto` match
arm** that drops the `.map_err` — `E0308`
3.
`paths::tests::nothing_else_in_this_crate_reads_a_home_out_of_the_environment`
fails on a `var_os("HOME")` inside a **comment** — byte-identical at
`HEAD~1`

All three are **#1747 / #1749**'s scope, and I have deliberately not
duplicated their fix here. I verified this branch by applying those two
fixes locally and *not* committing them; the numbers above are from that
run. Once either lands I will `gh pr update-branch`.

The remaining 1 test failure in the count above is #3.

## Two pre-existing reds this PR does fix

Both sat in files this branch already rewrites, and without them "does
the rename pass the tests" is unanswerable.

**The help index at 80 columns.** `self-driving` is 12 characters where
`fullauto` was 8, and the help index sizes its name column to the
longest listed command. That widened the column 11 → 12 and narrowed
every about line's budget 65 → 64 columns — and `stella dataset`'s about
was *exactly* 65. Shortened it and synced the site index, which
`the_docs_index_summaries_are_the_clis_own` holds byte-identical to the
CLI's own about string. **This one is genuinely caused by the rename.**

**The shell harness's stub `gh` never answered `--version`.** `demand()`
gates every count behind `gh_available()`, which shells out to `gh
--version` and reads the exit status. The stub had no arm for it, fell
through to its catch-all and exited 1 — so `plan` saw an empty queue and
skipped the clamp entirely. The three demand-rung cases were red for
that reason alone, and **neither the batch clamp nor the P0 rescue they
name was ever actually exercised**. Pre-existing, not caused by the
rename: the 0.6.120 release binary reproduces `QUEUE=0 BATCH=20`
exactly. With the arm, 53/53.

A third, `clippy::collapsible_if` in the run-doc tier guard, is also
pre-existing (byte-identical at `origin/main`, same line) but invisible
there because the crate does not compile, so clippy never reaches the
lint pass. Collapsed to an edition-2024 let-chain.

## A measurement note

My first `make self-driving-test` run reported 3 failures against
`/Users/…/target/release/stella` — a **0.6.120 pre-rename build on
`PATH`** that only knows `fullauto`. That was a measurement artifact,
and the numbers above are from re-running against this branch's own
build. Flagging it because the harness resolves `stella` from `PATH`, so
anyone with a stale shadow build will measure the wrong binary and not
be told.

## Nothing left behind

Filed as handoffs:

- **#1753** — `make self-driving-test` is not part of `gate`, which is
why it sat red on main
- **#1755** — the observatory scans only the new state root, so an
un-migrated loop is invisible until the CLI runs once
- **#1756** — `migrate_legacy_state`'s new multi-home walk has no
witness test
- **#1763** — `install-commands` leaves a stale, broken `/fullauto` in
`~/.claude/commands`: it writes the new tree but never removes the old
one, and those files still reference `scripts/fullauto.sh` and `stella
fullauto`, neither of which exists after this PR
- **#1761** — *not caused by this PR:* `main` fails the `file-size` gate
(`deck.rs` 1517 vs a 1510 ceiling, from #1742). Deliberately not papered
over with `make file-size-update` — `deck.rs` is a grandfathered god
file closed to growth

---------

Co-authored-by: Stella Test <test@stella.local>
macanderson added a commit that referenced this pull request Aug 7, 2026
… stacked behind one clippy error (#2000)

## What & why

`main` was red at `e0fbbe02` on **five distinct breaks stacked behind
one
another**. CI's log showed only the first, because both compile-tier
gates
report one unit at a time: clippy stops at the first *hard* error in a
crate,
and `cargo doc --workspace` stops at the first crate that fails to
document.
That is why this is the sixth consecutive unbreak PR — each one can only
reveal
the next layer. Root cause tracked in #1986 (`ci.yml` does not run on a
push to
`main`); evidence from this session added there.

### 1. `pipeline/scope_stage.rs:34` — dead `spend` local

```
error: variable does not need to be mutable
error: unused variable: `spend`
```

`plan_with_review` binds `let mut spend = Spend { budget, total };` and
never
reads it — the re-planning loop builds a fresh `Spend` by reborrowing on
each
iteration, which is the only construction the code uses. Dead since
#1971,
unmasked when #1985 cleared the `plan_stage` arg-count error above it.

### 2. `management_prompt/tests.rs` — `ModelCallRole::Research` listed
twice

`unreachable_patterns`. Kept the documented placement beside `Unknown`,
whose
comment explains why `Research` never reaches the chokepoint; dropped
the copy
appended after `Summarization`.

### 3. `verification_hardening.rs` — three items nothing constructs

`dead_code` ×3 on `SHELL_TOOL`, `shell_call_result`, `PassingShell`. The
child
`flip_halt_arming` module defines its own, which shadow the parent's
through
`use super::*` — a glob import loses to a local definition silently, so
this was
never a name clash, just quietly unreachable code.

The child's are the live pair *and* the newer one: a per-command
`call_id: format!("call-shell-{command}")` that `FlipHalt` correlates
on, versus
the parent's fixed `"call-shell"` which cannot distinguish two shell
calls. So
the parent's stale copies go. Its `mod` doc claimed the child existed in
order
to reach the parent's fakes — the pre-split rationale, now false — and
is
rewritten to point at the child's own doc, where the anti-clobber reason
for
colocating them lives (#1997).

**#2 and #3 are the same shape**: a merge landed the same addition
twice. Neither
side conflicts textually, so review saw nothing.

### 4. `stella-protocol/src/event.rs` — unresolved intra-doc link

`AgentEvent::Compaction::rewrites` documents itself with
`[`CompactionRewrite`]`,
but `event.rs` never imports the type (the field spells it
`crate::CompactionRewrite`
inline), so `broken_intra_doc_links` failed `doc-warnings`. A *different
gate
step* from #1#3, invisible while clippy was red. Fourth recurrence of
the shape
#1986 tracks.

### 5. `file-size` — two ceilings exceeded on `main`

`driver.rs` at 2572/2571 and `pipeline/tests.rs` at 2537/2536. **Neither
file is
touched by this branch**; both were grown on `main` by merges that did
not
regenerate the baseline. That mechanism is #2004.

Also regenerated `docs/wire/*` — the protocol types' doc comments *are*
that
contract, so break #4's fix mechanically changed the emitted
`description`.

## About the two raised ceilings

A raised ceiling is normally a defect, so this is stated plainly rather
than
buried: `make file-size-update` moved `driver.rs` and
`pipeline/tests.rs` up by
one line each, for growth **this branch did not author**, because the
growth has
already landed on `main` and reverting another PR's line is outside this
task.
The alternative was leaving the gate red.

The same regeneration also **tightens** `pipeline.rs` from 3451 to 3181
—
a 270-line shrink the baseline had not captured. This branch adds no
lines to
any god file. A maintainer who would rather see those two lines pushed
into
submodules should say so; that is their call, not mine.

## The witness

- [x] No witness test. Four of the five are dead code, a duplicate match
arm, and
a doc link — no runtime behavior exists to witness, and the compiler is
the
oracle. The fifth is a generated baseline. Per CONTRIBUTING's carve-out
for
changes with no behavior delta, here is how it was verified instead:
- `cargo clippy --workspace --all-targets -- -D warnings` — fails on
`main` at
    break #1, exits 0 here.
- `RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps` — fails
on
    `main` at break #4, exits 0 here.
  - `cargo fmt --all --check` — exits 0.
- `cargo test -p stella-pipeline` — 618 pass, 0 fail. That suite **did
not
compile at all** on `main` (break #3), so these tests were not running.
  - `scripts/check-file-size.sh` and `make wire-schema` — both exit 0.

Exit codes were read from cargo directly, not through a pipe: `cargo … |
tail`
reports *tail's* status, which is always 0, and cargo colorizes `error`
so a
plain `^error` grep matches nothing. Both produced a false green in this
  session before being corrected.

## The gate

- [x] `file-size` and `god-files` pass; baseline regenerated, never
hand-edited.
- [x] `wire-schema` passes; the diff is comment-only — no field added,
removed,
      renamed or re-tagged, and no optional field made required, so the
      additive-only contract holds.
- [x] No behavior change, no new flags, no new dependencies.

## Nothing left behind

- **#2013** (filed) — sharing `CARGO_TARGET_DIR` between worktrees
produces
compile errors naming symbols that do not exist. Hit during this work: a
phantom `E0004` for `AgentEvent::TurnParked`/`TurnWoken`, variants
present in
neither checkout, because a parallel job's build was linked in. Nearly
caused
  a wrong "fix".
- **#1986** — commented with the full five-layer breakdown as evidence
for
  fixing the trigger rather than the instances.
- **#2004** — owns the file-size baseline skew behind break #5.

Refs #1972, #1986, #1997, #2004, #2013
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.

1 participant