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
17 changes: 17 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,23 @@ jobs:
shell: pwsh
run: ./scripts/eval-repro-check.ps1 -SelfCheck

# Hard C04 L40 rootless / no-net CI evidence cross-reference. The blocking
# PR gate lives in `.github/workflows/rootless-nonet.yml`; this PR-only
# smoke re-runs the script's `-SelfCheck` mode so `ci.yml` retains its
# cross-reference anchor (per `scripts/rootless-nonet-check.ps1` and
# `docs/ops/sandbox-boundary.md` C04 L40 row). Does NOT enforce live
# rootless-only runners or blocking no-net on cargo-fetch jobs — those
# remain unpaid.
rootless-nonet-policy:
name: ci / rootless-nonet policy smoke
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: rootless / no-net SelfCheck
shell: pwsh
run: ./scripts/rootless-nonet-check.ps1 -SelfCheck
Comment on lines +203 to +206

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow excerpt =="
sed -n '185,215p' .github/workflows/ci.yml 2>/dev/null || true

echo
echo "== checkout references in workflows =="
rg -n "actions/checkout|persist-credentials" .github/workflows || true

echo
echo "== git workflow related file listing =="
fd -a '^\..*\.ya?ml$' .github 2>/dev/null | sed 's#^\./##' | sort

Repository: KooshaPari/SessionLedger

Length of output: 14581


🌐 Web query:

actions/checkout v7 GitHub tag target commit

💡 Result:

The actions/checkout v7 release was officially released on June 18, 2026 [1][2]. It introduces a significant security change designed to prevent common pwn request vulnerabilities in GitHub Actions [2]. Specifically, actions/checkout v7 defaults to blocking the checkout of fork pull request code when used within pull_request_target and certain workflow_run events [2][3]. This enforcement applies when the target repository is a fork and the requested ref matches typical pull request head or merge commit patterns [2]. Key details regarding this release: - Security Enforcement: By default, the action now refuses to fetch fork PR code in privileged contexts (pull_request_target and workflow_run) to mitigate risks associated with untrusted code execution [2]. - Versioning: The release, tagged as v7.0.0, was prepared via PR #2464 [4] and includes updates to dependencies and internal documentation [4][5]. - Upgrading: Workflows pinned to major tags (like actions/checkout@v4 or @v6) will eventually be affected by backported enforcements, as GitHub planned to extend this protection to all supported major versions by July 16, 2026 [2]. Workflows pinned to specific SHAs or minor/patch versions remain unaffected until explicitly updated [2]. You can reference the latest version in your workflows using - uses: actions/checkout@v7 [6][7].

Citations:


🌐 Web query:

actions/checkout v7 GitHub release commit SHA

💡 Result:

The actions/checkout@v7 tag is a mutable Git reference that points to the latest release within the v7 major version series [1][2]. Because it is a branch/tag reference, its associated commit SHA changes as new patch releases (e.g., v7.0.1, v7.0.2) are published [3][4]. As of August 8, 2026, the initial v7.0.0 release occurred on June 18, 2026 [5][2]. A subsequent patch, v7.0.1, was released on July 20, 2026 [4]. Consequently, the v7 tag currently points to the commit SHA associated with v7.0.1 [3][4]. For production environments and to ensure supply-chain security, it is best practice to pin your GitHub Actions to a specific, immutable commit SHA rather than a mutable tag like @v7 [1][6]. You can find the specific SHA for any release by viewing the tags in the official actions/checkout repository [3]. As noted in community discussions, using a specific SHA prevents your workflows from automatically pulling in new, potentially breaking, or untested code when a tag is updated [6].

Citations:


🌐 Web query:

actions/checkout persist-credentials defaults persist-credentials false

💡 Result:

The persist-credentials input for the actions/checkout GitHub Action controls whether the repository's authentication token (or SSH key) is configured in the local git config [1][2]. By default, persist-credentials is set to true [1][3]. When set to true, the action configures the Git credential helper or local git config to use the provided token, which allows subsequent steps in the same job to run authenticated Git commands without additional setup [1][2]. The token is then automatically removed during the post-job cleanup [1][2]. Setting persist-credentials to false disables this behavior [1]. This is often used as a security measure to prevent subsequent, potentially untrusted, steps in a workflow from accessing the repository's authentication token through the git configuration [4][5]. There has been significant community discussion regarding changing the default value to false to enhance security [4][5], but as of August 2026, the default value remains true [1][3]. Security improvements have been made in recent versions of the action (such as storing credentials in a separate file under $RUNNER_TEMP rather than directly in.git/config) to mitigate some risks while keeping the feature enabled by default [1][2].

