Skip to content

v5 design: staged patch rollout (socket.yml + scan limit) - #290

Merged
Mikola Lysenko (mikolalysenko) merged 6 commits into
release/v5-prereleasefrom
v5/staged-rollout-plan
Sep 28, 2026
Merged

Mikola Lysenko (mikolalysenko) merged 6 commits into
release/v5-prereleasefrom
v5/staged-rollout-plan

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Sep 28, 2026 •

Copy link
Copy Markdown
Collaborator

Design only; no code. This PR adds docs/design/staged-rollout.md, updates docs/design/configuration.md (socket-patch now reads socket.yml for selection policy, with the trust boundary kept), and adds a WS9 entry to docs/design/v5-plan.md.

What the plan proposes

  • patches: block in socket.yml (version: 2, new top-level key; every existing parser strips or ignores it). Keys: enabled, includePaths, ignorePaths, ecosystems, packages, ignorePackages, minSeverity, maxNewPatches. Paths use the same gitignore semantics as projectIgnorePaths, which scan now also honors.
  • Hard-coded filter moves: the in-memory engine's test/tests/fixtures/__fixtures__/testdata root exclusions become overridable default ignores, applied on disk too. Every other hard-coded skip is safety, entitlement or correctness and stays (inventory in section 2.3, both repos).
  • scan --max-new-patches <N|none> (and the maxNewPatches key): caps patches added to packages that have none yet. Order: real severity, then advisory count, ecosystem, purl, uuid. Upgrades are exempt. Patches that can't land this run don't hold a slot. Repeated runs converge. Deferred patches are reported in a new rollout JSON block; exit code unchanged.
  • Fail closed: an invalid policy stops scan before any write (socket_yml_invalid, exit 1). Narrowing never removes or upgrades an already-applied patch.
  • Two parallel work items: A (policy loading and filters) and B (limit, ordering, reporting), with a frozen interface (section 9.0). Merge order is A then B.
  • depscan autopatch follow-up: section 7.

Candidate designs came from three angles (minimal, safety, user journey). Three independent judges scored them, and the result is a synthesis. Section 8 records each decision and why.

🤖 Generated with Claude Code


Generated by Claude Code


Note

Low Risk
Documentation-only; no runtime or behavioral changes until follow-up implementation PRs land.

Overview
Design-only — no implementation in this PR. It adds the v5.0 staged rollout spec and aligns configuration docs with that direction.

New: docs/design/staged-rollout.md defines repo-owned gradual patching: a top-level patches: block in socket.yml (under version: 2) for paths, ecosystems, packages, severity floor, enabled, and maxNewPatches; scan --max-new-patches for severity-ordered caps on new patches (upgrades exempt); fail-closed validation; policy / rollout JSON blocks; and two parallel work items (A policy, B limit) with a frozen pipeline contract and depscan follow-ups.

Updated configuration.md: Reverses the v3.5 rule that socket-patch never reads socket.yml — v5 will read projectIgnorePaths plus patches only, with a trust boundary that repo files may narrow or pace but never widen or carry credentials/endpoints. Invalid patches blocks fail scan (exit 1); manifest setup.defaults is dropped from the deferred plan in favor of socket.yml.

Updated v5-plan.md: Adds WS9 (branches v5/rollout-policy / v5/rollout-limit, merge A then B).

Reviewed by Cursor Bugbot for commit c434039. Configure here.

Adds the design for rolling Socket patches out gradually: a
`patches:` block in socket.yml that narrows what scan may patch
(paths, ecosystems, packages, severity floor, on/off), and a
severity-ordered per-run cap on new patches so each scan lands the
next few most critical fixes.

The plan splits the work into two parallel items with a frozen
interface, lists every hard-coded filter and where it belongs, and
covers the depscan autopatch follow-up. configuration.md now records
that socket-patch reads socket.yml for selection policy only, with
the trust boundary unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Three independent reviews (ambiguity, churn, trust boundary) found
gaps that would have let the two implementations disagree or let a
repo file widen or stall the rollout. The plan now:

- matches paths against marker files with the backend's top-down
  gitignore rules, so projectIgnorePaths means the same everywhere
- uses one data source for severity, supersession and ordering
- spends the budget only on patches the planning pass proves can
  land, and admits nothing new when a lookup failed
