automation — Repository automation is scripted capabilities over typed Rust, not shell
A capability may be scripted (ADR 0069): a Rhai script inside the Rust executable orchestrates typed capabilities through the one registry, reaches every surface by the projections that already exist, runs in a sandbox with a declared and enforced effect ceiling, and shell shrinks by migration under a gate that refuses new shell without a declared exemption.
ACTIVE 3 of 6 issues done
- ready
- 2
- blocked
- 1
- active
- 0
- verify
- 0
- done
- 3
- cancelled
- 0
Problem
Repository automation lives in shell, and every recurring defect class this repository has recorded about itself has a shell instance: a BSD/GNU flag that behaves differently on the runner, a pipeline whose count is lost in a subshell, a gate that cannot read its own pattern and reports clean, a sort whose order depends on the locale. An audit of 2026-09-15 classified 223 units: scripts/ci is 44 files and 6,017 lines, the other scripts and bin 82 files and 14,860 lines, lib 45 files and 19,513 lines, and 216 shell test cases 30,872 lines. By disposition 97 are domain logic that belongs in core Rust, 32 orchestration, 34 glue, 3 dead, 14 generated and 43 platform adaptors. A user of the tool needs Bash to run any of it, and Perl reaches the tree through shasum on macOS.
Outcome
A capability may be scripted (ADR 0069): a Rhai script inside the Rust executable orchestrates typed capabilities through the one registry, reaches every surface by the projections that already exist, runs in a sandbox with a declared and enforced effect ceiling, and shell shrinks by migration under a gate that refuses new shell without a declared exemption.
Current state
No interpreter is embedded. Every orchestration is a shell script, the baseline-ratchet logic is copied into ten scripts/ci gates, nothing refuses a new shell file, and Perl is reachable through shasum.
Desired state
The sandboxed engine is part of the executable; a scripted capability is a kind discovered by the index and registered in the one registry, runnable through majordomus run and projected like any capability; nothing runs Perl; new shell under governed paths is refused without an exemption and the migration inventory is tracked; the first shell gates are scripted capabilities over typed Rust.
Scope
- apps/majordomus-cli
- share
- scripts
- lib
- bin
- .ai/repo
- docs
- test
- SECURITY.md
Out of scope
- Transliterating shell scripts into Rhai. Domain logic moves to typed Rust capabilities; a script only orchestrates capabilities
- A generic shell or exec capability, or any function that takes a command string
- Perl, Python or Node as a portable layer
Acceptance criteria
- A Rhai script runs only inside the sandbox engine, with eval disabled, no clock, bounded work and cancellation
- A scripted capability declared in the layer appears in the registry, capabilities list, OpenAPI and MCP with no edit anywhere else
- A script calling a capability whose effect exceeds its own declared effect is refused at run time
- No tracked file invokes Perl, held by a gate
- A new shell file under a governed path without an exemption entry is refused, and the migration inventory is machine-readable
Validation
- majordomus plan validate
- bin/majordomus-cli capabilities validate
- bin/majordomus-cli generate --check
- bash test/run.sh 383_a_scripted_capability_runs
Evidence required
- sandbox_engine_proved
- scripted_capability_projected_everywhere
- effect_ceiling_enforced
- no_perl
- new_shell_refused
Issues
Each one is an execution contract with its own acceptance criteria, validation command and required evidence. The wave column is the layer of the dependency graph it sits in.
| issue | status | wave | depends on | title |
|---|---|---|---|---|
| I1800 | DONE | 0 | — | The sandboxed engine a scripted capability runs in |
| I1801 | READY | 1 | I1800 | A scripted capability is a kind, discovered and registered, runnable and projected like any capability |
| I1802 | DONE | 0 | — | Nothing runs Perl |
| I1803 | DONE | 0 | — | New shell is refused without a declared exemption, and the migration inventory is tracked |
| I1804 | BLOCKED | 2 | I1801 | The first shell scripts become scripted capabilities over typed Rust |
| I3160 | READY | 0 | — | A behavioural case can be written as a Rust integration test over a disposable repository, and the continuity cases are the first ported |
The graph
flowchart LR
I1800["I1800<br/>The sandboxed engine a scripted capability runs in"]:::done
I1801["I1801<br/>A scripted capability is a kind, discovered and registered, runnable and projected like any capability"]:::ready
I1802["I1802<br/>Nothing runs Perl"]:::done
I1803["I1803<br/>New shell is refused without a declared exemption, and the migration inventory is tracked"]:::done
I1804["I1804<br/>The first shell scripts become scripted capabilities over typed Rust"]:::blocked
I3160["I3160<br/>A behavioural case can be written as a Rust integration test over a disposable repository, and the continuity cases are the first ported"]:::ready
I1800 --> I1801
I1801 --> I1804
classDef done stroke:#16a34a,fill:#dcfce7,color:#14532d,stroke-width:2px
classDef active stroke:#2563eb,fill:#eff6ff,color:#1e3a8a,stroke-width:2px
classDef verify stroke:#7c3aed,fill:#f5f3ff,color:#4c1d95,stroke-width:2px
classDef ready stroke:#0891b2,fill:#ecfeff,color:#164e63,stroke-width:2px
classDef planned stroke:#0891b2,fill:#ecfeff,color:#164e63,stroke-width:2px
classDef blocked stroke:#b45309,fill:#fffbeb,color:#78350f,stroke-width:2px
classDef cancelled stroke:#6b7280,fill:#f9fafb,color:#374151,stroke-width:2px
classDef superseded stroke:#6b7280,fill:#f9fafb,color:#374151,stroke-width:2pxLinked from this diagram (6)
- I1800 The sandboxed engine a scripted capability runs in
- I1801 A scripted capability is a kind, discovered and registered, runnable and projected like any capability
- I1802 Nothing runs Perl
- I1803 New shell is refused without a declared exemption, and the migration inventory is tracked
- I1804 The first shell scripts become scripted capabilities over typed Rust
- I3160 A behavioural case can be written as a Rust integration test over a disposable repository, and the continuity cases are the first ported
Canonical record: .ai/repo/project/milestones/automation.yaml. Read it back with majordomus plan show automation.