Citations:


🌐 Web query:

"actions/checkout@v7" "3d3c42e5aac5ba805825da76410c181273ba90b1"

💡 Result:

The SHA-1 commit hash 3d3c42e5aac5ba805825da76410c181273ba90b1 corresponds to the release preparation for actions/checkout v7.0.1 [1][2]. Key details regarding this commit and version: - Release Context: This commit is labeled as prep for the v7.0.1 release of the official GitHub actions/checkout repository [1][2]. - Version v7: GitHub released actions/checkout v7 in mid-2026, which introduced significant security improvements by blocking common pwn-request patterns by default in privileged pull_request_target workflows [3][4]. - Enforcement: While v7 includes these safer defaults, GitHub also backported this security enforcement to other supported major versions (e.g., v4) on July 20, 2026 [3]. - Usage: For workflows pinned to a specific SHA (like the one you specified) or a specific patch version, the automatic security backport is not applied; users must manually update these pins to incorporate the necessary security changes [3][4]. For security-conscious setups, it is recommended to ensure your checkout actions are updated to versions that include these security guardrails, especially if utilizing pull_request_target workflows [3][4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== actions/checkout v7 dereference =="
gh api repos/actions/checkout/git/refs/tags/v7 --jq '.object' || true

echo
echo "== suggested SHA validity =="
gh api repos/actions/checkout/commits/3d3c42e5aac5ba805825da76410c181273ba90b0 --jq '.sha' || true
gh api repos/actions/checkout/commits/3d3c42e5aac5ba805825da76410c181273ba90b1 --jq '.sha' || true

echo
echo "== compare suggested SHA to v7 dereferenced commit =="
python3 - <<'PY'
import subprocess, sys
try:
    ref = subprocess.check_output(['gh', 'api', 'repos/actions/checkout/git/refs/tags/v7'], text=True)
    object_ = subprocess.check_output(['gh', 'api', 'repos/actions/checkout/git/refs/tags/v7'], text=True)
except Exception as e:
    print(f"gh refused: {e}")
    sys.exit(0)
PY

Repository: KooshaPari/SessionLedger

Length of output: 755


Pin checkout and disable credential persistence.

This PR job checks out repository code and then runs ./scripts/rootless-nonet-check.ps1, so actions/checkout@v7 should be immutable and should not leave the token in local Git configuration.

Suggested fix
-      - uses: actions/checkout@v7
+++      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
+        with:
+          persist-credentials: false
🧰 Tools
🪛 zizmor (1.29.0)

[warning] 203-203: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 203-203: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 203 - 206, Update the actions/checkout
step in the rootless / no-net SelfCheck job to use an immutable commit SHA
instead of the floating v7 tag, and configure checkout with persist-credentials
disabled.

Source: Linters/SAST tools


security:
name: Security Scan
needs: detect
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ Follows [Keep a Changelog](https://keepachangelog.com/); versioning is [SemVer](

- IntentState serde property surface (WBS-6.2 #426): `tests/properties.rs` adds `intent_state_json_round_trip_preserves_variant` (every variant serialises to its kebab-case `Debug` name and round-trips back) and `intent_state_terminal_invariant_holds_across_serde` (`is_terminal` agrees with the serde representation). Guards drift in the `#[serde(rename_all = "kebab-case")]` attribute.

- sl-viewer unfinished-tab property surface (WBS-6.2 #428): `crates/sl-viewer/tests/properties_viewer_unfinished_tab.rs` adds 6 proptest properties — `reason_label` is non-empty + injective; `unfinished_items` is deterministic, orders known `last_activity_ms` descending, ties break by `session_id` ascending, is length-monotonic w.r.t. input.

- CI drift cleanups (WBS-6.2 #428): `scripts/fuzz-cadence-check.ps1` re-points the "PR smoke stays short" anchor from `ci.yml` (10 s budget) to `fuzz-blocking.yml` (30 s budget) since the PR smoke was consolidated there. `scripts/rootless-nonet-check.ps1` + `.github/workflows/ci.yml` restore the documented `rootless-nonet-policy` cross-reference smoke job, with the script's regex tightened so `continue-on-error` detection can't bleed across jobs. `tests/alloc_profile.rs` + `tests/replay_breadth.rs` clear `clippy::panic_in_if_then` / `clippy::unnecessary_trailing_comma` under `--all-targets --all-features`.

- Wave-44 plan landed: `WAVE44_SCOPE.md` + `docs/ops/WAVE44_PERT.md` enumerate 6 close-out lanes (3 machine, 3 human-gated) for the 6 unpaid residuals from Wave-43 (396/402 → 402/402 target). Theme: stack-stability closure + i18n migration + eval coverage + supply-chain signing.
- Wave-44 reaudit (Wave-44-D): `audit/SCORECARD.md` refresh at commit `13c974f7` (machine-w44-reaudit); `docs/ops/TRACEABILITY.json` overall_audit wave=Wave-44 commit=13c974f7 (conservative hold at 396/402); `docs/ops/GAP_QA_MATRIX.md` C00 + C08 + PLAN-W8-B rows reflect Wave-44 closure (#368 W44-B6 corpus / #372 W44-B1 loom / #373 PERT correction). 2 of 3 machine lanes shipped 2026-07-24; remaining 6 raw pts across C04 L36 / C08 L76 / C11 L110.

Expand Down
180 changes: 180 additions & 0 deletions crates/sl-viewer/tests/properties_viewer_unfinished_tab.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
//! Property evidence for sl-viewer's `unfinished_tab` module.
//!
//! This file complements `crates/sl-viewer/src/unfinished_tab.rs`'s
//! per-function `#[cfg(test)] mod tests` block by pinning invariants
//! over the *full* shape of the inputs the module can receive (the unit
//! tests pin specific values; the property tests below pin invariants
//! over many values).
//!
//! `unfinished_tab` invariants:
//! * `reason_label(reason)` is total and deterministic — every
//! `UnfinishedReason` variant yields a non-empty, label-shaped string.
//! * `reason_label` is distinct — two different reasons produce two
//! different labels (no accidental aliasing in the UI badge).
//! * `unfinished_items` is monotonic w.r.t. `last_activity_ms`:
//! items with a known timestamp appear before items without one
//! (None → "unknown last activity" → sorts last), and among
//! timestamped items the order is descending by timestamp.
//! * `unfinished_items` is stable under session-id tiebreak: when two
//! items share the same `last_activity_ms`, the one with the smaller
//! session_id appears first (lexicographic, ascending).

use proptest::prelude::*;
use session_ledger::domain::session::{Corpus, Message, Role, Session};
use session_ledger::domain::worklog::{UnfinishedReason, UnfinishedWorkItem};
use sl_viewer::unfinished_tab::{reason_label, unfinished_items};

// ── strategies ─────────────────────────────────────────────────────────────

fn session_strategy() -> impl Strategy<Value = Session> {
(
// session_id — non-empty, identifier-shaped.
"[a-zA-Z0-9_-]{1,16}",
// 0..6 messages; mix of roles + content. Bounded so the
// `detect_unfinished` projection runs cheaply.
prop::collection::vec(
(0u8..5, "[ -~]{1,40}"),
0..6,
),
// last_activity_ms — Some(i64) or None. None is the "unknown
// last activity" sentinel the worklog projector uses.
prop::option::of(0i64..1_000_000_000_000),
)
.prop_map(|(session_id, messages, ts_ms)| {
let mut session = Session::new(format!("sess-{session_id}"), Corpus::Forge);
for (role_idx, content) in messages {
let role = match role_idx % 5 {
0 => Role::User,
1 => Role::Assistant,
2 => Role::Subagent,
3 => Role::Tool,
_ => Role::System,
};
let mut msg = Message::new(role, content);
msg.ts_ms = ts_ms;
session.messages.push(msg);
Comment on lines +35 to +55

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Generate timestamps per message.

Lines 39-55 assign one ts_ms value to every message in a session. The property therefore cannot detect a regression where unfinished_items uses the first or last message timestamp instead of the maximum timestamp. The upstream contract uses the maximum timestamp in crates/sl-viewer/src/corpus_tab.rs, lines 37-39.

Generate Option<i64> with each message. Then assert that each projected last_activity_ms equals that session’s maximum known message timestamp.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/sl-viewer/tests/properties_viewer_unfinished_tab.rs` around lines 35 -
55, Update the property generator around the session message construction to
generate an independent Option<i64> timestamp for each message instead of one
session-wide value, while preserving the None unknown-timestamp case. Track the
maximum known timestamp per session and assert the projected unfinished_items
last_activity_ms matches that maximum, following the contract used by
unfinished_items.

}
session
})
}

fn unfinished_reason_strategy() -> impl Strategy<Value = UnfinishedReason> {
prop::sample::select(vec![
UnfinishedReason::AwaitingAssistantResponse,
UnfinishedReason::InterruptedExecution,
UnfinishedReason::MissingCompletionMarker,
])
}

// ── reason_label properties ─────────────────────────────────────────────────

proptest! {
/// Property: `reason_label` is total — every variant produces a
/// non-empty, non-whitespace string. Catches a future addition of a
/// `UnfinishedReason` variant whose match arm maps to `""`.
#[test]
fn reason_label_is_non_empty_for_every_variant(reason in unfinished_reason_strategy()) {
let label = reason_label(reason);
prop_assert!(!label.is_empty(), "reason_label must not be empty for {reason:?}");
prop_assert!(!label.trim().is_empty(), "reason_label must not be all-whitespace for {reason:?}");
}

/// Property: `reason_label` is injective — distinct reasons produce
/// distinct labels. Catches accidental aliasing where, e.g., two
/// reasons share the same badge text in the UI.
#[test]
fn reason_label_is_injective(
left in unfinished_reason_strategy(),
right in unfinished_reason_strategy(),
) {
if left == right {
return Ok(());
}
prop_assert_ne!(reason_label(left), reason_label(right));
}
}

// ── unfinished_items ordering properties ────────────────────────────────────

proptest! {
/// Property: `unfinished_items` is deterministic. Two calls on the
/// same input yield the same output (the function is pure).
#[test]
fn unfinished_items_is_deterministic(
sessions in prop::collection::vec(session_strategy(), 0..8),
) {
let first = unfinished_items(&sessions);
let second = unfinished_items(&sessions);
prop_assert_eq!(first, second);
}

/// Property: `unfinished_items` orders known timestamps descending.
/// Two items with the same `last_activity_ms` may appear in any order
/// (we don't constrain the tiebreak here; see next property).
#[test]
fn unfinished_items_orders_known_timestamps_descending(
sessions in prop::collection::vec(session_strategy(), 1..10),
) {
let items = unfinished_items(&sessions);

// Filter to items with a known timestamp so the descending
// invariant applies cleanly.
let with_ts: Vec<&UnfinishedWorkItem> =
items.iter().filter(|i| i.last_activity_ms.is_some()).collect();
Comment on lines +120 to +123

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert that unknown activity sorts last.

Lines 120-123 discard every item with last_activity_ms == None. The test cannot verify the documented invariant that known activity precedes unknown activity. Keep the full item list and assert that no Some(_) item follows a None item.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/sl-viewer/tests/properties_viewer_unfinished_tab.rs` around lines 120
- 123, Update the test around the with_ts filtering in the unfinished-work
sorting assertions to retain all items, including those with last_activity_ms ==
None. Add an assertion that no item with Some(last_activity_ms) appears after an
item with None, while preserving the existing descending-order check for known
timestamps.


for window in with_ts.windows(2) {
let prev = window[0].last_activity_ms.expect("filtered Some");
let next = window[1].last_activity_ms.expect("filtered Some");
prop_assert!(
prev >= next,
"known timestamps must be descending: {prev} came before {next}",
);
}
}

/// Property: `unfinished_items` ties on `last_activity_ms` break by
/// session_id ascending (lexicographic). When the `last_activity_ms`
/// field is equal, the item with the smaller session_id must appear
/// first.
#[test]
fn unfinished_items_ties_break_by_session_id_ascending(
sessions in prop::collection::vec(session_strategy(), 1..10),
) {
let items = unfinished_items(&sessions);

for window in items.windows(2) {
let prev = &window[0];
let next = &window[1];
match (prev.last_activity_ms, next.last_activity_ms) {
(Some(a), Some(b)) if a == b => {
prop_assert!(
prev.session_id <= next.session_id,
"tie on ts_ms must break by session_id asc: {} came before {}",
prev.session_id,
next.session_id,
);
}
_ => {
// No invariant to check across mixed-Some/None or
// unequal timestamps (covered by other properties).
}
}
}
}

/// Property: `unfinished_items` is length-monotonic w.r.t. input —
/// doubling the input sessions cannot produce fewer items than the
/// original (the projector is non-destructive).
#[test]
fn unfinished_items_is_length_monotonic(
base in prop::collection::vec(session_strategy(), 0..6),
more in prop::collection::vec(session_strategy(), 0..6),
) {
let base_items = unfinished_items(&base).len();
let combined_items = unfinished_items(&[base.clone(), more].concat()).len();
prop_assert!(
combined_items >= base_items,
"appending sessions must not lose items: base={base_items}, combined={combined_items}",
);
}
}
1 change: 1 addition & 0 deletions docs/ops/TRACEABILITY.json
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@
"tests/properties.rs",
"crates/sl-viewer/tests/properties_viewer.rs",
"crates/sl-viewer/tests/properties_viewer_theme_url.rs",
"crates/sl-viewer/tests/properties_viewer_unfinished_tab.rs",
"fuzz/fuzz_targets/okf_roundtrip.rs",
"fuzz/fuzz_targets/jsonl_ingest.rs",
".github/workflows/ci.yml",
Expand Down
2 changes: 1 addition & 1 deletion docs/ops/WBS.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ without a new audit.
| WBS-4.2 | P4 FTS recall via context-mode and explicit TUI decision | partial | human | `docs/DESIGN.md` §3, §7; `crates/sl-viewer/` | DESIGN P4 residual; C00, C11 |
| WBS-5.1 | P5 deterministic dedup merge and crash/lost-work recovery E2E | done | machine | `src/domain/merge.rs`; `src/domain/worklog.rs`; `tests/merge_recovery.rs` | FR-011; T-024, T-035; C03 |
| WBS-6.1 | P6 85% coverage gate and deterministic golden corpus | done | machine | `.github/workflows/ci.yml`; `tests/okf_golden.rs`; `tests/fixtures/okf/` | T-037, T-038; C01, C08 |
| WBS-6.2 | P6 property tests, fuzzing, race checks, and enforced performance budgets | partial | machine | `tests/properties.rs`; `crates/sl-viewer/tests/properties_viewer.rs`; `crates/sl-viewer/tests/properties_viewer_theme_url.rs`; `fuzz/fuzz_targets/okf_roundtrip.rs`; `fuzz/fuzz_targets/jsonl_ingest.rs`; `.github/workflows/ci.yml`; `.github/workflows/bench-gate.yml`; `docs/ops/perf-baseline.json`; `scripts/bench-gate.ps1`; `benches/pipeline.rs`; `tests/loom_model.rs` | DESIGN P6 residual; C00 L6-L8; C07 L66-L68; C08 L74; perf-budget enforced Wave-26 #223; p95 latency enforced Wave-30 #256; FSM properties Wave-31 #261; soft loom Wave-31 #264; viewer corpus_paths/parquet/settings properties #425; viewer theme + daemon_url properties #426; full loom/shuttle unpaid |
| WBS-6.2 | P6 property tests, fuzzing, race checks, and enforced performance budgets | partial | machine | `tests/properties.rs`; `crates/sl-viewer/tests/properties_viewer.rs`; `crates/sl-viewer/tests/properties_viewer_theme_url.rs`; `crates/sl-viewer/tests/properties_viewer_unfinished_tab.rs`; `fuzz/fuzz_targets/okf_roundtrip.rs`; `fuzz/fuzz_targets/jsonl_ingest.rs`; `.github/workflows/ci.yml`; `.github/workflows/bench-gate.yml`; `docs/ops/perf-baseline.json`; `scripts/bench-gate.ps1`; `benches/pipeline.rs`; `tests/loom_model.rs` | DESIGN P6 residual; C00 L6-L8; C07 L66-L68; C08 L74; perf-budget enforced Wave-26 #223; p95 latency enforced Wave-30 #256; FSM properties Wave-31 #261; soft loom Wave-31 #264; viewer corpus_paths/parquet/settings properties #425; viewer theme + daemon_url properties #427; viewer unfinished_tab properties + fuzz/rootless CI drift fixes #428; full loom/shuttle unpaid |

## audit-v38 waves

Expand Down
10 changes: 7 additions & 3 deletions scripts/fuzz-cadence-check.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -169,10 +169,14 @@ if ($workflow -notmatch 'okf_roundtrip' -or $workflow -notmatch 'jsonl_ingest')
[void](Write-Check -Label "workflow exercises both fuzz targets" -Ok $true)

Write-Host "PR smoke stays short:"
if ($ci -notmatch 'max_total_time=10') {
throw "ci.yml fuzz-smoke must keep -max_total_time=10 (do not slow PR CI here)."
# The PR smoke contract was consolidated into fuzz-blocking.yml (C07 L67
# follow-up). The 10 s `ci.yml` `fuzz-smoke` job no longer exists; the
# blocking sustained PR budget is now `fuzz-blocking.yml` at 30 s / target.
# Enforce that here so PR CI doesn't silently lose its bounded PR fuzz.
if ($blockingWorkflow -notmatch 'max_total_time=30') {
throw "fuzz-blocking.yml must keep -max_total_time=30 for the PR fuzz budget (do not slow PR CI here)."
}
[void](Write-Check -Label "ci.yml fuzz-smoke max_total_time=10" -Ok $true)
[void](Write-Check -Label "fuzz-blocking.yml PR fuzz max_total_time=30" -Ok $true)
Comment on lines +176 to +179

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: This replacement check no longer validates the separate blocking ci.yml fuzz-smoke lane or its 10-second limit. Because $ci is no longer checked here, removing that job or weakening its budget still passes SelfCheck, despite the documentation continuing to define it as required blocking PR coverage. Keep an explicit ci.yml smoke-job anchor in addition to the sustained-workflow check. [incomplete implementation]

Severity Level: Major ⚠️
- ❌ Required 10-second PR fuzz coverage is not enforced.
- ⚠️ CI drift checks ignore the documented `ci.yml` lane.
- ⚠️ Fuzz validation changes from short smoke to sustained coverage.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** scripts/fuzz-cadence-check.ps1
**Line:** 176:179
**Comment:**
	*Incomplete Implementation: This replacement check no longer validates the separate blocking `ci.yml` `fuzz-smoke` lane or its 10-second limit. Because `$ci` is no longer checked here, removing that job or weakening its budget still passes SelfCheck, despite the documentation continuing to define it as required blocking PR coverage. Keep an explicit `ci.yml` smoke-job anchor in addition to the sustained-workflow check.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎


Write-Host "Fuzz cadence SelfCheck passed"
exit 0
10 changes: 9 additions & 1 deletion scripts/rootless-nonet-check.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,15 @@ Test-DocContains -Doc $ciWf -Needle "rootless-nonet.yml" `
Test-DocContains -Doc $ciWf -Needle "rootless-nonet-check.ps1" `
-Label "ci.yml references rootless-nonet SelfCheck script" -Context ".github/workflows/ci.yml"

if ($ciWf -match '(?ms)^ rootless-nonet-policy:.*?continue-on-error:\s*true') {
# Extract just the rootless-nonet-policy: block (until the next top-level
# job or end of file) so the continue-on-error check can't bleed across
# into unrelated jobs like `security:`. (?ms) = multi-line + dotall so
# `.*?` can span newlines.
$policyBlockMatch = [regex]::Match(
$ciWf,
'(?ms)^ rootless-nonet-policy:.*?(?=^ [A-Za-z][\w-]*:\s|\z)'
)
if ($policyBlockMatch.Success -and $policyBlockMatch.Value -match 'continue-on-error:\s*true') {
throw "ci.yml rootless-nonet-policy job must be blocking (no continue-on-error)."
}
[void](Write-Check -Label "ci.yml rootless-nonet-policy job is blocking when present" -Ok $true)
Comment on lines +134 to 145

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Fail when the policy job is absent.

The condition skips the error when $policyBlockMatch.Success is $false. If rootless-nonet-policy is removed, the comments in .github/workflows/ci.yml at Lines 191-194 can still satisfy the string checks at Lines 129-132. The script then reports a passing blocking-policy check at Line 145. Throw when the policy block is missing.

Suggested fix
-if ($policyBlockMatch.Success -and $policyBlockMatch.Value -match 'continue-on-error:\s*true') {
+if (-not $policyBlockMatch.Success) {
+    throw "ci.yml must define the rootless-nonet-policy job."
+}
+if ($policyBlockMatch.Value -match 'continue-on-error:\s*true') {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Extract just the rootless-nonet-policy: block (until the next top-level
# job or end of file) so the continue-on-error check can't bleed across
# into unrelated jobs like `security:`. (?ms) = multi-line + dotall so
# `.*?` can span newlines.
$policyBlockMatch = [regex]::Match(
$ciWf,
'(?ms)^ rootless-nonet-policy:.*?(?=^ [A-Za-z][\w-]*:\s|\z)'
)
if ($policyBlockMatch.Success -and $policyBlockMatch.Value -match 'continue-on-error:\s*true') {
throw "ci.yml rootless-nonet-policy job must be blocking (no continue-on-error)."
}
[void](Write-Check -Label "ci.yml rootless-nonet-policy job is blocking when present" -Ok $true)
# Extract just the rootless-nonet-policy: block (until the next top-level
# job or end of file) so the continue-on-error check can't bleed across
# into unrelated jobs like `security:`. (?ms) = multi-line + dotall so
# `.*?` can span newlines.
$policyBlockMatch = [regex]::Match(
$ciWf,
'(?ms)^ rootless-nonet-policy:.*?(?=^ [A-Za-z][\w-]*:\s|\z)'
)
if (-not $policyBlockMatch.Success) {
throw "ci.yml must define the rootless-nonet-policy job."
}
if ($policyBlockMatch.Value -match 'continue-on-error:\s*true') {
throw "ci.yml rootless-nonet-policy job must be blocking (no continue-on-error)."
}
[void](Write-Check -Label "ci.yml rootless-nonet-policy job is blocking when present" -Ok $true)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/rootless-nonet-check.ps1` around lines 134 - 145, Update the
validation around $policyBlockMatch in rootless-nonet-check.ps1 to throw when
the rootless-nonet-policy block is absent, rather than treating a failed match
as success. Keep the existing continue-on-error validation for a present block
and only call Write-Check with Ok $true after confirming the block exists and is
blocking.

Expand Down
31 changes: 18 additions & 13 deletions tests/alloc_profile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,22 +82,27 @@ fn alloc_profile_script_self_check_parses_args_and_ceilings() {
assert!(stdout.contains("Profiler: dhat"), "expected profiler echo, got:\n{stdout}");
}
Err(error) => {
// Windows can't fall back to a portable load + print, so the
// spawn failure is unrecoverable there. Other targets run the
// portable fallback below. The clippy `panic_in_if_then` lint
// requires the if-then to have an else branch — fold the
// fallback into `else` so the panic sits on the windows-only path.
if cfg!(target_os = "windows") {
panic!("failed to spawn pwsh for self-check: {error}");
} else {
let (max_bytes, total_blocks) = load_profile();
println!(
"pwsh unavailable; running portable alloc-profile SelfCheck fallback.\nSelf-check passed\nMax bytes ceiling: {max_bytes}\nTotal blocks ceiling: {total_blocks}\nProfiler: dhat"
Comment on lines 90 to +95

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The non-Windows fallback converts every pwsh spawn failure—including permission errors, invalid invocation failures, and other environment problems—into a fabricated successful SelfCheck result. This means the test can pass without executing scripts/alloc-profile-check.ps1 or validating its documented workflow and path anchors. Only use the fallback for an explicitly detected missing executable, or fail the test for other spawn errors. [possible bug]

Severity Level: Major ⚠️
- ⚠️ Linux tests can pass without executing alloc-profile SelfCheck.
- ⚠️ PowerShell workflow-anchor regressions may be missed.
- ⚠️ Non-missing-executable spawn failures are misreported as success.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** tests/alloc_profile.rs
**Line:** 90:95
**Comment:**
	*Possible Bug: The non-Windows fallback converts every `pwsh` spawn failure—including permission errors, invalid invocation failures, and other environment problems—into a fabricated successful SelfCheck result. This means the test can pass without executing `scripts/alloc-profile-check.ps1` or validating its documented workflow and path anchors. Only use the fallback for an explicitly detected missing executable, or fail the test for other spawn errors.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

);
Comment on lines +85 to +96

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n -C 8 'Command::new\("pwsh"\)|ErrorKind::NotFound|pwsh unavailable|failed to spawn pwsh' .

Repository: KooshaPari/SessionLedger

Length of output: 50380


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== alloc_profile relevant section =="
sed -n '54,110p' tests/alloc_profile.rs

echo
echo "== imports =="
sed -n '1,35p' tests/alloc_profile.rs

echo
echo "== cfg target_os windows patterns in Rust files =="
python3 - <<'PY'
from pathlib import Path
hits=[]
for p in Path('.').rglob('*.rs'):
    text=p.read_text(errors='replace')
    if 'cfg!(target_os = "windows")' in text or 'cfg!(target_os=\"windows\")' in text:
        for i,line in enumerate(text.splitlines(),1):
            if 'cfg!' in line and ('windows' in line):
                hits.append((str(p), i, line.strip()))
for p,i,l in hits:
    print(f"{p}:{i}:{l}")
PY

Repository: KooshaPari/SessionLedger

Length of output: 4433


Use the fallback only when pwsh is missing.

Command::output() returns an Err for any spawn failure, not only missing executables. In non-Windows targets, errors such as std::io::ErrorKind::PermissionDenied currently fall through to the portable fallback and print Self-check passed without running scripts/alloc-profile-check.ps1. Make the fallback conditional on error.kind() == std::io::ErrorKind::NotFound; panic for all other spawn errors.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/alloc_profile.rs` around lines 85 - 96, Update the non-Windows error
handling around the pwsh `Command::output()` call so the portable fallback runs
only when `error.kind() == std::io::ErrorKind::NotFound`; panic with the
existing failure context for all other spawn errors, including permission
failures. Preserve the Windows behavior and the successful output path.

assert!(
max_bytes >= 1024 * 1024,
"max_bytes ceiling should stay >= 1 MiB for debug smoke (got {max_bytes})"
);
assert!(
total_blocks >= 1_000,
"total_blocks ceiling should stay generous (got {total_blocks})"
);
}

let (max_bytes, total_blocks) = load_profile();
println!(
"pwsh unavailable; running portable alloc-profile SelfCheck fallback.\nSelf-check passed\nMax bytes ceiling: {max_bytes}\nTotal blocks ceiling: {total_blocks}\nProfiler: dhat"
);
assert!(
max_bytes >= 1024 * 1024,
"max_bytes ceiling should stay >= 1 MiB for debug smoke (got {max_bytes})"
);
assert!(
total_blocks >= 1_000,
"total_blocks ceiling should stay generous (got {total_blocks})"
);
}
}
}
12 changes: 6 additions & 6 deletions tests/replay_breadth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ fn w44_b6_each_generated_fixture_is_well_formed_okf_v1() {
Err(e) => bad.push((slug.to_string(), format!("json parse: {e}"))),
}
}
assert!(bad.is_empty(), "W44-B6 fixtures failed shape check: {bad:#?}",);
assert!(bad.is_empty(), "W44-B6 fixtures failed shape check: {bad:#?}");
assert_eq!(parsed, W44_B6_SLUGS.len(), "parsed count mismatch");
}

Expand All @@ -128,18 +128,18 @@ fn w44_b6_generator_script_present_and_importable() {
// The generator is a Python script, not part of the Rust crate, but its
// presence on disk is part of the W44-B6 deliverable.
let script = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("scripts/corpus-generate.py");
assert!(script.is_file(), "expected corpus generator at {}", script.display(),);
assert!(script.is_file(), "expected corpus generator at {}", script.display());
let raw = std::fs::read_to_string(&script).expect("read corpus-generate.py");
assert!(raw.contains("OKF_VERSION"), "generator must define OKF_VERSION");
assert!(raw.contains("FIXTURE_SPECS"), "generator must declare FIXTURE_SPECS",);
assert!(raw.contains("FAILURE_FIXTURES"), "generator must isolate failure-mode fixtures",);
assert!(raw.contains("FIXTURE_SPECS"), "generator must declare FIXTURE_SPECS");
assert!(raw.contains("FAILURE_FIXTURES"), "generator must isolate failure-mode fixtures");
}

#[test]
fn w44_b6_corpus_breadth_doc_present() {
let doc = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("docs/ops/corpus-breadth.md");
assert!(doc.is_file(), "expected docs/ops/corpus-breadth.md at {}", doc.display(),);
assert!(doc.is_file(), "expected docs/ops/corpus-breadth.md at {}", doc.display());
let raw = std::fs::read_to_string(&doc).expect("read corpus-breadth.md");
assert!(raw.contains("C08 L73"), "doc must reference C08 L73 pillar");
assert!(raw.contains("Wave-44"), "doc must reference Wave-44 (W44-B6) close-out",);
assert!(raw.contains("Wave-44"), "doc must reference Wave-44 (W44-B6) close-out");
}
Loading