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
2 changes: 2 additions & 0 deletions configs/promotion-allowlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ scripts/check_release_artifact_matrix.sh
scripts/check_release_changelog.sh
scripts/release_checklist.sh
scripts/release_readiness/readiness.sh
scripts/release_readiness/rr002_cli_smoke.sh
scripts/release_readiness/lib/common.sh
scripts/release_readiness/lib/report.sh
scripts/release_readiness/test_smoke.sh
scripts/release_readiness/test_rr002_smoke.sh
Makefile
go.mod
go.sum
Expand Down
5 changes: 4 additions & 1 deletion configs/promotion-manifest.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Auto-generated promotion manifest.
# Source allowlist: configs/promotion-allowlist.txt
# Regenerate with: bash scripts/generate_promotion_manifest.sh
# Generated at (UTC): 2026-05-16T21:41:07Z
# Generated at (UTC): 2026-05-17T01:49:33Z

.github/workflows/release.yml
.github/workflows/universal-validator.yml
Expand Down Expand Up @@ -558,6 +558,7 @@ pkg/profile/profile.go
pkg/profile/profile_test.go
pkg/profile/refs.go
pkg/profile/refs_test.go
pkg/profile/release_readiness_matrix_test.go
pkg/profile/util.go
pkg/providers/auth_resolver.go
pkg/providers/auth_resolver_test.go
Expand Down Expand Up @@ -905,6 +906,8 @@ scripts/release_checklist.sh
scripts/release_readiness/lib/common.sh
scripts/release_readiness/lib/report.sh
scripts/release_readiness/readiness.sh
scripts/release_readiness/rr002_cli_smoke.sh
scripts/release_readiness/test_rr002_smoke.sh
scripts/release_readiness/test_smoke.sh
scripts/setup_wizard.sh
scripts/write_launcher.sh
61 changes: 54 additions & 7 deletions docs/release/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# Release Readiness

RR-001 adds local report-only release-readiness tooling. It gathers release facts,
runs validation commands, and writes a human-readable markdown report.
runs validation commands, and writes a human-readable markdown report. RR-002
adds local CLI smoke and config/profile matrix checks for safe operator-facing
entrypoints.

Run from any directory:

Expand All @@ -15,6 +17,18 @@ Reports are written under:
.local/release-readiness/reports/
```

Run RR-002 directly from any directory:

```bash
bash scripts/release_readiness/rr002_cli_smoke.sh
```

RR-002 reports are written under:

```text
.local/release-readiness/rr002/
```

## What RR-001 Checks

- Current branch, commit, and working tree status
Expand Down Expand Up @@ -44,16 +58,49 @@ Reports are written under:
RR-001 normally reports `NEEDS_RR_SUITE` unless a concrete blocker is found.
Reports also include `status: REPORT_ONLY`.

## What RR-002 Checks

- Safe `gorkbot` and `gorkweb` help startup modes with bounded timeouts.
- Unsupported version flags as skipped checks instead of failures.
- Unsafe bare `version` arguments as deferred because no safe subcommand exists.
- Conservative profile defaults across beginner, standard, power-user, expert,
lab, enterprise, custom, and unknown profiles.
- Trace, harness, release authority, automatic promotion, policy absence,
vector candidate-only, and vector truth-invariant postures through local tests.
- Report generation and temporary runtime output confined to `.local/`.
- Tracked-file mutation avoidance.

## What RR-002 Does Not Do

- It does not call live providers.
- It does not make network calls.
- It does not execute app tools.
- It does not create, move, or delete tags.
- It does not run release workflows.
- It does not publish releases.
- It does not claim final release readiness.
- It does not cover RR-003+ smoke layers.

The main wrapper runs RR-002 by default:

```bash
bash scripts/release_readiness/readiness.sh
```

If RR-002 passes or is incomplete without blockers, the wrapper still reports
`NEEDS_RR_SUITE` because RR-003 and later release-readiness layers remain
deferred. If RR-002 finds a concrete blocker, the wrapper reports `BLOCKED`.

## Private Scanner Handling

The wrapper only reports whether the private scanner path exists. Scanner
execution remains a separate private preflight for RR-001.

## Future RR Track

- RR-002 CLI smoke + config matrix
- RR-003 VAR spine fixture smoke
- RR-004 policy absence + statelock/paradox smoke
- RR-005 vector/RAG/engram preservation smoke
- RR-006 TUI/operator/session scripted smoke
- RR-007 final release report generator
- RR-002 CLI smoke + config matrix: implemented as local report-only smoke.
- RR-003 VAR spine fixture smoke: deferred.
- RR-004 policy absence + statelock/paradox smoke: deferred.
- RR-005 vector/RAG/engram preservation smoke: deferred.
- RR-006 TUI/operator/session scripted smoke: deferred.
- RR-007 final release report generator: deferred.
115 changes: 115 additions & 0 deletions pkg/profile/release_readiness_matrix_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package profile

import (
"testing"

"github.com/velariumai/gorkbot/pkg/evidence"
"github.com/velariumai/gorkbot/pkg/harness"
"github.com/velariumai/gorkbot/pkg/trace"
)

func TestReleaseReadinessProfileMatrixConservativeDefaults(t *testing.T) {
tests := []struct {
name string
profile Profile
traceModes []trace.Mode
harnessMode harness.Mode
}{
{name: "beginner", profile: ProfileBeginner, traceModes: []trace.Mode{trace.ModeMinimal}, harnessMode: harness.ModeAudit},
{name: "standard", profile: ProfileStandard, traceModes: []trace.Mode{trace.ModeAudit}, harnessMode: harness.ModeAudit},
{name: "power_user", profile: ProfilePowerUser, traceModes: []trace.Mode{trace.ModeAudit}, harnessMode: harness.ModeAudit},
{name: "expert", profile: ProfileExpert, traceModes: []trace.Mode{trace.ModeDebug}, harnessMode: harness.ModeAudit},
{name: "lab", profile: ProfileLab, traceModes: []trace.Mode{trace.ModeReplay}, harnessMode: harness.ModeAudit},
{name: "enterprise", profile: ProfileEnterprise, traceModes: []trace.Mode{trace.ModeAudit}, harnessMode: harness.ModeAudit},
{name: "custom", profile: ProfileCustom, traceModes: []trace.Mode{trace.ModeAudit}, harnessMode: harness.ModeAudit},
{name: "unknown", profile: ProfileUnknown, traceModes: []trace.Mode{trace.ModeMinimal}, harnessMode: harness.ModeAudit},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := DefaultConfig(tt.profile)
if !containsTraceMode(tt.traceModes, cfg.TraceMode) {
t.Fatalf("unexpected trace mode for %s: got %q want one of %v", tt.name, cfg.TraceMode, tt.traceModes)
}
if cfg.HarnessMode != tt.harnessMode {
t.Fatalf("unexpected harness mode for %s: got %q want %q", tt.name, cfg.HarnessMode, tt.harnessMode)
}
if !cfg.Evidence.VectorCandidateOnly {
t.Fatalf("profile %s must keep vector retrieval candidate-only", tt.name)
}
if tt.profile != ProfileExpert && tt.profile != ProfileLab && tt.profile != ProfileCustom &&
cfg.Authority.ReleaseAuthority == AuthorityAllowConfigured {
t.Fatalf("profile %s must not make release authority advanced by default", tt.name)
}
if cfg.Automation.ReleaseMode != AutomationDisabled && cfg.Automation.ReleaseMode != AutomationApprovalRequired && cfg.Automation.ReleaseMode != AutomationAllowConfigured {
t.Fatalf("profile %s has invalid release automation posture: %q", tt.name, cfg.Automation.ReleaseMode)
}
})
}
}

func TestReleaseReadinessPolicyAbsenceAndExplicitAuthority(t *testing.T) {
cfg := DefaultConfig(ProfileExpert)
cfg.ConfiguredCapabilities = nil

release := EvaluateCapability(cfg, CapabilityReleasePublish)
if release.Decision == evidence.DecisionAuditOnly || release.Decision == evidence.DecisionAllowLowRisk {
t.Fatalf("release without explicit configured capability must not be allowed, got %+v", release)
}
if release.ReasonCode != "release_authority_explicit_required" {
t.Fatalf("release must receipt explicit authority requirement, got %+v", release)
}

network := EvaluateCapability(cfg, CapabilityNetworkEgress)
if network.Decision == evidence.DecisionAuditOnly {
t.Fatalf("policy absence must not authorize network egress, got %+v", network)
}

vector := EvaluateCapability(cfg, CapabilityVectorRetrieve)
if vector.Metadata["vector_role"] != "candidate_only" {
t.Fatalf("vector retrieval must be candidate-only, got %+v", vector)
}

assertTruth := EvaluateCapability(cfg, CapabilityVectorAssertTruth)
if assertTruth.Authority != evidence.AuthorityHardInvariant || assertTruth.Decision != evidence.DecisionDenySensitive {
t.Fatalf("vector assert truth must remain hard-denied, got %+v", assertTruth)
}
}

func TestReleaseReadinessTraceAndHarnessModeMatrix(t *testing.T) {
traceModes := []trace.Mode{
trace.ModeOff,
trace.ModeMinimal,
trace.ModeAudit,
trace.ModeDebug,
trace.ModeReplay,
}
for _, mode := range traceModes {
t.Run("trace_"+string(mode), func(t *testing.T) {
if got := trace.ParseMode(string(mode)); got != mode {
t.Fatalf("trace mode %q parsed as %q", mode, got)
}
})
}

harnessModes := []harness.Mode{
harness.ModeOff,
harness.ModeAudit,
}
for _, mode := range harnessModes {
t.Run("harness_"+string(mode), func(t *testing.T) {
if got := harness.ParseMode(string(mode)); got != mode {
t.Fatalf("harness mode %q parsed as %q", mode, got)
}
})
}
}

func containsTraceMode(modes []trace.Mode, mode trace.Mode) bool {
for _, candidate := range modes {
if candidate == mode {
return true
}
}
return false
}
29 changes: 28 additions & 1 deletion scripts/release_readiness/readiness.sh
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,31 @@ run_required_command() {
fi
}

run_rr002_command() {
local title="RR-002 CLI Smoke + Config Matrix"
local command_text="bash scripts/release_readiness/rr002_cli_smoke.sh"
local output status recommendation

set +e
rr_run_shell_capture output "${command_text}"
status=$?
set -e
rr_report_command "${REPORT_FILE}" "${title}" "${command_text}" "${status}" "${output}"

recommendation="$(
printf '%s\n' "${output}" |
awk -F': ' '/^\[rr002\] final recommendation:/ { value=$2 } END { print value }'
)"

if [[ "${status}" != "0" || "${recommendation}" == "BLOCKED" ]]; then
record_fail "RR-002 CLI smoke + config matrix failed"
elif [[ "${recommendation}" == "RR002_PASS" ]]; then
record_pass
else
record_skip
fi
}

rr_report_begin "${REPORT_FILE}"

timestamp="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
Expand Down Expand Up @@ -257,6 +282,8 @@ release_body="$(
rr_report_list "${REPORT_FILE}" "Release workflow safety" "${release_body}"
record_pass

run_rr002_command

neutrality_findings=""
NEUTRALITY_FINDINGS_TMP=""
neutrality_patterns=(
Expand Down Expand Up @@ -378,7 +405,7 @@ else
fi

skipped_body="$(
printf 'RR-002 CLI smoke + config matrix: skipped\n'
printf 'RR-002 CLI smoke + config matrix: handled in RR-002 section\n'
printf 'RR-003 VAR spine fixture smoke: skipped\n'
printf 'RR-004 policy absence + statelock/paradox smoke: skipped\n'
printf 'RR-005 vector/RAG/engram preservation smoke: skipped\n'
Expand Down
Loading
Loading