Skip to content

Deliver Stave 1.1.0: persona evidence, execution order and release acceptance #38

Description

@ben-ranford

Outcome

Deliver a backwards-compatible Stave 1.1.0 that is easier to compose into real applications and stays responsive under load. This issue is the execution order and release acceptance checklist; the linked issues own implementation.

Baseline and evidence

Audited public root v1.0.0-rc.2 at 64b0d0d96954. The current v1.0.0 milestone remains open. Existing #2 and #3, including published immutable Lopper parity/rollback evidence, remain stable-release prerequisites. Do not present a prerelease comparison as a GA compatibility guarantee.

This is synthetic workflow testing and source analysis, not interviews or validated customer demand. Feature issues are explicit hypotheses; bug issues have isolated reproductions repeated twice. Timings and allocation observations describe this host, not portable release thresholds.

Synthetic personas and existing-surface results

| Persona/job | Existing surface exercised | Result and planning effect |

|---|---|---|

| Asha: integrate one Go app across people and automation | Public session, agent, replay and conformance package tests; composition source | Controls pass; explicit session-agent glue and one complete tutorial remain useful. |

| Noor: consume structured state and typed actions | Agent snapshot tests; semantic patch validation | Controls pass; preserve existing strict schemas while adding separately negotiated subscriptions/detail. |

| Rina: use keyboard/dialogs and multilingual output | Focus RepairFrom control, keymap default-dispatch control, raw wide-cell edge reproduction | Focus/routing controls pass; raw edge write has a reproduced ownership gap. Codec/lifecycle conveniences remain proposals. |

| Jon: operate large tables and trees | Three existing Table window/selection tests; relation-rich tree, surface-write and idle-wait probes | Table support already exists, so that feature was dropped. Measured repeated work produces four bounded refactor issues. |

| Eli: manage asynchronous/remote app lifecycle | Pre-cancelled LineDriver.Open repro, session effect-admission probe, replay tests | Startup defect reproduced; blocked effect admission motivates bounded architecture work. No new transport is promised. |

| Mina: ship a compatible library | Fuzz-discovery failure injection, public-API declaration mutation and documented schema probe | Three checking/docs defects reproduced twice; baseline and post-publication verification fill distinct gaps. |

| Erin: support macOS/Windows consumers | Existing Linux fixture/cross-compilation workflow inspected | Native execution is unverified by that job; proposed bounded hosted-native smoke matrix, not a claimed cross-platform bug. |

Focused controls and their actual results:

Existing-surface persona probes

These are executed controls over the public package surface at
64b0d0d96954a64d51d3c520211f2164fdacab18. They exercise current behavior;
they do not establish customer demand.

Large table window

Command:

go test -v ./primitive -run 'TestTable(ModalAndMasterDetailContracts|RejectsInvalidWindowAndSelectionContracts|ExplicitStates)$' -count=1

Result: all three tests passed. primitive.Table already accepts Offset,
Limit, and Total, validates the window and selection state, and emits
stable bounded-table semantics (primitive/collections.go:21-180). This
removes the proposed generic table-window feature from the candidate set.

Modal focus lifecycle

Command:

go test -v ./focus -run 'TestGraphRepairFromChoosesNearestSurvivingNeighbor$' -count=1

Result: passed. focus.Graph supports PushScope, PopScope, and
RepairFrom (focus/focus.go:130-207), including repair when the previous
target no longer survives. The remaining candidate is a narrow lifecycle
wrapper, not new focus mechanics.

Semantic patch boundary

Command:

go test -v ./semantic -run 'TestTreeValidationAndPatch$' -count=1

Result: passed. The current patch contract is limited to Added, Removed,
and GenerationChanged (semantic/semantic.go:303-359). Any changed-field
detail must be separately versioned and negotiated so strict existing decoders
retain the current schema.

Keymap mapping serialization

Command:

go test -v ./keymap -run 'TestDispatcherKeepsAllDefaultBindingsMapped$' -count=1

Result: passed. keymap.Mapping has JSON-tagged fields and Map.Inventory()
is deterministic (keymap/keymap.go:59-205), while Map stores its profile
and mappings privately. The candidate is therefore a strict import/export
codec around existing validation, not a new dispatcher.

Execution order

  1. Resolve release prerequisites in v1.0.0 separately. Implement the reproduced fixes and the explicit configuration/effect-admission decisions first.

  2. Preserve compatibility while doing independent measured refactors. Fix raw wide-cell edge semantics before sharing text placement mechanics.

  3. Build the opt-in session-agent bridge from the ownership decision; build subscriptions on publication notification; keep patch-detail negotiation independent.

  4. Deliver other additive features independently, then the shared application tutorial. Run native, parser, API-baseline and anonymous publication checks.

  5. Review all acceptance evidence and publish only after the existing stable-release gates pass. A blocked prerequisite is a blocker, not permission to waive a check.

Milestone issue checklist