- uses the merged recorded view in every mode and engine
- has depscan read the policy from the base commit for PR jobs
- hardens file handling (regular files, aliases, size, encoding,
  trusted repo root) and reports what a policy hides

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A final consistency pass found places where the two work items
would have produced incompatible code: base purls admitted in one
directory being charged again in the next, no defined hand-off of
the unfiltered offers from the severity filter to classification,
no shared repo-relative path helper, and override sources the JSON
must report but the interface could not carry. The shared contract
now defines each of these, and the parity tests match each engine's
budget scope.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The plan said both that the selector returns the shared offers
struct (work item A) and that it returns admitted/deferred rows
(work item B). The selector now returns the offers, and B adds the
rollout stage after it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@mikolalysenko

Mikola Lysenko (mikolalysenko) commented Sep 28, 2026 •

Copy link
Copy Markdown
Collaborator Author

[agent] plan final

Summary. The staged-rollout plan is final at 82f5b00 on v5/staged-rollout-plan: docs/design/staged-rollout.md (full design) and docs/design/configuration.md §4 (socket.yml trust boundary).

  • socket.yml: a new top-level patches: block, kept at version: 2 so every existing parser strips or ignores it. Keys: enabled, includePaths, ignorePaths, ecosystems, packages, ignorePackages, minSeverity, maxNewPatches.
  • Paths: matched against project marker files with the backend's gitignore rules, evaluated top-down. projectIgnorePaths is also honored. The hard-coded test/tests/fixtures/__fixtures__/testdata root exclusions become overridable defaults.
  • Validation: strict and fail-closed (socket_yml_invalid, exit 1). Narrowing never removes or upgrades an applied patch.
  • scan --max-new-patches <N|none>: caps new patches per run, ordered by severity, then advisory count, ecosystem, purl and uuid. Upgrades are exempt. The budget is spent only on patches the planning pass proves can land. Repeated runs converge. Deferred patches are reported in a new rollout block; the exit code is unchanged.
  • Build order: work items A and B are built in parallel against the shared contract below. Merge order is A then B, and B integrates.
  • depscan follow-up: §7.3 of the doc.
  • Process: three candidate designs were scored by three judges. Three adversarial reviews and one final consistency pass followed, and all their findings are folded in.

Updated at 82f5b00: in-memory path selection is now two-phase (the caller passes the root socket.yml text to selectHostedScanPaths, which applies the full path policy), per the Bugbot finding on §7.2. The work-item specs are otherwise unchanged.

Below are the work-item specs A and B in full (§9, verbatim). The sections they cite (§3–§5 semantics, §7.2 engine API) are normative; read them in docs/design/staged-rollout.md at 82f5b00.

Work items A and B (§9, verbatim)

9.0 Shared contract (frozen by this plan)

Scan pipeline, in order (disk and memory):

  1. load policy (A); fail closed before any write
  2. crawl; capture the prune universe (unchanged)
  3. root filter, ecosystem/package filter, retained set (A)
  4. batch API, by-package details (unchanged fetches)
  5. candidate severity filter on by-package records (A); discover_selected
    returns Offers (below) so B sees both the unfiltered and the
    floor-filtered candidates
  6. per-package ranking (unchanged ranking)
  7. classify, planning pass for eligibility, budget, deferral (B)
  8. writers (receive only admitted NEW rows, ALREADY rows with the recorded
    uuid, and UPGRADE rows)
