Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ It treats isolation and cryptographic provenance as co-equal pillars: work runs
## Status

The latest published release is `v0.1.0-alpha.7`, and the repository mainline already includes `v0.1.0-alpha.11` work in progress.
RuneCode remains pre-production: the signed, tag-driven release pipeline exists, but the shipped Go binaries are still scaffold-heavy and not feature-complete.
RuneCode remains pre-production: the signed, tag-driven release pipeline exists, and the current shipped surface is a local-first beta-hardening slice rather than the full long-term product.
Today that supported slice is the repo-scoped local lifecycle plus verified RuneContext project-substrate lifecycle, change/spec drafting, reviewed draft promote/apply, approved implementation, and inspectable audit/evidence surfaces.

## Why RuneCode

Expand Down Expand Up @@ -148,7 +149,7 @@ This quick path verifies signed checksums and the signed archive before install.
- Workflow/process planning schemas and fixtures, plus trusted Go compilation, persistence, and selection of immutable `RunPlan` authority that binds reviewed workflow selection, authoritative process DAG shape, executor bindings, deterministic gate definitions, dependency edges, and compiled runtime entries into one broker-owned execution contract
- A first-party RuneContext workflow pack with broker-owned routing for `change_draft`, `spec_draft`, `draft_promote_apply`, and `approved_change_implementation`, where drafting remains artifact-first, approved implementation binds one exact reviewed `implementation_input_set`, and shared-workspace execution stays at one active mutation-bearing run per authoritative repository root in `v0`
- Deterministic gate contracts and reporting families for gate planning, runner checkpoint/result reporting, gate checkpoint/result reporting, and gate evidence persistence, with stored evidence bound back to the active plan, workflow/process definition hashes, policy context hash, and validated project context digest
- A thin untrusted runner kernel foundation that loads broker-compiled `RunPlan` data from the shared schema bundle, persists plan-bound journal/snapshot durable state, replays approval waits and recovery state fail closed, schedules plan entries, and emits typed reports back to the broker
- A thin untrusted runner kernel foundation that loads broker-compiled `RunPlan` data from the shared schema bundle, persists plan-bound journal/snapshot durable state, replays approval waits and recovery state fail closed, schedules plan entries, emits typed reports back to the broker, and supports a plan-first product launch path that fails closed on missing broker transport or schema inputs while confining `--plan-file` and `--state-root` under a trusted `--plan-root`
- A narrow internal runner runtime seam for local checkpoint, wait, and resume mechanics without making runner-local state, third-party runtimes, or framework checkpoints authoritative
- MVP artifact data classes and an `ArtifactPolicy` schema family anchoring flow-matrix, approval-promotion, quota, and retention/GC controls
- A trusted local artifact store with immutable hash-addressed artifact persistence, broker-facing flow checks, quota enforcement, retention/GC, self-contained signed backup bundle export and fail-closed restore, approval records, persisted policy decisions, and audit event recording for artifact and approval actions
Expand Down Expand Up @@ -283,15 +284,16 @@ just ci

## Components

The Go binaries currently shipped by the release pipeline remain pre-production and intentionally do not expose the full production system surface.
The Go binaries currently shipped by the release pipeline remain pre-production and intentionally expose a local-first supported slice rather than the full production system surface.

Alongside that still-incremental surface, the repository already includes working foundations with:
- manifest-verified schemas and registries
- cross-language fixture validation
- canonicalization/hash golden tests
- runner trust-boundary static checks
- a trusted full-screen `runecode-tui` workbench with dashboard/chat/runs/approvals/Action Center/artifacts/audit/status/model-providers/git-setup/git-remote routes, shell-owned pane composition, session quick switching, a configurable `space`-default leader surface, bottom-left `:` command mode, one unified action graph for help/discovery/leader/command aliases, a visible quit action plus double-press `ctrl+c` emergency escape hatch, typed watch-backed live activity, chat execution progress derived from broker-owned session execution trigger plus turn-execution watch state, selection-mode copy ergonomics, broker-owned direct-credential provider setup with masked secret entry, and local-only layout/theme persistence
- a trusted full-screen `runecode-tui` workbench with dashboard/chat/runs/approvals/Action Center/artifacts/audit/status/model-providers and other admin routes, shell-owned pane composition, session quick switching, a configurable `space`-default leader surface, bottom-left `:` command mode, one unified action graph for help/discovery/leader/command aliases, a visible quit action plus double-press `ctrl+c` emergency escape hatch, typed watch-backed live activity, chat execution progress derived from broker-owned session execution trigger plus turn-execution watch state, selection-mode copy ergonomics, broker-owned direct-credential provider setup with masked secret entry, and local-only layout/theme persistence
- the TUI status route now surfaces broker-owned project-substrate posture plus adopt, init, and upgrade actions without making the TUI itself authoritative
- a broker-owned local-first RuneContext workflow slice covering project-substrate inspect/adopt/init/upgrade, `change_draft`, `spec_draft`, reviewed `draft_promote_apply`, and `approved_change_implementation`, with runs, artifacts, approvals, and audit/evidence surfaces linked back to the authoritative plan
- a trusted local artifact store and broker CLI for artifact put/get/head/list, flow checks, excerpt promotion and revocation, run-status updates, GC, and self-contained signed backup bundle export or fail-closed restore that preserves runtime evidence, lifecycle state, and related durable attestation state
- a trusted local audit ledger plus broker/auditd CLI surfaces for audit readiness, audit verification inspection, audit record inspection, audit record inclusion lookup, evidence snapshots and retention review, verifier-friendly evidence-bundle manifest generation, streaming bundle export, offline bundle verification, explicit audit anchoring over signed segment seals, and external-anchor evidence plus sidecar persistence used by verification and projections
- a broker local IPC API and CLI read/action surfaces for run list/detail, session list/detail/message append/execution trigger/session watch, approval list/detail/resolve, policy-backed artifact reads, audit timeline/record inspection, audit record inclusion lookup, audit evidence snapshot/retention review/bundle manifest/bundle export/offline verify, audit anchoring presence/action, audit verification/readiness, external-anchor mutation prepare/get/issue-execute-lease/execute, trusted-contract import, version inspection, structured log streaming, broker-projected backend posture get/change operations, project-substrate posture/get/adopt/init/upgrade operations with preview-digest-bound upgrade apply, provider profile list/get, provider setup session and secret-ingress flows, provider validation lifecycle operations, provider credential lease issuance, and broker-owned session-turn-execution watch streams for in-flight execution state
Expand Down
1 change: 1 addition & 0 deletions cmd/runecode-broker/main_cli_core_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ func TestHelpAndUnknownCommand(t *testing.T) {
"--audit-ledger-root path",
"--runtime-dir dir",
"--socket-name name",
"low-level start default: change_draft",
"audit-anchor-segment",
"audit-record-inclusion-get",
"audit-evidence-snapshot-get",
Expand Down
12 changes: 12 additions & 0 deletions cmd/runecode-broker/main_cli_local_api_adoption_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,18 @@ func handleSessionRPCStub(t *testing.T, wire localRPCRequest) (localRPCResponse,
case "session_send_message":
return mustOKLocalRPCResponse(t, brokerapi.SessionSendMessageResponse{SchemaID: "runecode.protocol.v0.SessionSendMessageResponse", SchemaVersion: "0.1.0", RequestID: "req-session-send", SessionID: "sess-1", Turn: brokerapi.SessionTranscriptTurn{SchemaID: "runecode.protocol.v0.SessionTranscriptTurn", SchemaVersion: "0.1.0", TurnID: "sess-1.turn.000001", SessionID: "sess-1", TurnIndex: 1, StartedAt: "2026-01-01T00:00:00Z", CompletedAt: "2026-01-01T00:00:00Z", Status: "completed", Messages: []brokerapi.SessionTranscriptMessage{{SchemaID: "runecode.protocol.v0.SessionTranscriptMessage", SchemaVersion: "0.1.0", MessageID: "sess-1.turn.000001.msg.000001", TurnID: "sess-1.turn.000001", SessionID: "sess-1", MessageIndex: 1, Role: "user", CreatedAt: "2026-01-01T00:00:00Z", ContentText: "hello", RelatedLinks: brokerapi.SessionTranscriptLinks{SchemaID: "runecode.protocol.v0.SessionTranscriptLinks", SchemaVersion: "0.1.0", RunIDs: []string{}, ApprovalIDs: []string{}, ArtifactDigests: []string{}, AuditRecordDigests: []string{}}}}}, Message: brokerapi.SessionTranscriptMessage{SchemaID: "runecode.protocol.v0.SessionTranscriptMessage", SchemaVersion: "0.1.0", MessageID: "sess-1.turn.000001.msg.000001", TurnID: "sess-1.turn.000001", SessionID: "sess-1", MessageIndex: 1, Role: "user", CreatedAt: "2026-01-01T00:00:00Z", ContentText: "hello", RelatedLinks: brokerapi.SessionTranscriptLinks{SchemaID: "runecode.protocol.v0.SessionTranscriptLinks", SchemaVersion: "0.1.0", RunIDs: []string{}, ApprovalIDs: []string{}, ArtifactDigests: []string{}, AuditRecordDigests: []string{}}}, EventType: "session_message_ack", StreamID: "session-sess-1", Seq: 1}), true
case "session_execution_trigger":
request := brokerapi.SessionExecutionTriggerRequest{}
if err := json.Unmarshal(wire.Request, &request); err != nil {
t.Fatalf("Unmarshal session_execution_trigger request error: %v", err)
}
if request.RequestedOperation == "start" {
if request.WorkflowRouting == nil {
t.Fatal("session_execution_trigger request missing workflow_routing for start")
}
if request.WorkflowRouting.WorkflowFamily != "runecontext" || request.WorkflowRouting.WorkflowOperation != "change_draft" {
t.Fatalf("session_execution_trigger default workflow_routing = %+v, want runecontext/change_draft", request.WorkflowRouting)
}
}
return mustOKLocalRPCResponse(t, brokerapi.SessionExecutionTriggerResponse{SchemaID: "runecode.protocol.v0.SessionExecutionTriggerResponse", SchemaVersion: "0.1.0", RequestID: "req-session-trigger", SessionID: "sess-1", TriggerID: "sess-1.trigger.000001", TriggerSource: "interactive_user", RequestedOperation: "start", UserMessageContentText: "hello", EventType: "session_execution_trigger_ack", StreamID: "session-sess-1", Seq: 1}), true
case "session_watch":
return mustOKLocalRPCResponse(t, []brokerapi.SessionWatchEvent{{SchemaID: "runecode.protocol.v0.SessionWatchEvent", SchemaVersion: "0.1.0", StreamID: "sw-1", RequestID: "req-session-watch", Seq: 1, EventType: "session_watch_snapshot", Session: &brokerapi.SessionSummary{SchemaID: "runecode.protocol.v0.SessionSummary", SchemaVersion: "0.1.0", Identity: brokerapi.SessionIdentity{SchemaID: "runecode.protocol.v0.SessionIdentity", SchemaVersion: "0.1.0", SessionID: "sess-1", WorkspaceID: "workspace-local", CreatedAt: "2026-01-01T00:00:00Z"}, UpdatedAt: "2026-01-01T00:00:00Z", Status: "active", LastActivityKind: "chat_message", TurnCount: 1, LinkedRunCount: 1, LinkedApprovalCount: 0, LinkedArtifactCount: 0, LinkedAuditEventCount: 0, HasIncompleteTurn: false}}, {SchemaID: "runecode.protocol.v0.SessionWatchEvent", SchemaVersion: "0.1.0", StreamID: "sw-1", RequestID: "req-session-watch", Seq: 2, EventType: "session_watch_terminal", Terminal: true, TerminalStatus: "completed"}}), true
Expand Down
58 changes: 58 additions & 0 deletions cmd/runecode-broker/main_cli_local_api_run_session_args_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package main

import (
"bytes"
"context"
"testing"

"github.com/runecode-ai/runecode/internal/brokerapi"
)

func TestRunAndSessionCommandsRejectPositionalArguments(t *testing.T) {
setBrokerServiceForTest(t)
stdout := &bytes.Buffer{}
stderr := &bytes.Buffer{}
originalDispatch := localRPCDispatch
localRPCDispatch = func(_ *brokerapi.Service, _ context.Context, wire localRPCRequest, _ brokerapi.RequestContext) localRPCResponse {
t.Fatalf("unexpected local rpc dispatch for %s", wire.Operation)
return localRPCResponse{}
}
t.Cleanup(func() { localRPCDispatch = originalDispatch })

for _, tt := range positionalArgRejectionCases() {
t.Run(tt.name, func(t *testing.T) {
stdout.Reset()
stderr.Reset()
err := run(tt.args, stdout, stderr)
if err == nil {
t.Fatalf("%s expected usage error for positional arguments", tt.name)
}
usageErr, ok := err.(*usageError)
if !ok {
t.Fatalf("%s error type = %T, want *usageError", tt.name, err)
}
if usageErr.Error() != tt.wantErr {
t.Fatalf("%s error = %q, want %q", tt.name, usageErr.Error(), tt.wantErr)
}
})
}
}

type positionalArgRejectionCase struct {
name string
args []string
wantErr string
}

func positionalArgRejectionCases() []positionalArgRejectionCase {
return []positionalArgRejectionCase{
{name: "run-list", args: []string{"run-list", "--limit", "1", "extra"}, wantErr: "run-list does not accept positional arguments"},
{name: "run-get", args: []string{"run-get", "--run-id", "run-1", "extra"}, wantErr: "run-get does not accept positional arguments"},
{name: "run-watch", args: []string{"run-watch", "--follow", "extra"}, wantErr: "run-watch does not accept positional arguments"},
{name: "session-list", args: []string{"session-list", "--limit", "1", "extra"}, wantErr: "session-list does not accept positional arguments"},
{name: "session-get", args: []string{"session-get", "--session-id", "sess-1", "extra"}, wantErr: "session-get does not accept positional arguments"},
{name: "session-send-message", args: []string{"session-send-message", "--session-id", "sess-1", "--content", "hello", "extra"}, wantErr: "session-send-message does not accept positional arguments"},
{name: "session-execution-trigger", args: []string{"session-execution-trigger", "--session-id", "sess-1", "--trigger-source", "interactive_user", "--requested-operation", "start", "--user-message", "hello", "extra"}, wantErr: "session-execution-trigger does not accept positional arguments"},
{name: "session-watch", args: []string{"session-watch", "--follow", "extra"}, wantErr: "session-watch does not accept positional arguments"},
}
}
2 changes: 1 addition & 1 deletion cmd/runecode-broker/main_help.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Commands:
session-list [--limit N]
session-get --session-id id
session-send-message --session-id id --content text [--role user|assistant|system|tool] [--idempotency-key key]
session-execution-trigger --session-id id [--turn-id id] [--trigger-source interactive_user|autonomous_background|resume_follow_up] [--requested-operation start|continue] [--workflow-family runecontext] [--workflow-operation change_draft|spec_draft|draft_promote_apply|approved_change_implementation] [--user-message text] [--idempotency-key key]
session-execution-trigger --session-id id [--turn-id id] [--trigger-source interactive_user|autonomous_background|resume_follow_up] [--requested-operation start|continue] [--workflow-family runecontext] [--workflow-operation change_draft|spec_draft|draft_promote_apply|approved_change_implementation] [--user-message text] [--idempotency-key key] (low-level start default: change_draft)
session-watch [--stream-id id] [--session-id id] [--workspace-id id] [--status active|completed|archived] [--last-activity-kind kind] [--follow] [--include-snapshot]
approval-list [--run-id id] [--status pending|approved|denied|expired|cancelled|superseded|consumed] [--limit N]
approval-get --approval-id sha256:...
Expand Down
86 changes: 86 additions & 0 deletions cmd/runecode-broker/main_local_api_audit_cmds_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package main

import (
"bytes"
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"

"github.com/runecode-ai/runecode/internal/brokerapi"
Expand All @@ -22,3 +27,84 @@ func TestAuditAnchorFailureReasonFallsBackToFailureMessage(t *testing.T) {
t.Fatalf("auditAnchorFailureReason() = %q, want external anchor confirmation is deferred", got)
}
}

func TestAuditEvidenceBundleCommandsSmokePath(t *testing.T) {
root := setBrokerServiceForTest(t)
if err := seedLedgerForBrokerCommandTest(filepath.Join(root, "audit-ledger")); err != nil {
t.Fatalf("seedLedgerForBrokerCommandTest returned error: %v", err)
}
stdout := &bytes.Buffer{}
stderr := &bytes.Buffer{}

if err := run([]string{"audit-evidence-snapshot-get"}, stdout, stderr); err != nil {
t.Fatalf("audit-evidence-snapshot-get returned error: %v", err)
}
snapshot := brokerapi.AuditEvidenceSnapshot{}
if err := json.Unmarshal(stdout.Bytes(), &snapshot); err != nil {
t.Fatalf("audit-evidence-snapshot-get output parse error: %v", err)
}
if len(snapshot.SegmentSealDigests) == 0 {
t.Fatal("snapshot.segment_seal_digests empty, want evidence snapshot material")
}

stdout.Reset()
requestPath, outPath := writeAuditEvidenceBundleExportFixtures(t)
if err := run([]string{"audit-evidence-bundle-export", "--request-file", requestPath, "--out", outPath}, stdout, stderr); err != nil {
t.Fatalf("audit-evidence-bundle-export returned error: %v", err)
}
exportResp := map[string]any{}
if err := json.Unmarshal(stdout.Bytes(), &exportResp); err != nil {
t.Fatalf("audit-evidence-bundle-export output parse error: %v", err)
}
if got := auditEvidenceBundleExportOutPath(t, exportResp); got != outPath {
t.Fatalf("export out path = %q, want %q", got, outPath)
}
if info, err := os.Stat(outPath); err != nil {
t.Fatalf("Stat(export out) returned error: %v", err)
} else if info.Size() == 0 {
t.Fatal("exported bundle size = 0, want tar archive bytes")
}

stdout.Reset()
if err := run([]string{"audit-evidence-bundle-offline-verify", "--bundle", outPath, "--archive-format", "tar"}, stdout, stderr); err != nil {
t.Fatalf("audit-evidence-bundle-offline-verify returned error: %v", err)
}
verification := brokerapi.AuditEvidenceBundleOfflineVerification{}
if err := json.Unmarshal(stdout.Bytes(), &verification); err != nil {
t.Fatalf("audit-evidence-bundle-offline-verify output parse error: %v", err)
}
if verification.BundleID == "" || verification.VerificationStatus == "" {
t.Fatalf("offline verification missing core fields: %+v", verification)
}
if len(verification.VerificationReports) == 0 {
t.Fatal("offline verification reports empty, want projected report posture")
}
}

func writeAuditEvidenceBundleExportFixtures(t *testing.T) (string, string) {
t.Helper()
tempRoot := canonicalTempDir(t)
requestPath := filepath.Join(tempRoot, "audit-evidence-bundle-export.request.json")
outPath := filepath.Join(tempRoot, "audit-evidence-bundle-export.tar")
writeJSONFixtureFile(t, requestPath, map[string]any{
"scope": map[string]any{"scope_kind": "run", "run_id": "run-1"},
"export_profile": "external_relying_party_minimal",
"created_by_tool": map[string]any{"tool_name": "runecode-broker", "tool_version": "0.0.0-dev"},
"disclosure_posture": map[string]any{"posture": "digest_metadata_only", "selective_disclosure_applied": true},
"archive_format": "tar",
})
return requestPath, outPath
}

func auditEvidenceBundleExportOutPath(t *testing.T, exportResp map[string]any) string {
t.Helper()
outValue, ok := exportResp["out"]
if !ok {
t.Fatalf("audit-evidence-bundle-export response missing out field: %#v", exportResp)
}
outString, ok := outValue.(string)
if !ok {
t.Fatalf("audit-evidence-bundle-export response out field has type %T, want string", outValue)
}
return strings.TrimSpace(outString)
}
Loading
Loading