Release acceptance

  • Every included implementation issue meets its bounded acceptance criteria; any scope removal is explicitly recorded.

  • Public v1 APIs, schemas, canonical hashes, stable IDs, stale-target safety, confirmation/redaction and resource-limit contracts remain compatible.

  • Required CI, race/targeted regression, conformance, performance, vulnerability and workflow gates pass on the exact final revision.

  • Independent code and architecture review confirms the new ownership, admission, wait and protocol extension boundaries.

  • Stable v1.0.0/Lopper evidence prerequisites are satisfied; real released consumers pass the compatibility matrix.

  • Immutable tag, release assets and anonymous Go-proxy/checksum-database consumption are verified; documentation commands are executable.

Reproduction appendix

Use a disposable checkout of the baseline above. The following source contains only local fixtures; it makes no network calls and changes no tracked implementation. Save as .omx/planning/architecture/analyze.go, then run go run ./.omx/planning/architecture/analyze.go. Repeat to inspect ranges; do not assert fixed timing ratios as deterministic tests.

Local architecture/performance probe source
// Command analyze emits stable evidence for the Stave 1.1.0 architecture plan.
// It is a planning-only diagnostic and intentionally does not modify Stave.
package main

import (
	"context"
	"fmt"
	"runtime"
	"sync/atomic"
	"time"

	"github.com/ben-ranford/stave/effect"
	"github.com/ben-ranford/stave/event"
	"github.com/ben-ranford/stave/layout"
	"github.com/ben-ranford/stave/semantic"
	"github.com/ben-ranford/stave/session"
	"github.com/ben-ranford/stave/state"
	"github.com/ben-ranford/stave/surface"
)

func main() {
	surfaceEvidence()
	semanticEvidence()
	sessionEffectEvidence()
	sessionWaitEvidence()
}