// crates/socket-patch-core/src/policy/mod.rs — OWNER A
pub struct SelectionPolicy { /* private fields */ }
pub enum PolicySource { None, File { path: String, sha256: String }, Bypassed }
pub enum FilterReason {
    Disabled, PathExcluded { pattern: String, list: &'static str }, PathNotIncluded,
    Ecosystem, PackageNotListed, PackageIgnored { spec: String },
    Severity { found: Option<String>, floor: String },
}
impl FilterReason { pub fn code(&self) -> &'static str; pub fn detail(&self) -> String; }
pub enum PolicyError {
    Invalid { file: String, key: String, message: String },
    Ambiguous { files: [String; 2] },
}
impl PolicyError { pub fn code(&self) -> &'static str; } // socket_yml_invalid | socket_yml_ambiguous
pub enum RootFile { Absent, Present(Vec<u8>), PresentWithoutContent }
pub trait PolicyFs { fn read_root_file(&self, name: &str, cap: usize) -> std::io::Result<RootFile>; }
pub enum OverrideSource { Flag, Env }
pub struct PolicyOverrides { pub bypass: bool, pub min_severity: Option<(Option<u8>, OverrideSource)> } // (None, _) = "none"
pub struct PolicyWarning { pub code: &'static str, pub detail: String }
pub struct Root<'a> { pub rel_dir: &'a str, pub markers: &'a [String], pub explicit: bool }
impl SelectionPolicy {
    pub fn unrestricted() -> Self;                          // built-in default ignores only
    pub fn load(fs: &dyn PolicyFs, o: &PolicyOverrides) -> Result<(Self, Vec<PolicyWarning>), PolicyError>;
    pub fn source(&self) -> &PolicySource;
    pub fn enabled(&self) -> bool;
    pub fn admits_root(&self, root: &Root) -> Result<(), FilterReason>;
    pub fn admits_purl(&self, purl: &str) -> Result<(), FilterReason>;        // ecosystem + packages
    pub fn admits_severity(&self, severity_order: u8) -> Result<(), FilterReason>;
    pub fn max_new_patches(&self) -> Option<u32>;  // the file's value; None when source() is None or Bypassed, or the key is absent
}
pub fn package_spec_matches(spec: &str, purl: &str) -> bool; // moved from cli scan/mod.rs:383
pub fn find_repo_root(cwd: &Path) -> PathBuf;                // 4.5
pub fn repo_relative(repo_root: &Path, dir: &Path) -> String; // "" for the repo root, `/` separators

// crates/socket-patch-core/src/policy/mod.rs — OWNER A (the step 5 → 7 seam)
pub struct Offers {
    pub unfiltered: BTreeMap<String, Vec<PatchSearchResult>>, // purl → every offer (after tier)
    pub selected: BTreeMap<String, PatchSearchResult>,        // purl → winner among floor-admitted offers
}

// crates/socket-patch-core/src/rollout.rs — OWNER B
pub enum Recorded { None, Same, Kept { uuid: String }, Superseded { old_uuid: String } }
pub struct Candidate {
    pub project: String, pub purl: String, pub base_purl: String, pub uuid: String,
    pub ecosystem: &'static str, pub severity_order: u8, pub advisory_count: usize,
    pub recorded: Recorded, pub eligible: bool, pub in_flight: bool,
}
pub enum MaxNewSource { Flag, Env, File, Default, Cap }
pub struct MaxNew { pub value: Option<u32>, pub source: MaxNewSource }
pub fn resolve_max_new(flag: Option<Option<u32>>, env: Option<Option<u32>>,
                       file: Option<u32>, cap: Option<u32>) -> MaxNew;
pub fn canonical_base_purl(purl: &str) -> String;
pub fn rollout_cmp(a: &Candidate, b: &Candidate) -> std::cmp::Ordering;
pub struct RolloutCounts { pub new: u32, pub deferred: u32, pub upgrade: u32, pub already: u32 }
pub struct RolloutPlan {
    pub admitted: Vec<Candidate>, pub deferred: Vec<(Candidate, u32)>,
    pub counts: RolloutCounts,
    pub remaining: Option<u32>,                       // carried to the next directory
    pub admitted_base_purls: BTreeSet<String>,        // carried too
}
// Rows whose base_purl is in `already_admitted` are admitted without spending budget.
pub fn plan_rollout(candidates: Vec<Candidate>, max_new: &MaxNew, incomplete: bool,
                    already_admitted: &BTreeSet<String>) -> RolloutPlan; // pure

// crates/socket-patch-core/src/api/ranking.rs — OWNER B (addition)
pub fn search_result_supersedes(candidate: &PatchSearchResult, recorded: &PatchSearchResult) -> bool;

Rules both items follow:

  • Severity input is always max_severity_order over the by-package
    record's vulnerabilities, never RankKey.severity, never the batch
    list.
  • Skip-reason strings are the stable codes in 4.7 and 5.5; warnings go to
    scan's top-level warnings[].
  • JSON: A owns the top-level policy block; B owns the top-level
    rollout block. Neither edits the other's.
  • CLI args: A adds a #[command(flatten)] SocketYmlArgs
    (--no-socket-yml, --min-severity) in scan/socket_yml_args.rs; B
    adds a flattened RolloutArgs (--max-new-patches) in
    scan/rollout_args.rs. Both derive Default; each adds its field to
    the ~18 ScanArgs struct literals. B resolves the adjacent-line
    conflicts on rebase.
  • run_project_dirs changes: A adds the per-directory explicit flag;
    B adds the carried remaining budget. B resolves the overlap on rebase.

9.1 Work item A — socket.yml loading and filtering

Scope:

  • crates/socket-patch-core/src/policy/{mod.rs, socket_yml.rs, paths.rs};
    pub mod policy; in crates/socket-patch-core/src/lib.rs; move
    package_spec_matches to core (the cli re-uses it).
  • Dependencies, exact-pinned in Cargo.toml: a maintained YAML 1.2 serde
    crate that reports duplicate keys and can refuse aliases and bound depth
    (e.g. serde_norway; prove each property with a test, pick another
    crate otherwise), and ignore (for Gitignore::matched; the top-down
    walk is ours). No other new deps.
  • Loader: lookup (4.5, incl. ceiling dirs and ownership), regular-file,
    size and symlink confinement on the opened handle, exact-name match,
    encoding, both-files rule, strict validation with key paths and
    did-you-mean (4.4), PolicyFs for disk and memory.
  • Path matcher (4.3): marker-file subject, npm-ignore top-down
    semantics, defaults + lists in order, includePaths, pattern hygiene;
    golden fixture generated from npm ignore (commit the generator script
    under scripts/ and the fixture under crates/socket-patch-core/tests/).
  • Filters at the pipeline points in 9.0:
    • disk: root filter in project_dirs / run_project_dirs
      (scan/mod.rs:1268-1320, carrying explicit) and the agent project;
      admits_purl next to --package (scan/mod.rs:1490-1511); severity
      filter on by-package candidates before select_patches
      (discover_selected, scan/mod.rs:553, and the human arm,
      mod.rs:2386-2402); retained set computed from the recorded view and
      excluded from writers.
    • memory: full path policy in selectHostedScanPaths
      (hosted_memory/select.rs), from the caller-supplied policyFiles
      text (7.2, two-phase); the same root filter in the session before
      max_projects (hosted_memory/mod.rs:377); admits_purl at
      hosted_memory/mod.rs:428-432; severity filter before
      select_top_ranked.
  • Move test tests fixtures __fixtures__ testdata out of
    EXCLUDED_ROOT_SEGMENTS (hosted_memory/roots.rs:56-67) into the
    built-in default ignores, and apply them to disk PATH-glob expansion.
  • enabled: false report-only path; get's policy_bypassed warning;
    --global ignores the file; PATHs outside the repo root → exit 2.
  • Flags: --no-socket-yml/SOCKET_NO_SOCKET_YML,
    --min-severity/SOCKET_MIN_SEVERITY (SocketYmlArgs).
  • napi + hosted-bundle (7.2, A rows); npm/index.d.ts types.
  • JSON policy block (4.7), human policy line (naming suppressed
    critical/high), error output with errorCode, warnings
    socket_yml_ignored_value, socket_yml_name_case,
    socket_yml_repo_untrusted, patches_disabled, policy_bypassed;
    output string hygiene.

Tests:

  • Unit (core, table-driven): every row of 4.4 in order; the npm-ignore
    golden fixture (anchoring, bare names, trailing /, !, excluded
    parents, case); marker rule (all markers ignored / any included);
    defaults + negation; explicit vs discovered; package specs incl.
    invalid ones; severity floor incl. unknown and moderate; both-files
    equal / different / one invalid; lookup with .git dir, .git file,
    none, GIT_CEILING_DIRECTORIES, foreign-owned .git; symlink inside
    and outside, directory, FIFO; alias bomb; oversize; BOM, CRLF, UTF-16.
  • Parser contract: tests/cli_parse_scan.rs rows for both flags and env
    vars (empty = unset, malformed = exit 2).
  • E2E (wiremock, tests/in_process_scan.rs style): hosted, vendored,
    agent and --dry-run with a socket.yml filtering by path, ecosystem,
    package and severity; invalid file → exit 1, errorCode, no bytes
    changed; --no-socket-yml; narrowing after a patch is applied leaves the
    pinned package byte-identical in all three modes (retained); a recorded
    merged patch below a new floor is kept, not replaced; --prune universe
    unchanged; PATH outside the repo → exit 2.
  • Parity: tests/hosted_memory_parity.rs gains a socket.yml fixture
    (single-lockfile roots) where disk and memory filter the same roots and
    packages; a memory test where the tree lists socket.yml but its content
    is withheld → policyError; a memory test where
    ignorePaths: ["!/e2e/tests/"] re-includes a default-ignored root, so
    its lockfile is selected, fetched and patched as on disk.
  • This repo's own socket.yml keeps working (its projectIgnorePaths
    now also excludes the fixtures from patching).

Docs (A): CLI_CONTRACT.md (new "socket.yml patch policy" section:
grammar, precedence, paths, lookup, validation, commands; flag + env rows;
error codes; policy JSON block; the trust-boundary bullet gains the
"narrow or pace" sentence), README (scan section: "Roll out gradually"
with recipes R1-R4, R6), CHANGELOG [Unreleased] (Added: socket.yml
patch policy; Changed (BREAKING): scan honors projectIgnorePaths,
default test/fixture ignores on discovered roots, invalid socket.yml with
a patches block fails scan).

9.2 Work item B — limit, ordering, reporting

Scope:

  • crates/socket-patch-core/src/rollout.rs; pub mod rollout; in
    crates/socket-patch-core/src/lib.rs; search_result_supersedes in
    ranking.rs, and detect_updates / updates[] switched to by-package
    supersession; move the detect_updates call (today scan/mod.rs:1912,
    on batch data) after discover_selected so it receives the by-package
    offers.
  • Make discover_selected (scan/mod.rs:553) the single disk selection
    point: route the human agent/vendored arm (mod.rs:2386-2402) through
    it, and add the step-7 stage after it (classify its Offers, planning
    pass, plan_rollout) yielding {admitted, deferred}, so hosted
    (run_redirect_selected, hosted.rs:1196), vendored and agent writers
    receive only the rows 9.0 step 8 allows (ALREADY with the recorded
    uuid). discover_selected's return type is A's Offers.
  • Classification from the merged recorded view (5.1); the planning pass
    for eligibility (hosted: grants, purl/url, vlt preflight, symlink
    refusals, rewriter planning; vendored: preflight; agent: partition);
    fetch-all references; rollout_reference_failed and
    rollout_incomplete_lookup; plan_rollout; the remaining budget
    carried through run_project_dirs in sorted directory order.
  • In-memory engine (7.2, B rows): pin discovery and state-file reads,
    collect → plan → apply, options and result fields, npm/index.d.ts,
    hosted-bundle fields.
  • Flag: --max-new-patches <N|none>/SOCKET_MAX_NEW_PATCHES
    (RolloutArgs); resolve_max_new including the file value from A
    (9.3).
  • JSON rollout block (5.5), redirect.skipped[] mirror, human
    "Rollout:" line and the Next-steps deferred line (hosted
    format_next_steps, hosted.rs:3457, and the agent/vendored
    summaries).

Tests:

  • Unit (core): rollout_cmp total order (property test: every
    permutation sorts the same); plan_rollout caps only eligible NEW,
    counts base purls, one package across roots costs 1, 0 = no NEW, none
    = unlimited, ineligible rows hold no slot, incomplete admits nothing
    NEW, in-flight first, remaining budget; resolve_max_new precedence
    table incl. cap on none; canonical_base_purl twins;
    search_result_supersedes rungs.
  • E2E (wiremock): hosted, vendored, agent, --dry-run: 9 candidates with
    --max-new-patches 3 apply the 3 most severe; a rerun on the result
    applies the next 3; a third run the last 3; a fourth changes nothing;
    dry-run output equals the wet run's decisions; upgrades land regardless
    of the cap; a withdrawn, a bad_purl and a vlt-withheld top-ranked
    patch hold no slot; a failed detail lookup with a cap admits nothing
    NEW; two PATH directories share one budget in sorted order; JSON
    rollout and redirect.skipped[]; exit 0.
  • Parity: hosted_memory_parity.rs single-root cap fixture: disk and
    memory admit and defer the same rows. Two-root fixture: assert memory's
    run-wide order and disk's per-directory order separately (they differ by
    design, 5.2). A memory rerun with pins (and with a committed
    manifest / vendor state) lands the next N.
  • Parser contract rows for the flag and env var.

Docs (B): CLI_CONTRACT.md (limit semantics: classification,
eligibility, unit, budget scope, order, convergence, version-bump and
twin notes, starvation and non-committing CI; flag + env rows; rollout
block; rollout_deferred; warnings; jq recipe; "Which patch gets
selected" gains the cross-package order and the by-package supersession
change), README (recipe R5, --max-new-patches), CHANGELOG
[Unreleased] Added (and Changed: updates[] uses by-package data).

9.3 Integration (B, after rebasing on A)

  • Pass policy.max_new_patches() as the file layer of resolve_max_new.
  • Resolve the ScanArgs struct-literal and run_project_dirs conflicts.
  • Combined e2e: a socket.yml with includePaths, minSeverity: high and
    maxNewPatches: 2 over a two-root fixture, disk and memory, three runs to
    convergence, asserting each engine's own budget scope (5.2);
    --no-socket-yml drops the file's cap but keeps a flag cap.
  • Switch Candidate.project to A's repo_relative and consume A's
    Offers (before A lands, B uses canonical --cwd as the repo root and
    treats the selected offers as the unfiltered list).
  • If B is ready before A merges, B ships with the file layer passed as
    None and wires it in a follow-up commit on its branch once A lands.

Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] CI note: test (windows-latest) fails on ff7a30a, in the target -p socket-patch-cli --test covgap_commands_scan_mod.

This PR changes only three Markdown files under docs/design/, so it can't cause a Rust test failure. The base branch is already red: CI run 36352437716 on release/v5-prerelease (8ae7dc3, PR #277) also failed test (windows-latest), along with test (ubuntu-latest), test (macos-latest), coverage and test-release.

I don't know of an existing fix to port. I'll re-run the failed job once, after this CI run finishes.


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

#283 landed on release/v5-prerelease as 06437d2; please merge origin/release/v5-prerelease again, resolve conflicts, get green, and keep it ready.


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] test (windows-latest) fails in -p socket-patch-cli --test covgap_commands_scan_mod, on the first run and on its one rerun. This PR changes only docs.

The same target failed on Windows in base PR #283's own last CI run (job 108911094705), and #283 is now in release/v5-prerelease. That makes this a base-branch failure, not one this PR causes. Ubuntu and macOS pass. No fix exists yet.


Generated by Claude Code

@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review September 28, 2026 15:32
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] ready to land

The plan is final and CI is green except test (windows-latest), which also fails on the base branch (see the comment above). This PR changes only docs.


Generated by Claude Code

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is ON. A cloud agent has been kicked off to fix the reported issue.

Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit c434039. Configure here.

Comment thread docs/design/staged-rollout.md Outdated
Bugbot on #290: the plan had the in-memory engine's path selector
apply only the built-in test/fixture ignores, because it runs before
socket.yml is read. A negation such as `!/e2e/tests/` could then
never bring those trees back in depscan, while it works on disk.

Selection is now two-phase: the caller fetches the root policy
file(s) first and passes their text to selectHostedScanPaths, which
applies the full path policy. A listed policy file that is not passed
fails closed. A new memory test covers the negation case.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] Plan amendment for work item A, at 82f5b00. I edited the plan final comment above in place.

In-memory path selection is now two-phase. depscan fetches the root socket.yml/socket.yaml first and passes the text to selectHostedScanPaths in a new option, policyFiles. The selector then applies the full path policy, not just the built-in defaults, so an ignorePaths negation works in memory the same as on disk. A listed policy file that is not passed fails closed with policyError. See §7.2 (A row), §7.3 item 2, and the §9.1 memory bullet and memory test. Work item B is unaffected.


Generated by Claude Code

@mikolalysenko
Mikola Lysenko (mikolalysenko) merged commit 0f2de18 into release/v5-prerelease Sep 28, 2026
84 of 85 checks passed
@mikolalysenko
Mikola Lysenko (mikolalysenko) deleted the v5/staged-rollout-plan branch September 28, 2026 16:36
Mikola Lysenko (mikolalysenko) pushed a commit that referenced this pull request Sep 28, 2026
…etup-and-ui

Take the base's configuration.md deferred-defaults paragraph, which
already accounts for `setup` being removed in v5.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PQaKzoW5dSw9u5pgAAvVRj
Mikola Lysenko (mikolalysenko) pushed a commit that referenced this pull request Sep 28, 2026
…ed-engine

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KHZ8uzdXfkG2zH8ZYDG8ju
Mikola Lysenko (mikolalysenko) pushed a commit that referenced this pull request Sep 28, 2026
Brings in the squashed staged-rollout plan (#290), the e2e-tier fix
(#288: Windows path separators in a scan test, CI cancel rules) and
the docs-only design PRs. The plan doc's later edits (two-phase
policy selection for the in-memory engine) merge with this branch's
implementation notes unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko) pushed a commit that referenced this pull request Sep 28, 2026
Brings in the merged staged-rollout plan (#290), the Windows scan
test fix (#288) and the docs-only design PRs. The plan's late
two-phase memory selection is implemented in the next commit.

Claude-Session: https://claude.ai/code/session_01BKsyzefGhAnPkYmXCwq3H3
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] Work item B status: #294 (v5: cap new patches per scan, most critical first) is ready to land.

  • Implements: §5, §7.2 (B rows), §9.2 and §9.3. There's a per-run cap on new patches, and eligibility is checked before any budget is spent. Deterministic ordering (severity, advisory count, ecosystem, purl, uuid) means repeated scans converge without churn. Upgrades are uncapped, and there are rollout JSON and human reports. Disk and memory behave the same.
  • Wired to A: A's patches.maxNewPatches sets the file layer. The combined socket.yml + cap e2e (§9.3) passes on disk and in memory.
  • Gap decisions: listed in docs/design/staged-rollout.md §11 and in the v5: cap new patches per scan, most critical first #294 description.
  • Integration: merged with the base at 73c0c4f and with A at 4b71112.
  • CI: the only red checks are four vlt install-proof jobs (vlt 0.0.0-1 and 0.0.0-11). They also fail on the base's own run at 28cebf7.

Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] Work item A (socket.yml patch config and filtering) is ready to land as #293. It targets release/v5-prerelease.

What it adds

  • crates/socket-patch-core/src/policy/ holds the frozen §9.0 contract:
    • a strict parser for the patches block, covering enabled, includePaths, ignorePaths, ecosystems, packages, ignorePackages, minSeverity, and maxNewPatches (validated here, enforced by B);
    • a gitignore-semantics path matcher, pinned by a golden fixture generated from npm ignore.
  • The policy applies across hosted, vendored, agent and --dry-run scans, and in the in-memory engine through §7.2's two-phase selection.
    • The handshake uses policySha256, so a session that reads a policy different from what selection saw fails closed.
  • The test/fixture directory defaults moved out of the in-memory engine's hard-coded list into the policy. They apply to discovered roots only and can be re-included with a negation.
  • Flags only narrow further:
    • --min-severity / SOCKET_MIN_SEVERITY beats the file's floor.
    • --no-socket-yml / SOCKET_NO_SOCKET_YML ignores the file.
  • A broken or ambiguous file fails before any request or write. The error codes are socket_yml_invalid and socket_yml_ambiguous.
  • Narrowing never removes a patch that is already recorded. Such packages are reported in policy.retained[].
  • Trust boundary: nothing in socket.yml can set endpoints, credentials or modes, or turn off safety checks. Keys like that are rejected as unknown.

Decisions where the plan left a gap: recorded in docs/design/staged-rollout.md §9.4, items 1–7. The items B picks up are:

  • the in-memory retained[] view;
  • search_result_supersedes for the floor-vs-recorded rule;
  • README recipes R1/R5.

CI: green except failures the base branch already has:

  • vlt 0.0.0-1 / 0.0.0-11: hosted-rollback tests;
  • yarn-classic 1.0.2 / 1.6.0 / 1.7.0 / 1.9.4: mode_migration_npm expects an integrity line.

No fix exists yet for either.


Generated by Claude Code

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants