From 5b21af45f500e38aa797512674eb0a1f6d0e787b Mon Sep 17 00:00:00 2001 From: velariumai Date: Sun, 17 May 2026 00:52:21 -0500 Subject: [PATCH] test(release): add policy statelock smoke --- configs/promotion-allowlist.txt | 2 + configs/promotion-manifest.txt | 5 +- docs/release/README.md | 79 +++- .../rr004_policy_statelock_test.go | 400 ++++++++++++++++++ scripts/release_readiness/readiness.sh | 28 +- .../rr004_policy_statelock_smoke.sh | 325 ++++++++++++++ scripts/release_readiness/test_rr004_smoke.sh | 92 ++++ 7 files changed, 923 insertions(+), 8 deletions(-) create mode 100644 pkg/releasecheck/rr004_policy_statelock_test.go create mode 100755 scripts/release_readiness/rr004_policy_statelock_smoke.sh create mode 100755 scripts/release_readiness/test_rr004_smoke.sh diff --git a/configs/promotion-allowlist.txt b/configs/promotion-allowlist.txt index f86327e..ecefc4f 100644 --- a/configs/promotion-allowlist.txt +++ b/configs/promotion-allowlist.txt @@ -14,11 +14,13 @@ scripts/release_checklist.sh scripts/release_readiness/readiness.sh scripts/release_readiness/rr002_cli_smoke.sh scripts/release_readiness/rr003_var_spine_smoke.sh +scripts/release_readiness/rr004_policy_statelock_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 scripts/release_readiness/test_rr003_smoke.sh +scripts/release_readiness/test_rr004_smoke.sh Makefile go.mod go.sum diff --git a/configs/promotion-manifest.txt b/configs/promotion-manifest.txt index 9230f52..1fecd37 100644 --- a/configs/promotion-manifest.txt +++ b/configs/promotion-manifest.txt @@ -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-17T04:11:27Z +# Generated at (UTC): 2026-05-17T05:45:40Z .github/workflows/release.yml .github/workflows/universal-validator.yml @@ -597,6 +597,7 @@ pkg/python/manager.go pkg/registry/registry.go pkg/registry/types.go pkg/releasecheck/rr003_var_spine_test.go +pkg/releasecheck/rr004_policy_statelock_test.go pkg/replay/case.go pkg/replay/case_test.go pkg/replay/comparator.go @@ -909,8 +910,10 @@ scripts/release_readiness/lib/report.sh scripts/release_readiness/readiness.sh scripts/release_readiness/rr002_cli_smoke.sh scripts/release_readiness/rr003_var_spine_smoke.sh +scripts/release_readiness/rr004_policy_statelock_smoke.sh scripts/release_readiness/test_rr002_smoke.sh scripts/release_readiness/test_rr003_smoke.sh +scripts/release_readiness/test_rr004_smoke.sh scripts/release_readiness/test_smoke.sh scripts/setup_wizard.sh scripts/write_launcher.sh diff --git a/docs/release/README.md b/docs/release/README.md index e39e0e3..357d561 100644 --- a/docs/release/README.md +++ b/docs/release/README.md @@ -5,7 +5,8 @@ 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. RR-003 adds a fixture-based VAR spine smoke that exercises local validation, audit, and replay-style readiness without live providers or release -mutation. +mutation. RR-004 adds local fixture smoke for policy absence, state lock +conflict, paradox report, and operator-facing negative-path clarity. Run from any directory: @@ -43,6 +44,18 @@ RR-003 reports are written under: .local/release-readiness/rr003/ ``` +Run RR-004 directly from any directory: + +```bash +bash scripts/release_readiness/rr004_policy_statelock_smoke.sh +``` + +RR-004 reports are written under: + +```text +.local/release-readiness/rr004/ +``` + ## What RR-001 Checks - Current branch, commit, and working tree status @@ -66,6 +79,8 @@ RR-003 reports are written under: - It does not run CLI/TUI interaction smoke tests. - It does not claim VAR spine fixture coverage unless the nested RR-003 section runs and records its own recommendation. +- It does not claim policy absence/statelock/paradox smoke coverage unless the + nested RR-004 section runs and records its own recommendation. - It does not run vector/RAG/engram smoke tests. - It does not run Workbench/operator/session smoke tests. - It does not claim final release readiness. @@ -144,10 +159,61 @@ bash scripts/release_readiness/readiness.sh ``` If RR-003 passes or is incomplete without blockers, the wrapper still reports -`NEEDS_RR_SUITE` because RR-004 and later release-readiness layers remain -deferred. If RR-003 finds a concrete blocker, the wrapper reports `BLOCKED`. -RR-003 starts the transition from shallow smoke checks toward operator-like -fixture validation. +`NEEDS_RR_SUITE` because later release-readiness layers remain deferred. If +RR-003 finds a concrete blocker, the wrapper reports `BLOCKED`. RR-003 starts +the transition from shallow smoke checks toward operator-like fixture +validation. + +## RR-004 Policy Absence + Statelock / Paradox Smoke + +RR-004 is a local synthetic operator harness for negative-path release-readiness +signals. It tests the operator-facing invariant: no policy is not permission. + +### What RR-004 Checks + +- Sensitive policy absence behavior, using a fixture network-egress request with + no matching policy. +- Explicit low-risk policy absence behavior, using a read-only manifest-count + style fixture. +- State lock conflict behavior, using an in-memory lock and a permission-scope + widening proposal. +- Paradox report wording across possible, confirmed, and inconclusive states. +- Skillruntime/profile linkage, showing that adverse statelock/paradox evidence + cannot silently pass a promotion-like fixture. +- Operator-facing report quality: scenario name, operator story, requested + action, policy state, risk class, expected outcome, actual outcome, + evidence/ref summary, pass/fail/skip classification, remediation/next-step + text, and weak seams. +- Report generation and runtime output confined to + `.local/release-readiness/rr004/`. +- Tracked-file mutation avoidance. + +### What RR-004 Does Not Do + +- It does not call live providers. +- It does not make network calls. +- It does not execute app tools. +- It does not open sockets intentionally. +- It does not create, move, or delete tags. +- It does not run release workflows. +- It does not publish releases. +- It does not upload artifacts. +- It does not execute automatic remediation. +- It does not claim runtime enforcement when only fixture/facade behavior ran. +- It does not provide RR-005 vector/RAG/engram preservation coverage. +- It does not provide RR-006 TUI/operator/session scripting. +- It does not provide RR-007 final release report generation. +- It does not claim final release readiness. + +The main wrapper runs RR-004 by default: + +```bash +bash scripts/release_readiness/readiness.sh +``` + +If RR-004 passes or is incomplete without blockers, the wrapper still reports +`NEEDS_RR_SUITE` because RR-005 and later release-readiness layers remain +deferred. If RR-004 finds a concrete blocker, the wrapper reports `BLOCKED`. ## Private Scanner Handling @@ -158,7 +224,8 @@ execution remains a separate private preflight for RR-001. - RR-002 CLI smoke + config matrix: implemented as local report-only smoke. - RR-003 VAR spine fixture smoke: implemented as local fixture validation. -- RR-004 policy absence + statelock/paradox smoke: deferred. +- RR-004 policy absence + statelock/paradox smoke: implemented as local fixture + validation. - RR-005 vector/RAG/engram preservation smoke: deferred. - RR-006 TUI/operator/session scripted smoke: deferred. - RR-007 final release report generator: deferred. diff --git a/pkg/releasecheck/rr004_policy_statelock_test.go b/pkg/releasecheck/rr004_policy_statelock_test.go new file mode 100644 index 0000000..256201d --- /dev/null +++ b/pkg/releasecheck/rr004_policy_statelock_test.go @@ -0,0 +1,400 @@ +package releasecheck + +import ( + "context" + "strings" + "testing" + "time" + + "github.com/velariumai/gorkbot/pkg/evidence" + "github.com/velariumai/gorkbot/pkg/harness" + "github.com/velariumai/gorkbot/pkg/profile" + "github.com/velariumai/gorkbot/pkg/replay" + "github.com/velariumai/gorkbot/pkg/skillruntime" + "github.com/velariumai/gorkbot/pkg/statelock" + "github.com/velariumai/gorkbot/pkg/trace" +) + +func TestRR004PolicyStatelockSmoke(t *testing.T) { + ctx := context.Background() + cfg := rr004FixtureConfig() + + sensitive := rr004PolicyAbsenceSensitiveAction(t, cfg) + lowRisk := rr004PolicyAbsenceLowRiskAction(t) + conflict := rr004StateLockConflict(t, ctx, lowRisk.ReceiptRef) + possible, confirmed, inconclusive := rr004ParadoxReport(t, conflict.Result) + linkage := rr004SkillruntimeProfileLinkage(t, cfg, conflict.Result, sensitive.ReceiptRef) + + t.Logf("RR004_SUMMARY scenarios=5 sensitive=%s low_risk=%s statelock=%s paradox_possible=%s paradox_confirmed=%s paradox_inconclusive=%s skillruntime=%s", sensitive.Classification, lowRisk.Classification, conflict.Classification, possible.Status, confirmed.Status, inconclusive.Status, linkage.Classification) + t.Logf("RR004_OPERATOR_SUMMARY no policy is not permission; sensitive paths require approval or denial, low-risk paths must be explicit, conflicts surface with refs, paradox remediation stays descriptive") +} + +type rr004ScenarioResult struct { + Classification string + Assessment evidence.Assessment + Receipt evidence.Receipt + ReceiptRef trace.Ref + Result statelock.CheckResult + SkillResult skillruntime.Result +} + +func rr004FixtureConfig() profile.Config { + cfg := profile.DefaultConfig(profile.ProfileExpert) + cfg.ConfiguredCapabilities = map[profile.Capability]bool{ + profile.CapabilitySkillStage: true, + profile.CapabilitySkillPromote: true, + } + cfg.Approval.RequireApprovalForSelfmod = false + cfg.Approval.RequireApprovalForPolicyAbsence = true + return cfg.Normalized() +} + +func rr004PolicyAbsenceSensitiveAction(t *testing.T, cfg profile.Config) rr004ScenarioResult { + t.Helper() + + assessment := evidence.Evaluate(evidence.Assessment{ + PolicyState: evidence.PolicyNoMatch, + Risk: evidence.RiskSensitive, + Operation: string(evidence.SensitiveNetworkEgress), + SensitiveClass: evidence.SensitiveNetworkEgress, + Authority: evidence.AuthorityNone, + }) + if assessment.Decision == evidence.DecisionAllowLowRisk || assessment.Status == evidence.StatusPass { + t.Fatalf("policy absence sensitive action silently allowed: status=%s decision=%s", assessment.Status, assessment.Decision) + } + if assessment.Decision != evidence.DecisionRequireApproval { + t.Fatalf("sensitive absent policy decision=%s, want require_approval", assessment.Decision) + } + if assessment.ReasonCode != evidence.ReasonPolicyAbsentSensitive { + t.Fatalf("sensitive absent policy reason=%s, want %s", assessment.ReasonCode, evidence.ReasonPolicyAbsentSensitive) + } + + profileAssessment := profile.EvaluateCapability(cfg, profile.CapabilityNetworkEgress) + if profileAssessment.Decision == evidence.DecisionAllowLowRisk || profileAssessment.Status == evidence.StatusPass { + t.Fatalf("profile network egress under absent policy silently allowed: status=%s decision=%s", profileAssessment.Status, profileAssessment.Decision) + } + + receipt := evidence.Receipt{ + Assessment: assessment, + Status: evidence.StatusWarn, + Summary: "policy_absence_sensitive_action blocked: no policy is not permission", + Records: []evidence.Record{(evidence.Record{ + Kind: evidence.KindPolicyAbsence, + Status: evidence.StatusWarn, + Subject: string(evidence.SensitiveNetworkEgress), + Summary: "network egress has no matching policy; request approval or provide policy before proceeding", + PolicyState: evidence.PolicyNoMatch, + Risk: evidence.RiskSensitive, + Authority: evidence.AuthorityNone, + }).Normalized()}, + Metadata: map[string]string{ + "scenario": "policy_absence_sensitive_action", + "operator": "attempted network egress", + "next_step": "provide matching policy or request explicit approval", + "profile_link": profileAssessment.ID, + }, + }.Normalized() + if err := receipt.Validate(); err != nil { + t.Fatalf("sensitive absence receipt invalid: %v", err) + } + + ref := evidence.ReceiptRef(receipt) + t.Logf("RR004_SCENARIO name=policy_absence_sensitive_action classification=PASS") + t.Logf("RR004_STORY policy_absence_sensitive_action: operator attempts network egress with no matching policy") + t.Logf("RR004_EXPECTED policy_absence_sensitive_action: no silent allow; no policy is not permission; approval or policy required") + t.Logf("RR004_ACTUAL policy_absence_sensitive_action: status=%s decision=%s reason=%s profile_status=%s profile_decision=%s", assessment.Status, assessment.Decision, assessment.ReasonCode, profileAssessment.Status, profileAssessment.Decision) + t.Logf("RR004_EVIDENCE policy_absence_sensitive_action: receipt=%s ref=%s next_step=%s", receipt.ID, ref.Ref, receipt.Metadata["next_step"]) + t.Logf("RR004_WEAK_SEAM policy_absence_sensitive_action: profile exposes approval_required but does not name a concrete policy file path") + + return rr004ScenarioResult{Classification: "PASS", Assessment: assessment, Receipt: receipt, ReceiptRef: ref} +} + +func rr004PolicyAbsenceLowRiskAction(t *testing.T) rr004ScenarioResult { + t.Helper() + + assessment := evidence.Evaluate(evidence.Assessment{ + PolicyState: evidence.PolicyNoMatch, + Risk: evidence.RiskLow, + Operation: "manifest_count", + ExplicitLowRisk: true, + Authority: evidence.AuthorityNone, + }) + if assessment.Decision != evidence.DecisionAllowLowRisk || assessment.Status != evidence.StatusPass { + t.Fatalf("explicit low-risk absent policy result status=%s decision=%s, want pass allow_low_risk", assessment.Status, assessment.Decision) + } + if assessment.ReasonCode != evidence.ReasonLowRiskExplicitAbsent { + t.Fatalf("low-risk absent reason=%s, want %s", assessment.ReasonCode, evidence.ReasonLowRiskExplicitAbsent) + } + + implicit := evidence.Evaluate(evidence.Assessment{ + PolicyState: evidence.PolicyNoMatch, + Risk: evidence.RiskLow, + Operation: "manifest_count", + Authority: evidence.AuthorityNone, + }) + if implicit.Decision == evidence.DecisionAllowLowRisk { + t.Fatalf("implicit low-risk classification allowed without explicit low-risk marker") + } + + receipt := evidence.Receipt{ + Assessment: assessment, + Status: evidence.StatusPass, + Summary: "policy_absence_low_risk_action allowed only because risk class is explicit", + Records: []evidence.Record{(evidence.Record{ + Kind: evidence.KindValidationReport, + Status: evidence.StatusPass, + Subject: "manifest_count", + Summary: "read-only local manifest count classified as explicit low-risk", + PolicyState: evidence.PolicyNoMatch, + Risk: evidence.RiskLow, + Authority: evidence.AuthorityNone, + }).Normalized()}, + Metadata: map[string]string{ + "scenario": "policy_absence_low_risk_action", + "risk_class": "explicit_low_risk", + "not_permission": "no broad permission inferred from absent policy", + }, + }.Normalized() + if err := receipt.Validate(); err != nil { + t.Fatalf("low-risk absence receipt invalid: %v", err) + } + + ref := evidence.ReceiptRef(receipt) + t.Logf("RR004_SCENARIO name=policy_absence_low_risk_action classification=PASS") + t.Logf("RR004_STORY policy_absence_low_risk_action: operator requests read-only manifest count while no matching policy exists") + t.Logf("RR004_EXPECTED policy_absence_low_risk_action: may proceed only as explicit low-risk; no broad permission inferred") + t.Logf("RR004_ACTUAL policy_absence_low_risk_action: explicit_status=%s explicit_decision=%s implicit_decision=%s", assessment.Status, assessment.Decision, implicit.Decision) + t.Logf("RR004_EVIDENCE policy_absence_low_risk_action: receipt=%s ref=%s note=%s", receipt.ID, ref.Ref, receipt.Metadata["not_permission"]) + t.Logf("RR004_WEAK_SEAM policy_absence_low_risk_action: distinction relies on explicit_low_risk bit rather than richer operator explanation") + + return rr004ScenarioResult{Classification: "PASS", Assessment: assessment, Receipt: receipt, ReceiptRef: ref} +} + +func rr004StateLockConflict(t *testing.T, ctx context.Context, receiptRef trace.Ref) rr004ScenarioResult { + t.Helper() + + store := statelock.NewMemoryStore() + lock := statelock.Lock{ + Scope: statelock.ScopeRepository, + Dimension: statelock.DimensionPermissionScope, + Subject: "release/pr-019", + StateHash: "perm:read", + Status: statelock.StatusActive, + Source: statelock.SourceHarness, + PolicyState: statelock.PolicyMatched, + EvidenceRefs: []trace.Ref{receiptRef}, + }.Normalized() + if err := store.SaveLock(ctx, lock); err != nil { + t.Fatalf("save fixture lock: %v", err) + } + + proposed := statelock.ProposedState{ + Scope: statelock.ScopeRepository, + Dimension: statelock.DimensionPermissionScope, + Subject: "release/pr-019", + StateHash: "perm:admin", + PolicyState: statelock.PolicyMatched, + Risk: statelock.RiskLow, + EvidenceRefs: []trace.Ref{receiptRef}, + Metadata: map[string]string{ + "scenario": "statelock_conflict", + }, + } + result := (&statelock.Evaluator{Store: store}).Check(ctx, proposed) + if result.Status != statelock.CheckStatusConflict { + t.Fatalf("statelock result=%s err=%v, want conflict", result.Status, result.Err) + } + if len(result.Conflicts) == 0 { + t.Fatalf("statelock conflict missing conflict list") + } + if result.Paradox == nil { + t.Fatalf("statelock conflict missing paradox report") + } + joinedReasons := rr004ConflictReasons(result.Conflicts) + if !strings.Contains(joinedReasons, statelock.ReasonPermissionScopeWidened) { + t.Fatalf("statelock conflict reasons=%s, want permission_scope_widened", joinedReasons) + } + if result.Paradox.Status == statelock.ParadoxConfirmed { + t.Fatalf("permission widening fixture should be possible, not confirmed") + } + + receipt := evidence.Receipt{ + Assessment: evidence.Evaluate(evidence.Assessment{ + PolicyState: evidence.PolicyMatched, + Risk: evidence.RiskLow, + Operation: "statelock_conflict", + Authority: evidence.AuthorityPolicyMatch, + }), + Status: evidence.StatusWarn, + Summary: "statelock_conflict blocked unsafe widening", + Records: []evidence.Record{(evidence.Record{ + Kind: evidence.KindStateLock, + Status: evidence.StatusWarn, + Subject: "release/pr-019", + Summary: "permission widening conflict visible to operator", + PolicyState: evidence.PolicyMatched, + Risk: evidence.RiskLow, + Authority: evidence.AuthorityPolicyMatch, + EvidenceRefs: []trace.Ref{statelock.ParadoxRef(*result.Paradox)}, + }).Normalized()}, + EvidenceRefs: []trace.Ref{statelock.ParadoxRef(*result.Paradox)}, + Metadata: map[string]string{ + "scenario": "statelock_conflict", + "reasons": joinedReasons, + }, + }.Normalized() + if err := receipt.Validate(); err != nil { + t.Fatalf("statelock conflict receipt invalid: %v", err) + } + + t.Logf("RR004_SCENARIO name=statelock_conflict classification=PASS") + t.Logf("RR004_STORY statelock_conflict: operator attempts to widen repository permission scope while an active lock records read-only state") + t.Logf("RR004_EXPECTED statelock_conflict: conflict detected; unsafe path not silently allowed; severity and reason visible") + t.Logf("RR004_ACTUAL statelock_conflict: status=%s conflicts=%d reasons=%s paradox=%s", result.Status, len(result.Conflicts), joinedReasons, result.Paradox.Status) + t.Logf("RR004_EVIDENCE statelock_conflict: receipt=%s paradox_ref=%s remediation_count=%d", receipt.ID, statelock.ParadoxRef(*result.Paradox).Ref, len(result.Recommendations)) + t.Logf("RR004_WEAK_SEAM statelock_conflict: conflict summaries are reason-code based and could use richer operator text") + + return rr004ScenarioResult{Classification: "PASS", Receipt: receipt, ReceiptRef: evidence.ReceiptRef(receipt), Result: result} +} + +func rr004ParadoxReport(t *testing.T, possibleResult statelock.CheckResult) (statelock.ParadoxReport, statelock.ParadoxReport, statelock.ParadoxReport) { + t.Helper() + if possibleResult.Paradox == nil { + t.Fatalf("possible paradox input missing report") + } + possible := possibleResult.Paradox.Normalized() + if possible.Status != statelock.ParadoxPossible { + t.Fatalf("possible paradox status=%s, want possible", possible.Status) + } + if strings.Contains(strings.ToLower(possible.Summary), "no valid path") { + t.Fatalf("possible paradox overclaims summary: %s", possible.Summary) + } + + confirmedConflict := statelock.DetectConflicts(nil, statelock.ProposedState{ + Scope: statelock.ScopeRelease, + Dimension: statelock.DimensionPolicyState, + Subject: "release_publish", + StateHash: "publish", + PolicyState: statelock.PolicyNoMatch, + Risk: statelock.RiskSensitive, + }) + confirmedResult := (&statelock.Evaluator{}).Check(context.Background(), statelock.ProposedState{ + Scope: statelock.ScopeRelease, + Dimension: statelock.DimensionPolicyState, + Subject: "release_publish", + StateHash: "publish", + PolicyState: statelock.PolicyNoMatch, + Risk: statelock.RiskSensitive, + }) + if len(confirmedConflict) == 0 || confirmedResult.Paradox == nil { + t.Fatalf("confirmed paradox fixture failed to produce conflict/report") + } + confirmed := confirmedResult.Paradox.Normalized() + if confirmed.Status != statelock.ParadoxConfirmed { + t.Fatalf("confirmed paradox status=%s, want confirmed", confirmed.Status) + } + if !strings.Contains(strings.ToLower(confirmed.Summary), "no valid path") { + t.Fatalf("confirmed paradox summary did not clearly state no valid path: %s", confirmed.Summary) + } + + now := time.Date(2026, 5, 17, 0, 0, 0, 0, time.UTC) + inconclusive := statelock.ParadoxReport{ + Status: statelock.ParadoxInconclusive, + Summary: "insufficient data to confirm policy and lock compatibility", + PolicyState: statelock.PolicyUnavailable, + Risk: statelock.RiskSensitive, + StartedAt: now, + FinishedAt: now, + Remediation: []statelock.Remediation{ + {Kind: statelock.RemediationProvidePolicy, Description: "provide missing policy evidence"}, + {Kind: statelock.RemediationRequestApproval, Description: "request explicit approval before execution"}, + }, + Metadata: map[string]string{ + "scenario": "paradox_report", + }, + }.Normalized() + if err := inconclusive.Validate(); err != nil { + t.Fatalf("inconclusive paradox invalid: %v", err) + } + if strings.Contains(strings.ToLower(inconclusive.Summary), "no valid path") { + t.Fatalf("inconclusive paradox overclaims summary: %s", inconclusive.Summary) + } + + t.Logf("RR004_SCENARIO name=paradox_report classification=PASS") + t.Logf("RR004_STORY paradox_report: operator receives possible, confirmed, and inconclusive paradox summaries from fixture constraints") + t.Logf("RR004_EXPECTED paradox_report: confirmed only for critical constraints; possible/inconclusive do not overclaim no valid path; remediation descriptive only") + t.Logf("RR004_ACTUAL paradox_report: possible=%s confirmed=%s inconclusive=%s remediation_possible=%d remediation_confirmed=%d remediation_inconclusive=%d", possible.Status, confirmed.Status, inconclusive.Status, len(possible.Remediation), len(confirmed.Remediation), len(inconclusive.Remediation)) + t.Logf("RR004_EVIDENCE paradox_report: possible_ref=%s confirmed_ref=%s inconclusive_ref=%s", statelock.ParadoxRef(possible).Ref, statelock.ParadoxRef(confirmed).Ref, statelock.ParadoxRef(inconclusive).Ref) + t.Logf("RR004_WEAK_SEAM paradox_report: remediation is descriptive; no executor hook exists in this fixture, which is intentional for RR-004") + + return possible, confirmed, inconclusive +} + +func rr004SkillruntimeProfileLinkage(t *testing.T, cfg profile.Config, conflict statelock.CheckResult, receiptRef trace.Ref) rr004ScenarioResult { + t.Helper() + + harnessReport := harness.Report{HarnessID: "rr004.fixture.harness", ArtifactID: "rr004-artifact", Status: harness.StatusPass}.Normalized() + replayResult := replay.Result{CaseID: "rr004-replay", CandidateID: "rr004-candidate", Verdict: replay.VerdictPass, Duration: time.Millisecond} + paradoxRef := trace.Ref{} + if conflict.Paradox != nil { + paradoxRef = statelock.ParadoxRef(*conflict.Paradox) + } + result := skillruntime.Evaluate(skillruntime.Request{ + Operation: skillruntime.OperationPromote, + Candidate: skillruntime.Candidate{ + Name: "rr004 fixture candidate", + Source: "local fixture", + Risk: evidence.RiskSensitive, + OperationClass: evidence.SensitiveSelfmodPromotion, + Profile: cfg.Profile, + EvidenceRefs: []trace.Ref{receiptRef, paradoxRef}, + Metadata: map[string]string{ + "rollback_path": "fixture rollback", + "disable_path": "fixture disable", + }, + }, + Config: cfg, + HarnessReport: &harnessReport, + ReplayResult: &replayResult, + StateLockResult: &conflict, + EvidenceRefs: []trace.Ref{receiptRef, paradoxRef}, + Metadata: map[string]string{ + "scenario": "skillruntime_profile_linkage", + "rollback_path": "fixture rollback", + "disable_path": "fixture disable", + }, + }) + if result.Status != skillruntime.StatusDenied { + t.Fatalf("skillruntime conflict linkage status=%s warnings=%v, want denied", result.Status, result.Warnings) + } + joinedWarnings := strings.Join(result.Warnings, "|") + if !strings.Contains(joinedWarnings, "state lock conflict") { + t.Fatalf("skillruntime warnings=%v, want state lock conflict", result.Warnings) + } + if len(result.ValidationRefs) == 0 { + t.Fatalf("skillruntime result missing validation refs") + } + if err := result.Receipt.Validate(); err != nil { + t.Fatalf("skillruntime linkage receipt invalid: %v", err) + } + + t.Logf("RR004_SCENARIO name=skillruntime_profile_linkage classification=PASS") + t.Logf("RR004_STORY skillruntime_profile_linkage: operator tries promotion with conflict/paradox evidence attached") + t.Logf("RR004_EXPECTED skillruntime_profile_linkage: adverse statelock/paradox evidence cannot silently pass; receipt/ref linkage present") + t.Logf("RR004_ACTUAL skillruntime_profile_linkage: status=%s decision=%s warnings=%s refs=%d receipt=%s", result.Status, result.Decision, joinedWarnings, len(result.ValidationRefs), result.Receipt.ID) + t.Logf("RR004_EVIDENCE skillruntime_profile_linkage: receipt_ref=%s paradox_ref=%s profile=%s", evidence.ReceiptRef(result.Receipt).Ref, paradoxRef.Ref, cfg.Profile) + t.Logf("RR004_WEAK_SEAM skillruntime_profile_linkage: result warns about state lock conflict but does not inline all conflict reasons") + + return rr004ScenarioResult{Classification: "PASS", SkillResult: result, Receipt: result.Receipt, ReceiptRef: evidence.ReceiptRef(result.Receipt)} +} + +func rr004ConflictReasons(conflicts []statelock.Conflict) string { + reasons := make([]string, 0, len(conflicts)) + for i := range conflicts { + if conflicts[i].ReasonCode == "" { + continue + } + reasons = append(reasons, conflicts[i].ReasonCode) + } + return strings.Join(reasons, ",") +} diff --git a/scripts/release_readiness/readiness.sh b/scripts/release_readiness/readiness.sh index 3103f81..2a159fd 100755 --- a/scripts/release_readiness/readiness.sh +++ b/scripts/release_readiness/readiness.sh @@ -110,6 +110,31 @@ run_rr003_command() { fi } +run_rr004_command() { + local title="RR-004 Policy Absence + Statelock / Paradox Smoke" + local command_text="bash scripts/release_readiness/rr004_policy_statelock_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': ' '/^\[rr004\] final recommendation:/ { value=$2 } END { print value }' + )" + + if [[ "${status}" != "0" || "${recommendation}" == "BLOCKED" ]]; then + record_fail "RR-004 policy absence + statelock/paradox smoke failed" + elif [[ "${recommendation}" == "RR004_PASS" ]]; then + record_pass + else + record_skip + fi +} + rr_report_begin "${REPORT_FILE}" timestamp="$(date -u +%Y-%m-%dT%H:%M:%SZ)" @@ -309,6 +334,7 @@ record_pass run_rr002_command run_rr003_command +run_rr004_command neutrality_findings="" NEUTRALITY_FINDINGS_TMP="" @@ -433,7 +459,7 @@ fi skipped_body="$( printf 'RR-002 CLI smoke + config matrix: handled in RR-002 section\n' printf 'RR-003 VAR spine fixture smoke: handled in RR-003 section\n' - printf 'RR-004 policy absence + statelock/paradox smoke: skipped\n' + printf 'RR-004 policy absence + statelock/paradox smoke: handled in RR-004 section\n' printf 'RR-005 vector/RAG/engram preservation smoke: skipped\n' printf 'RR-006 TUI/operator/session scripted smoke: skipped\n' printf 'RR-007 final release report generator: skipped\n' diff --git a/scripts/release_readiness/rr004_policy_statelock_smoke.sh b/scripts/release_readiness/rr004_policy_statelock_smoke.sh new file mode 100755 index 0000000..aa74659 --- /dev/null +++ b/scripts/release_readiness/rr004_policy_statelock_smoke.sh @@ -0,0 +1,325 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=lib/common.sh +source "${SCRIPT_DIR}/lib/common.sh" +# shellcheck source=lib/report.sh +source "${SCRIPT_DIR}/lib/report.sh" + +ROOT="$(rr_repo_root "${SCRIPT_DIR}")" +cd "${ROOT}" + +REPORT_DIR="${RR004_REPORT_DIR:-${ROOT}/.local/release-readiness/rr004}" +RUN_DIR="${REPORT_DIR}/run" +mkdir -p "${REPORT_DIR}" "${RUN_DIR}/home" "${RUN_DIR}/gocache" "${RUN_DIR}/gopath" "${RUN_DIR}/tmp" +REPORT_FILE="${REPORT_DIR}/rr004-policy-statelock-smoke.$(rr_timestamp).md" + +TIMEOUT_SECONDS="${RR004_TIMEOUT_SECONDS:-120}" +PASS_COUNT=0 +FAIL_COUNT=0 +SKIP_COUNT=0 +BLOCKERS="" +FINAL_RECOMMENDATION="RR004_PASS" + +rr004_mark_blocker() { + rr_append_unique_line "$1" BLOCKERS +} + +rr004_record_pass() { + PASS_COUNT=$((PASS_COUNT + 1)) +} + +rr004_record_fail() { + FAIL_COUNT=$((FAIL_COUNT + 1)) + rr004_mark_blocker "$1" +} + +rr004_record_skip() { + SKIP_COUNT=$((SKIP_COUNT + 1)) +} + +rr004_truncate_output() { + awk 'NR <= 180 { print; next } NR == 181 { print "... [truncated]"; exit }' +} + +rr004_report_check() { + local title="$1" + local command_text="$2" + local status="$3" + local state="$4" + local output="$5" + + { + printf '## %s\n\n' "${title}" + printf -- '- Command: `%s`\n' "${command_text}" + printf -- '- Exit code: `%s`\n' "${status}" + printf -- '- Check state: `%s`\n\n' "${state}" + printf '```text\n' + if [[ -n "${output}" ]]; then + printf '%s\n' "${output}" | rr004_truncate_output + else + printf '(no output)\n' + fi + printf '```\n\n' + } >> "${REPORT_FILE}" +} + +rr004_run_fixture_go_test() { + local __out_var="$1" + local captured status gomodcache + + gomodcache="$(go env GOMODCACHE 2>/dev/null || true)" + if [[ -z "${gomodcache}" ]]; then + gomodcache="${RUN_DIR}/gopath/pkg/mod" + fi + + set +e + captured="$( + env -i \ + HOME="${RUN_DIR}/home" \ + PATH="${PATH}" \ + GOCACHE="${RUN_DIR}/gocache" \ + GOPATH="${RUN_DIR}/gopath" \ + GOMODCACHE="${gomodcache}" \ + TMPDIR="${RUN_DIR}/tmp" \ + GOTMPDIR="${RUN_DIR}/tmp" \ + GOPROXY=off \ + GOSUMDB=off \ + GOFLAGS=-mod=readonly \ + TERM=dumb \ + timeout "${TIMEOUT_SECONDS}" \ + go test ./pkg/releasecheck -run TestRR004PolicyStatelockSmoke -count=1 -v 2>&1 + )" + status=$? + set -e + + printf -v "${__out_var}" '%s' "${captured}" + return "${status}" +} + +rr004_fixture_lines() { + local prefix="$1" + local output="$2" + local lines + + lines="$(printf '%s\n' "${output}" | grep "${prefix}" || true)" + if [[ -n "${lines}" ]]; then + printf '%s\n' "${lines}" + else + printf '(fixture not run)\n' + fi +} + +rr_report_begin "${REPORT_FILE}" +rr_report_section "${REPORT_FILE}" "RR-004 Policy Absence + Statelock / Paradox Smoke" "Local fixture-based release-readiness smoke for policy absence, explicit low-risk behavior, state lock conflict handling, paradox report wording, and skillruntime/profile linkage. Provider calls, app tool execution, network calls, release workflows, tag mutation, and release publication are not performed. Core invariant: no policy is not permission." + +timestamp="$(date -u +%Y-%m-%dT%H:%M:%SZ)" +branch="$(git branch --show-current 2>/dev/null || true)" +commit="$(git log -1 --oneline 2>/dev/null || true)" +status_before="$(git status --short 2>/dev/null || true)" + +preflight_body="$( + printf 'timestamp: %s\n' "${timestamp}" + printf 'branch: %s\n' "${branch:-unknown}" + printf 'commit: %s\n' "${commit:-unknown}" + printf 'repo root: %s\n' "${ROOT}" + printf 'report directory: %s\n' "${REPORT_DIR}" + printf 'timeout seconds: %s\n' "${TIMEOUT_SECONDS}" + printf 'working tree before:\n' + if [[ -n "${status_before}" ]]; then + printf '%s\n' "${status_before}" + else + printf '(clean)\n' + fi +)" +rr_report_list "${REPORT_FILE}" "Preflight" "${preflight_body}" +rr004_record_pass + +inventory_body="$( + printf 'policy absence APIs usable for fixture checks:\n' + printf 'evidence.PolicyState, evidence.Evaluate, evidence.Assessment, evidence.Receipt, profile.EvaluateCapability\n' + printf 'statelock APIs usable for conflict/paradox checks:\n' + printf 'statelock.Lock, statelock.ProposedState, statelock.Evaluator.Check, statelock.ParadoxRef\n' + printf 'evidence/profile APIs usable for operator-facing assessment:\n' + printf 'evidence.Record/Receipt/Ref helpers, profile.ConfigRef, profile capability rules\n' + printf 'skillruntime/trace APIs usable for linkage:\n' + printf 'skillruntime.Evaluate, skillruntime.Result receipts, trace.Ref, trace.StableHash\n' + printf 'safe without runtime/provider/tool execution:\n' + printf 'in-memory fixture policy checks, in-memory statelock store, fixture paradox reports, local Go test logs\n' + printf 'unsafe/deferred:\n' + printf 'live providers, app tools, network endpoints, sockets, release workflows, tag mutation, release publication, private scanner mutation\n' + printf 'vector/semantic memory inspected only: yes\n' + printf 'likely sensitive sinks in planned changes: bounded local go test command; markdown report writes under .local only\n' +)" +rr_report_list "${REPORT_FILE}" "Initial inventory findings" "${inventory_body}" +rr004_record_pass + +safety_body="$( + printf 'network calls: no\n' + printf 'provider calls: no\n' + printf 'app tool execution: no\n' + printf 'release workflow execution: no\n' + printf 'tag creation/movement/deletion: no\n' + printf 'release publication: no\n' + printf 'tracked file mutation by RR-004 runtime: no\n' + printf 'fixture output confinement: %s\n' "${REPORT_DIR}" + printf 'operator invariant: no policy is not permission\n' +)" +rr_report_list "${REPORT_FILE}" "Operational safety posture" "${safety_body}" +rr004_record_pass + +if ! command -v timeout >/dev/null 2>&1; then + rr004_report_check "Timeout availability" "command -v timeout" "1" "BLOCKED" "timeout command is required for bounded RR-004 fixture execution" + rr004_record_fail "timeout command unavailable" +fi + +fixture_command="go test ./pkg/releasecheck -run TestRR004PolicyStatelockSmoke -count=1 -v" +fixture_output="" +fixture_status=0 +if [[ "${RR004_SKIP_GO_TEST:-0}" == "1" ]]; then + skip_output="RR004_SKIP_GO_TEST=1; fixture package execution skipped by request." + rr004_report_check "Fixture workflow" "${fixture_command}" "not-run" "SKIP" "${skip_output}" + rr004_record_skip +else + set +e + rr004_run_fixture_go_test fixture_output + fixture_status=$? + set -e + + if [[ "${fixture_status}" == "0" ]]; then + rr004_report_check "Fixture workflow" "${fixture_command}" "${fixture_status}" "PASS" "${fixture_output}" + rr004_record_pass + elif [[ "${fixture_status}" == "124" ]]; then + rr004_report_check "Fixture workflow" "${fixture_command}" "${fixture_status}" "BLOCKED" "${fixture_output}" + rr004_record_fail "RR-004 fixture workflow timed out" + else + rr004_report_check "Fixture workflow" "${fixture_command}" "${fixture_status}" "BLOCKED" "${fixture_output}" + rr004_record_fail "RR-004 fixture workflow failed with exit code ${fixture_status}" + fi +fi + +scenario_body="$( + printf 'policy_absence_sensitive_action:\n' + printf 'operator story: requested network egress with no matching policy\n' + printf 'requested action: network egress fixture, no network call made\n' + printf 'policy state: policy_no_match\n' + printf 'risk/sensitive class: sensitive/network_egress\n' + printf 'expected outcome: approval required or denied; no silent allow\n' + printf 'actual/evidence lines:\n' + rr004_fixture_lines 'RR004_.*policy_absence_sensitive_action' "${fixture_output}" + printf '\npolicy_absence_low_risk_action:\n' + printf 'operator story: requested read-only manifest count under absent policy\n' + printf 'requested action: local read-only metadata fixture\n' + printf 'policy state: policy_no_match\n' + printf 'risk/sensitive class: explicit low-risk/non-sensitive\n' + printf 'expected outcome: may proceed only because low-risk class is explicit\n' + printf 'actual/evidence lines:\n' + rr004_fixture_lines 'RR004_.*policy_absence_low_risk_action' "${fixture_output}" + printf '\nstatelock_conflict:\n' + printf 'operator story: requested permission widening while active lock exists\n' + printf 'requested action: in-memory statelock proposed state\n' + printf 'policy state: policy_matched\n' + printf 'risk/sensitive class: low-risk fixture with permission-scope conflict\n' + printf 'expected outcome: conflict visible; unsafe path not silently allowed\n' + printf 'actual/evidence lines:\n' + rr004_fixture_lines 'RR004_.*statelock_conflict' "${fixture_output}" + printf '\nparadox_report:\n' + printf 'operator story: reviewed possible, confirmed, and inconclusive paradox states\n' + printf 'requested action: report-only paradox fixture\n' + printf 'policy state: matched/no_match/unavailable across fixture cases\n' + printf 'risk/sensitive class: low-risk and sensitive fixture states\n' + printf 'expected outcome: confirmed only for critical constraints; possible/inconclusive do not overclaim\n' + printf 'actual/evidence lines:\n' + rr004_fixture_lines 'RR004_.*paradox_report' "${fixture_output}" + printf '\nskillruntime_profile_linkage:\n' + printf 'operator story: promotion attempted with conflict/paradox evidence attached\n' + printf 'requested action: local skillruntime fixture assessment, no skill execution\n' + printf 'policy state: profile configured; statelock conflict adverse evidence\n' + printf 'risk/sensitive class: sensitive selfmod promotion\n' + printf 'expected outcome: denied/approval_required/incomplete according to contract; refs present\n' + printf 'actual/evidence lines:\n' + rr004_fixture_lines 'RR004_.*skillruntime_profile_linkage' "${fixture_output}" + printf '\noperator-facing summary lines:\n' + rr004_fixture_lines 'RR004_SUMMARY\\|RR004_OPERATOR_SUMMARY' "${fixture_output}" +)" +rr_report_list "${REPORT_FILE}" "Fixture scenario behavior" "${scenario_body}" +if [[ "${RR004_SKIP_GO_TEST:-0}" == "1" ]]; then + rr004_record_skip +elif [[ "${fixture_status}" == "0" ]]; then + rr004_record_pass +fi + +seams_body="$( + printf 'observed weak seams:\n' + printf 'profile policy absence output can require approval without naming a concrete policy file path\n' + printf 'low-risk distinction is explicit but compact; richer user-facing classification text would help\n' + printf 'statelock conflict summaries are reason-code based and could use richer operator language\n' + printf 'skillruntime warns about state lock conflict but does not inline every conflict reason\n' + printf 'remaining incomplete:\n' + printf 'RR-005 vector/RAG/engram preservation smoke deferred\n' + printf 'RR-006 TUI/operator/session scripted smoke deferred\n' + printf 'RR-007 final release report generator deferred\n' +)" +rr_report_list "${REPORT_FILE}" "Weak seams and incomplete work" "${seams_body}" +rr004_record_pass + +status_after="$(git status --short 2>/dev/null || true)" +tracked_diff_after="$( + { + git diff --name-only 2>/dev/null || true + git diff --cached --name-only 2>/dev/null || true + } | awk 'NF' | sort -u +)" +mutation_body="$( + printf 'working tree after:\n' + if [[ -n "${status_after}" ]]; then + printf '%s\n' "${status_after}" + else + printf '(clean)\n' + fi + printf 'tracked changed files after:\n' + if [[ -n "${tracked_diff_after}" ]]; then + printf '%s\n' "${tracked_diff_after}" + else + printf '(none)\n' + fi + printf 'generated output confined to: %s\n' "${REPORT_DIR}" +)" +rr_report_list "${REPORT_FILE}" "Mutation confinement" "${mutation_body}" +if [[ "${status_after}" != "${status_before}" ]]; then + rr004_record_fail "working tree status changed during RR-004" +else + rr004_record_pass +fi + +if [[ -n "${BLOCKERS}" ]]; then + FINAL_RECOMMENDATION="BLOCKED" +elif [[ "${SKIP_COUNT}" != "0" ]]; then + FINAL_RECOMMENDATION="RR004_INCOMPLETE" +else + FINAL_RECOMMENDATION="RR004_PASS" +fi + +final_body="$( + printf 'status: REPORT_ONLY\n' + printf 'recommendation: %s\n' "${FINAL_RECOMMENDATION}" + printf 'checks passed: %s\n' "${PASS_COUNT}" + printf 'checks failed: %s\n' "${FAIL_COUNT}" + printf 'checks skipped: %s\n' "${SKIP_COUNT}" + printf 'blockers:\n' + if [[ -n "${BLOCKERS}" ]]; then + printf '%s\n' "${BLOCKERS}" + else + printf '(none)\n' + fi +)" +rr_report_list "${REPORT_FILE}" "Final recommendation" "${final_body}" + +echo "[rr004] report path: ${REPORT_FILE}" +echo "[rr004] final recommendation: ${FINAL_RECOMMENDATION}" +echo "[rr004] checks passed=${PASS_COUNT} failed=${FAIL_COUNT} skipped=${SKIP_COUNT}" + +if [[ "${FINAL_RECOMMENDATION}" == "BLOCKED" ]]; then + exit 1 +fi diff --git a/scripts/release_readiness/test_rr004_smoke.sh b/scripts/release_readiness/test_rr004_smoke.sh new file mode 100755 index 0000000..95a9f50 --- /dev/null +++ b/scripts/release_readiness/test_rr004_smoke.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +RR004="${ROOT}/scripts/release_readiness/rr004_policy_statelock_smoke.sh" +READINESS="${ROOT}/scripts/release_readiness/readiness.sh" +DOCS="${ROOT}/docs/release/README.md" +GO_FIXTURE="${ROOT}/pkg/releasecheck/rr004_policy_statelock_test.go" + +required_files=( + "${RR004}" + "${READINESS}" + "${DOCS}" + "${GO_FIXTURE}" +) + +for file in "${required_files[@]}"; do + [[ -f "${file}" ]] || { + echo "[rr004-smoke] missing ${file}" >&2 + exit 1 + } +done + +[[ -x "${RR004}" ]] || { + echo "[rr004-smoke] rr004_policy_statelock_smoke.sh is not executable" >&2 + exit 1 +} + +bash -n "${RR004}" +bash -n "${READINESS}" + +grep -Fq "RR-004 Policy Absence + Statelock / Paradox Smoke" "${RR004}" +grep -Fq ".local/release-readiness/rr004" "${RR004}" +grep -Fq "RR004_PASS" "${RR004}" +grep -Fq "RR004_INCOMPLETE" "${RR004}" +grep -Fq "BLOCKED" "${RR004}" +grep -Fq "go test ./pkg/releasecheck -run TestRR004PolicyStatelockSmoke -count=1 -v" "${RR004}" +grep -Fq "no policy is not permission" "${RR004}" +grep -Fq "RR-004 Policy Absence + Statelock / Paradox Smoke" "${READINESS}" +grep -Fq "bash scripts/release_readiness/rr004_policy_statelock_smoke.sh" "${READINESS}" +grep -Fq "RR-004 Policy Absence + Statelock / Paradox Smoke" "${DOCS}" +grep -Fq "bash scripts/release_readiness/rr004_policy_statelock_smoke.sh" "${DOCS}" +grep -Fq "TestRR004PolicyStatelockSmoke" "${GO_FIXTURE}" +grep -Fq "policy_absence_sensitive_action" "${GO_FIXTURE}" +grep -Fq "policy_absence_low_risk_action" "${GO_FIXTURE}" +grep -Fq "statelock_conflict" "${GO_FIXTURE}" +grep -Fq "paradox_report" "${GO_FIXTURE}" +grep -Fq "skillruntime_profile_linkage" "${GO_FIXTURE}" +grep -Fq "no policy is not permission" "${GO_FIXTURE}" + +forbidden_patterns=( + "rm -""rf" + "git pu""sh" + "gh rel""ease" + "gh workflow ""run" + "cu""rl" + "wg""et" + "s""sh" + "s""cp" + " n""c " + "net""cat" + "ev""al" +) + +for pattern in "${forbidden_patterns[@]}"; do + if grep -Fq "${pattern}" "${RR004}"; then + echo "[rr004-smoke] forbidden shell pattern in rr004_policy_statelock_smoke.sh: ${pattern}" >&2 + exit 1 + fi +done + +tag_pattern="git ""tag" +tag_list_pattern="git ""tag --list" +if grep -Fq "${tag_pattern}" "${RR004}" && ! grep -Fq "${tag_list_pattern}" "${RR004}"; then + echo "[rr004-smoke] unexpected tag operation in rr004_policy_statelock_smoke.sh" >&2 + exit 1 +fi + +tmp_dir="${ROOT}/.local/release-readiness/rr004-smoke-test" +mkdir -p "${tmp_dir}" +tmp_root="${TMPDIR:-/tmp}" +out_file="$(mktemp "${tmp_root%/}/rr004-smoke.XXXXXX")" +trap 'rm -f "$out_file"' EXIT +( + cd "${tmp_dir}" + RR004_SKIP_GO_TEST=1 bash "${RR004}" >"${out_file}" +) +grep -Fq "[rr004] report path:" "${out_file}" +grep -Fq "[rr004] final recommendation:" "${out_file}" +grep -Fq "[rr004] checks passed=" "${out_file}" + +echo "[rr004-smoke] OK"