func surfaceEvidence() {
	const repeats = 25
	style := surface.ResolvedStyle{Foreground: "#ffffff"}
	clip := layout.Rect{Width: 120, Height: 40}
	measure := func(fn func()) uint64 {
		runtime.GC()
		var before, after runtime.MemStats
		runtime.ReadMemStats(&before)
		for i := 0; i < repeats; i++ {
			fn()
		}
		runtime.ReadMemStats(&after)
		return (after.TotalAlloc - before.TotalAlloc) / repeats
	}
	immutable := measure(func() {
		base := surface.New(120, 40)
		_, _ = base.WithText(0, 0, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", style, "", "", 1, clip)
	})
	builder := measure(func() {
		b, _ := surface.NewBuilder(120, 40, 4800)
		_ = b.WithText(0, 0, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", style, "", "", 1, clip)
		_ = b.Surface()
	})
	fmt.Printf("surface_immutable_alloc_bytes=%d\n", immutable)
	fmt.Printf("surface_builder_alloc_bytes=%d\n", builder)
	fmt.Printf("surface_alloc_ratio=%.1f\n", float64(immutable)/float64(builder))
}

func semanticEvidence() {
	measure := func(nodes int) time.Duration {
		ids := make([]semantic.NodeID, nodes)
		for i := range ids {
			ids[i], _ = semantic.NodeIDFor(semantic.NodeKey{AppNamespace: "plan", View: "semantic", Kind: "row", Entity: fmt.Sprint(i), Slot: "main"})
		}
		children := make([]semantic.Node, 0, nodes-1)
		for i := 1; i < nodes; i++ {
			n, _ := semantic.NewNode(semantic.NodeSpec{ID: ids[i], Role: "text", Name: "row", Relations: []semantic.Relation{{Kind: "describedby", Target: ids[nodes-1]}}})
			children = append(children, n)
		}
		root, _ := semantic.NewNode(semantic.NodeSpec{ID: ids[0], Role: "application", Name: "root", Children: children})
		tree, _ := semantic.NewTree(1, root)
		start := time.Now()
		_ = tree.Validate()
		return time.Since(start)
	}
	first := measure(1000)
	second := measure(2000)
	fmt.Printf("semantic_validate_1000_us=%d\n", first.Microseconds())
	fmt.Printf("semantic_validate_2000_us=%d\n", second.Microseconds())
	fmt.Printf("semantic_validate_scale_ratio=%.1f\n", float64(second)/float64(first))
}

func sessionEffectEvidence() {
	release := make(chan struct{})
	started := make(chan struct{})
	s, err := session.New(context.Background(), session.Options[int]{
		QueueCapacity:     8,
		MaxActiveBatches:  1,
		EffectParallelism: 1,
		EffectPorts: map[string]effect.Port{"block": effect.PortFunc(func(context.Context, effect.Call) (any, error) {
			select {
			case <-started:
			default:
				close(started)
			}
			<-release
			return nil, nil
		})},
		Reduce: func(_ context.Context, current int, ev event.Event) (int, []effect.Request, error) {
			if ev.Kind == event.Key {
				return current + 1, []effect.Request{{Spec: effect.Spec{Kind: "block"}}}, nil
			}
			return current, nil, nil
		},
		View: func(_ context.Context, current int) (session.ViewResult, error) {
			id, _ := semantic.NodeIDFor(semantic.NodeKey{AppNamespace: "plan", View: "session", Kind: "root", Entity: fmt.Sprint(current), Slot: "main"})
			n, _ := semantic.NewNode(semantic.NodeSpec{ID: id, Role: "application", Name: "root"})
			t, _ := semantic.NewTree(uint64(current+1), n)
			return session.ViewResult{Tree: t}, nil
		},
	})
	if err != nil {
		panic(err)
	}
	defer s.Close()
	key, _ := event.New(event.Key, event.KeyPayload{Key: "enter"})
	_ = s.Send(key)
	<-started
	_ = s.Send(key)
	_ = s.Send(key)
	time.Sleep(20 * time.Millisecond)
	snap, _ := s.Snapshot()
	fmt.Printf("effect_admission_active_batches=1\n")
	fmt.Printf("effect_admission_sequence_after_third_event=%d\n", snap.Sequence)
	fmt.Printf("effect_admission_stalled_events=%d\n", 3-int(snap.Sequence))
	close(release)
}

func sessionWaitEvidence() {
	var clones atomic.Int64
	s, err := session.New(context.Background(), session.Options[int]{
		ModelPolicy: state.ModelPolicy[int]{Clone: func(value int) (int, error) { clones.Add(1); return value, nil }},
		Reduce: func(_ context.Context, current int, _ event.Event) (int, []effect.Request, error) {
			return current, nil, nil
		},
		View: planSessionView,
	})
	if err != nil {
		panic(err)
	}
	defer s.Close()
	clones.Store(0)
	ctx, cancel := context.WithTimeout(context.Background(), 12*time.Millisecond)
	defer cancel()
	_ = s.Wait(ctx, func(state.State[int]) bool { return false })
	fmt.Printf("session_wait_window_ms=12\n")
	fmt.Printf("session_wait_model_clones=%d\n", clones.Load())
}

func planSessionView(_ context.Context, current int) (session.ViewResult, error) {
	id, _ := semantic.NodeIDFor(semantic.NodeKey{AppNamespace: "plan", View: "wait", Kind: "root", Entity: fmt.Sprint(current), Slot: "main"})
	n, _ := semantic.NewNode(semantic.NodeSpec{ID: id, Role: "application", Name: "root"})
	t, _ := semantic.NewTree(uint64(current+1), n)
	return session.ViewResult{Tree: t}, nil
}

For the structural probe, save the following as .omx/planning/architecture/analyze_static.py and run python3 .omx/planning/architecture/analyze_static.py.

Structural source-analysis probe
#!/usr/bin/env python3
"""Stable source metrics for the Stave 1.1.0 planning pass."""

from pathlib import Path


ROOT = Path(__file__).resolve().parents[3]


def count(path: str, needle: str) -> int:
    return (ROOT / path).read_text(encoding="utf-8").count(needle)


def main() -> None:
    stave = (ROOT / "stave.go").read_text(encoding="utf-8")
    print(f"program_runtime_config_wires={stave.count('cfg.Runtime.InputQueue')}")
    print(f"program_protocol_config_wires={stave.count('cfg.Protocol.')}")
    print(f"program_security_config_wires={stave.count('cfg.Security.')}")
    print("agent_limit_option_kinds=3")
    print(f"agent_limit_defaults={sum(count('runtime/agent/server.go', needle) for needle in ('opts.MaxMessageBytes =', 'opts.MaxOutputBytes =', 'opts.MaxTreeNodes ='))}")
    render = (ROOT / "render/render.go").read_text(encoding="utf-8")
    render_body = render[render.index('func Render('):render.index('\ntype renderNode')]
    print(f"render_unconditional_products={sum(render_body.count(call) for call in ('surface.Diff(', 'renderPlain(', 'renderMachine(', 'writer.Render('))}")
    lopper = (ROOT / "cmd/lopper/main.go").read_text(encoding="utf-8")
    print(f"lopper_terminal_result_reads={lopper.count('result.Terminal')}")
    clone_files = ('event/event.go', 'replay/replay.go', 'state/state.go', 'effect/effect.go')
    clone_decoders = sum(1 for path in clone_files if count(path, 'json.NewDecoder(bytes.NewReader') > 0)
    print(f"canonical_clone_boundary_files={len(clone_files)}")
    print(f"canonical_clone_json_decoder_files={clone_decoders}")


if __name__ == '__main__':
    main()

Scope boundaries

No library implementation was changed while planning. Stave remains a library dependency; Homebrew packages Lopper separately. Do not change repository visibility, publish adapter modules, create a new transport, raise the Go floor or add a root dependency as an implied part of this milestone. Breaking proposals require redesign or a future major-version plan.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    priority:p1Foundational or high-impact milestone workrigor-upliftRepository hygiene, CI, release, or governance hardeningtype:planningMilestone execution plan and release acceptance

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions