Skip to content

fix(ci): run Web tests and preserve audit evidence (successor to #169) - #198

Merged
PeterGuy326 merged 19 commits into
mainfrom
codex/122-pr169-ci-successor
Sep 17, 2026
Merged

PeterGuy326 merged 19 commits into
mainfrom
codex/122-pr169-ci-successor

Conversation

@PeterGuy326

Copy link
Copy Markdown
Collaborator

Tracking record and provenance

Refs #122 — bounded CI/release-readiness follow-up, not completion or publication
of that release. Canonical draft successor to #169, with the original author's
sun-970 commits and maintainer follow-ups retained unchanged in ancestry.
The original PR stays open; its reviews and discussion remain authoritative
history, not an approval of this successor.

  • Original/final source head: 5c3a4a75ab96bed4c046c0822ebc76a87f215cda.
  • Successor head: 5c3a4a75ab96bed4c046c0822ebc76a87f215cda.
  • Source and successor tree: 9d8a127375fd8e945113e06ae88983a1328b9fae.
  • Current canonical main: 2986fe38175f54d99f15dd38a498708c6ecd88cd, already
    an ancestor. A normal merge reports Already up to date; no empty commit,
    cherry-pick reconstruction, rebasing, or force push was used.
  • The audited lockfile retains chore(web): refresh audited development dependencies #192's provenance from
    11e02e21ef2c3dbd2dae26e4376872e54e78ecb5 (cherry-pick -x in ancestry).

Summary and acceptance criteria

The Web CI job must run its unit tests. Recognized transient npm audit failures
may retry, but vulnerabilities, unknown errors, failed spawns, signals and
timeouts must remain failures. Successful audit output must remain visible,
including below-threshold findings. The Windows evidence helper must complete
its report and preserve a nonzero audit exit status.

This successor changes no source file relative to #169's exact source head.
It exists because the canonical security workflow skips the entire CodeQL job
on fork PRs while main requires three language-specific CodeQL contexts. A
same-repository candidate executes the existing supported path; no security
workflow, permission, required check, audit threshold, or fork restriction is
modified or waived.

Changed files against main: .github/workflows/ci.yml, CHANGELOG.md,
scripts/test_win_audit_verify.mjs, scripts/win-audit-verify.bat,
web/audit-retry.mjs, web/audit-retry.test.mjs, web/package.json,
web/package-lock.json, web/vite.config.ts.

Validation ledger

Environment: isolated Linux aarch64, Linux 6.8, GNU find 4.9.0/coreutils 9.4,
Bash 5.2.21, Node 24.13.0/npm 11.6.2; checksum-verified official Node archive.
Fresh Git clone from an exact local bundle, two CPUs, GOMAXPROCS=2 and
GOFLAGS=-p=1. Tests use disposable fixtures, not production data or secrets.

Command/check Expected Actual
git diff --exit-code 5c3a4a75ab96bed4c046c0822ebc76a87f215cda HEAD Exact source tree PASS, no differences
git merge-base --is-ancestor 2986fe38175f54d99f15dd38a498708c6ecd88cd HEAD Current main included PASS
From root: bash scripts/test_release_guards.sh Release guards fail closed PASS
bash scripts/test_release_helpers_compat.sh Collection compatibility PASS
bash scripts/test_validate_release_action_pins_compat.sh Official pinned actions and compatibility PASS
bash scripts/validate_release_version.sh 0.1.1 Version surfaces agree PASS
From web/: npm ci --registry=https://registry.npmjs.org --fetch-timeout=45000 Locked install PASS, 401 installed packages
npm run audit --registry=https://registry.npmjs.org Both unchanged thresholds pass PASS, zero vulnerabilities, both reports printed
npm test Application and audit regressions pass PASS, 104 tests / 7 files, including 37 audit regressions
npm run lint / npm run typecheck / npm run build Static validation and production build pass PASS, existing large-chunk warning only
git -c core.whitespace=blank-at-eol,blank-at-eof,space-before-tab,cr-at-eol diff --check 2986fe38175f54d99f15dd38a498708c6ecd88cd HEAD Clean diff, intentional batch CRLF retained PASS
Canonical successor CI and CodeQL All required named contexts execute and pass PENDING at draft creation; no all-green claim

Tests, coverage and known limits

The original successful-report regression was red before the fix (1 failed /
36 passed). Existing exact-source Web CI
passed 104 tests including 37 audit regressions. Existing real
Windows CI
passed four native batch-helper regressions, including negative controls for
both original defects. These are source-head evidence, not independent review
or substitute results for the new canonical workflow execution.

Coverage percentage was not remeasured. A real-registry Windows audit has not
been requalified; its helper tests use local npm.cmd fixtures. The unchanged
five-second process fixture timeout reproduces on both pre-follow-up and fixed
heads on the managed macOS host; Linux is the validated environment. The
existing large-chunk build warning remains. Full Linux process/database and
browser acceptance, and native Windows execution, are delegated to unchanged
CI; no temporary Go executable runs on macOS.

Risk, rollback and review

Build/CI/dependency maintenance; no application API, CLI/MCP authorization,
storage or migration contract changes. [Unreleased] already documents the
behavior. The PR-triggered workflows reference no custom repository secrets;
CodeQL retains only its existing security-events upload permission. No release,
npm publication, Scorecard dispatch, tag, merge, or formal approval is performed.
Rollback is to leave this draft unmerged; no deployed behavior changes.

Draft until the exact candidate has required CI plus independent review. The
Owner reports independent review of the fix deltas is already in progress;
this description does not represent that review as completed. Automated
assistance produced validation and the maintainer follow-up; the original
human-authored history remains intact.

liyuanyang and others added 11 commits September 6, 2026 10:53
The Web CI job ran npm audit but never ran npm test, leaving the six
vitest files under web/src/ without pipeline coverage. The audit step
also failed the entire Web leg on the first transient registry error
(503, timeout, ECONNRESET) with zero retries.

- ci.yml: add "Run unit tests" step (npm test) after audit
- audit-retry.mjs: distinguish network errors (retry up to 3x with
  10s backoff) from real vulnerability reports (fail immediately)
- web/package.json: route audit script through audit-retry.mjs
spawnSync returns null status when npm is killed by signal or not found
on PATH. Three return sites in runWithRetry passed null through to
process.exit, which Node treats as 0 — failing the audit gate silently.

Add `?? 1` at the two unguarded sites (:67, :85) and remove the
redundant 4th spawnSync after loop exhaustion (review finding #1),
reusing the last loop result with the same `?? 1` guard. All three
sites now aligned.
The 60s ATTEMPT_TIMEOUT_MS kills npm before its default 300s
fetch-timeout fires, so the retry logic never sees "network timeout"
for slow registry hangs. Adding --fetch-timeout=45000 to both audit
args lets npm report the timeout within the 60s budget, making
isNetworkError catch it and trigger retries as designed.
- Assert vite.config.ts includes audit-retry.test.mjs so removing it
  fails the suite instead of silently dropping 35 tests.
- Assert --fetch-timeout < spawn timeout so the dead-zone fix stays
  enforced if either constant drifts.
Captures git head, npm_execpath probe, full npm run audit output,
and exit code. Must be run from cmd.exe (not WSL) to prove the
win32 branch in audit-retry.mjs:117 receives npm_execpath.
Windows cmd.exe requires CRLF; LF-only caused every line to be
misread as an unknown command.
@PeterGuy326

Copy link
Copy Markdown
Collaborator Author

Independent automated preflight — PASS, bounded audit/CI fix at 5c3a4a75ab96bed4c046c0822ebc76a87f215cda.

This successor preserves the exact authored source tree reviewed on #169. Independent reviewer Ohm reproduced successful-audit output loss on the old implementation, verified stdout/stderr retention on this candidate, and passed all 37 focused tests. Native Windows CI independently shows four process-fixture passes, including exit-code and missing-CALL/ENDLOCAL negative controls; this is not live-registry Windows audit qualification. The shared lockfile is identical to reviewed #192, with no threshold or retry-policy waiver.

Coordinator rechecked the canonical successor's latest CI, acceptance, and security: all 20 concrete current checks succeed, including CodeQL go, python, javascript-typescript and aggregate CodeQL. The historical skipped fork placeholder is not counted as proof of any of those required contexts. No security workflow permissions were changed to obtain this result.

No new blocker was found in the scoped fix. This is automated evidence, not human APPROVE or merge authorization. The original #169 stays open until the replacement's normal review/merge completes; old votes are not inherited.

@PeterGuy326
PeterGuy326 marked this pull request as ready for review September 10, 2026 05:23
@sun-970

sun-970 commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Suggested Improvements

1. Fixed backoff interval risks thundering herd

audit-retry.mjs:5 has BACKOFF_MS = 10_000 as a fixed value. When the npm registry is globally overloaded, all CI jobs retry at the same cadence, which won't improve hit rates.

Suggestion: Add exponential backoff + jitter:

const baseDelay = BACKOFF_MS * 2 ** (attempt - 1);
const jitter = Math.random() * 5_000;
await wait(baseDelay + jitter);

Worst-case total wait goes from 20s to ~45s, still well within the CI timeout budget.


2. Vulnerability detection regex may silently break on minor npm output changes

audit-retry.mjs:19 uses "found \\d+ vulnerabilit(?:y|ies)", which requires found followed immediately by a space and a number. If npm changes to found: 0 vulnerabilities or Found 0 vulnerabilities (capitalized), the regex won't match and execution falls through to the "unknown error" branch — the outcome is still "immediate failure" but the diagnostic message changes from a clear reason to a different, less clear one.

Suggestion: Broaden the match:

const VULNERABILITY_PATTERNS = [
  "found[:\\s]+\\d+ vulnerabilit",
  "npm audit report",
  "vulnerabilities found",
];

3. win-audit-verify.bat timestamp is locale-dependent

win-audit-verify.bat:33 uses %DATE% %TIME%, whose output format depends on the system locale (e.g., 2026/09/11 vs Fri 09/11/2026), making cross-machine log alignment difficult.

Suggestion: Use wmic to generate an ISO 8601 timestamp:

for /f "tokens=2 delims==" %%I in ('wmic os get localdatetime /value 2^>nul') do set "DT=%%I"
echo [win-audit-verify] starting at %DT:~0,4%-%DT:~4,2%-%DT:~6,2%T%DT:~8,2%:%DT:~10,2%:%DT:~12,2%

4. No overall timeout cap

Each attempt has a 60s timeout, but the entire runAudits() has no upper bound. Worst case: 2 thresholds × 3 attempts × 60s + 4 × 10s backoff = 400s (~6.7 minutes). CI jobs typically have a 10–15 minute cap, and a single step consuming that much budget is concerning.

Suggestion: Add a deadline check at the runAudits entry point:

const DEADLINE_MS = 5 * 60_000; // 5 min hard cap
const start = Date.now();
// In the runWithRetry loop:
if (Date.now() - start > DEADLINE_MS) {
  stderr.write(`[audit-retry] overall deadline exceeded.\n`);
  return 1;
}

5. isNetworkError weights stdout and stderr equally

Currently isNetworkError searches the combined text of stdout and stderr (audit-retry.mjs:83). However, npm audit normal report output goes to stdout — if a report happens to contain the string "ETIMEDOUT" (e.g., in a dependency's description being audited), it would be misclassified as a network error.

The actual probability is very low, but it's worth checking stderr first to reduce false-positive risk:

function classifyResult(result) {
  // stderr is the canonical stream for npm diagnostic messages;
  // check it first to avoid false positives from report content on stdout.
  if (isNetworkError(result.stderr ?? "")) return "network";
  if (isVulnerabilityReport(result.stdout ?? "")) return "vulnerability";
  return "unknown";
}

All of the above are non-blocking suggestions — the core logic and test coverage are already solid.

The step log was the only copy of the audit report, and an attempt that
retried left no trace at all. Tee the transcript under RUNNER_TEMP, upload
it, and echo each retried attempt to stderr before backing off.

The tee runs under shell: bash on purpose: the default bash -e {0} shell has
no pipefail, so tee's exit status would have turned the dependency gate
fail-open.
Resolves the only collision, in CHANGELOG.md's [Unreleased] "Fixed" list, by
keeping both entries: this branch's CI Web bullet and main's #165 bullet about
withholding unverifiable URL credentials. No duplicate "### Fixed" heading was
introduced.

main's "### Changed" side won on its own: the branch's "retaining the published
npm scope, MCP identity, and existing cache paths" wording is false after #162
changed mcpName to io.github.bytefolk/mem-mcp, and the merged text no longer
carries that clause. GOVERNANCE.md arrived from main without conflict because
this branch never added one.

This commit is the conflict resolution only; the CI evidence fixes follow.
Closes the remaining review blockers on #198.

The batch helper was dead code that the description advertised as evidence:
nothing executed scripts/win-audit-verify.bat, only scripts/test_win_audit_verify.mjs
ran, and that test replays the batch source against a stub npm.cmd. The repo
does have a Windows runner (windows-2025 in the npm-wrapper-compatibility
matrix), so the helper can be executed for real. Adds a dedicated
`windows-audit-evidence` job that runs npm ci in web, executes the helper
against the registry from web/ so `npm run` supplies npm_execpath, and uploads
win-audit-transcript-<sha> with the same retention and if-no-files-found: error
as the Linux transcript. The helper's fixture test moves into this job: it was
gated behind `if: runner.os == 'Windows'` on a matrix entry whose removal would
have deleted the regression silently.

Replaces the "ci.yml keeps the audit transcript pipe fail-closed and uploads it"
assertion added by 62d6507, which never passed: its regex required a blank line
before `shell: bash`, and the step has comment lines there. It now splits the
workflow into steps and asserts the properties directly - every transcript step
uses shell: bash (measured: without pipefail a failing audit exits 0, with it
exits 1), both transcript uploads use if: always() && !cancelled() plus
if-no-files-found: error and retention-days: 14, and ci.yml stays free of
continue-on-error and `|| true`. Adds a matching assertion that the batch
helper is really executed on a Windows runner.
The job added in d594942 was green but collected nothing. Its step log shows
cmd.exe printing its interactive banner and a bare prompt, then exiting 0:
under the runner's git-bash, the leading /d and /c were rewritten to D:/ and
C:/, so cmd.exe never received its option flags and win-audit-verify.bat never
ran. The 267-byte artifact was the banner.

Doubles the slashes so MSYS leaves the flags alone, and adds a gate that greps
the transcript for the helper's starting line, its finished line, a recorded
audit exit code of 0, and the report itself. cmd.exe's exit status is not proof
of execution, and that is the difference between evidence and a green check.
The step dumps the transcript and fails with ::error:: when a marker is missing.

Pins the same property in the workflow assertion, since the previous assertion
matched the invocation text and still passed while nothing executed.

@waterbro-8 waterbro-8 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

COMMENT — the gate genuinely got stricter; the body's central provenance claim is now false

Two separate things, and only one of them needs work from you.

The code direction is right

I looked specifically for a PR that quietly makes CI easier to pass, and it does the opposite:

  • No continue-on-error, no || true, no --force, no widened job timeout anywhere in the
    diff — every timeout line is an addition (timeout-minutes: 20 on the new Windows leg,
    ATTEMPT_TIMEOUT_MS per attempt, --fetch-timeout=45000).
  • 4e1ea308a fix(ci): stop the Windows audit job from passing on an empty transcript fixes a
    job that could go green while having done nothing. That is the correct instinct and the
    reason I am commenting rather than requesting changes on substance.
  • audit-retry.test.mjs:454-455 asserts every --fetch-timeout is strictly below the spawn
    timeout, so npm aborts before SIGKILL instead of the reverse, and :691 pins "terminates a
    stalled process and fails closed at the attempt timeout". Ordering invariants asserted rather
    than assumed is good to see.

But the tracking record in the body no longer identifies this PR

The body says:

  • Original/final source head: 5c3a4a75ab96bed4c046c0822ebc76a87f215cda.
  • Successor head: 5c3a4a75ab96bed4c046c0822ebc76a87f215cda.
  • This successor changes no source file relative to #169's exact source head.

Current head is 4e1ea308a6. compare/5c3a4a75...4e1ea308a6 = 12 commits: two
Merge branch 'main', main's already-merged work, and three of this PR's own
62d650784, d59494240, 4e1ea308a.

Of those, 62d650784 modifies web/audit-retry.mjs (+6/−1). That is shipped source, not a
test and not a doc. So as of this head both sentences are untrue: the successor head is no
longer 5c3a4a75, and the successor does now change a source file relative to #169.

I want to be precise about the nature of the problem, because it is not sloppiness: those
sentences were true when written, and the three commits that broke them are the reviewer
responses this PR was supposed to carry. The claims are load-bearing though — "changes no
source file relative to #169" is exactly the sentence that lets a reviewer skip re-reading
audit-retry.mjs, and Original/final source head == Successor head is what tells them the
evidence in the table transfers unchanged. A reviewer who trusts either will now be trusting
something false.

What I'd ask for

Refresh the tracking-record SHAs, and either reword the no-source-change sentence to name what
62d650784 deliberately changed in audit-retry.mjs, or restate it as scoped to the CI
workflows if that was the intent. The evidence table's commands also need re-running on
4e1ea308a — in particular the Windows leg, since the three commits after the snapshot are the
ones that changed how the Windows audit helper executes.

CI

mergeable_state=blocked; Validate Agent memory fails on this head with
pull access denied for minio/minio, the same Docker Hub anonymous-pull failure as
#199/#203/#205 — it happens at container start, before any project code runs.

Provenance, and what I did not check

Commit attribution is from compare/5c3a4a75...4e1ea308a6 plus per-commit file lists; the
gate-tightening read is from the diff. I did not run any Go or Node test, and I have not
verified the real-Windows behaviour that win-audit-verify.bat exists to prove
— which is
also why I would not sign off on the evidence table's Windows rows myself. Note #169 is
closed (merged=False), so all of that work still shows as new relative to main; nothing in
this comment is a criticism of the commit history.

@waterbro-8

Copy link
Copy Markdown
Collaborator

The one red check is external infrastructure, not this PR

Tracked in #207. Posting so this job's failure is not read as a defect in #198.

What is failing, verbatim

Validate Agent memory → job HTTP, CLI and MCP lifecycle cannot start its test stack.
The daemon refuses an anonymous pull of minio/minio ~0.13 s after minio Pulling, and
postgres is then reported Interrupted as a consequence — not as a second, independent failure:

 minio Error pull access denied for minio/minio, repository does not exist or may require 'docker login': denied: requested access to the resource is denied

The job dies during container startup, i.e. before any script under test is reached.

Evidence specific to this PR

  • Head 4e1ea308 has 21 check-runs: 20 success, 1 failure — the only failure is this job.
    Go, Web, PostgreSQL integration, Dependency review and Workflow, scripts and Compose
    are all green on this exact SHA.
  • None of the three files on the failure path appears in this PR's diff:
    .github/workflows/memory-validation.yml, docker-compose.test.yml,
    scripts/acceptance_agent_memory.sh. What this PR does change — ci.yml,
    web/audit-retry.mjs + its test, web/package.json, web/vite.config.ts,
    scripts/win-audit-verify.bat, scripts/test_win_audit_verify.mjs, CHANGELOG.md
    is not reachable from memory-validation.yml:192.
  • This PR has already hit the wall twice: run 34811014671 / job 103872207418 on predecessor
    head d5949424 failed at 2026-09-14T05:49:33Z, and run 34813242683 / job 103892578203 on the
    current 4e1ea308 failed at 07:27:37Z. Same error, same step. (That second run was created
    06:23Z and sat queued roughly an hour before failing — if you are lining timestamps up against
    ci(infra): minio pull denied blocks HTTP, CLI and MCP lifecycle acceptance #207's table, that is the gap.)

What this does not ask of you

Please do not rebase or re-push to chase this red — that spends a CI cycle and returns the
identical result. And please do not add continue-on-error, a || true, or a skip to this job to
get the PR merged: it is this repository's only end-to-end agent-memory lifecycle gate, and
scripts/acceptance_agent_memory.sh:200 has no fallback path in which minio would be optional.
If anything, the red is an argument for keeping the gate strict and fixing the pull.

What I did not verify — please do not treat the above as complete

  • Whether it is still red right now. The newest Validate Agent memory run anywhere in this
    repository is 2026-09-14T15:17Z; nothing has re-run since ci(infra): minio pull denied blocks HTTP, CLI and MCP lifecycle acceptance #207 was filed. Recovery status is
    unknown to me, and I did not re-run anyone's workflow to find out.
  • Which side is at fault (image removed upstream, anonymous-pull policy change, or runner
    egress). Docker Hub gives one string for all three, and I could not query the registry from
    where I gathered this. pgvector/pgvector pulls fine from the same runners, which rules out
    general egress and a rate-limit toomanyrequests, but not more than that.
  • Whether minio/mc (docker-compose.test.yml:43) is denied too — the run dies on the minio
    service first, so minio-init never executes.
  • Whether this job is in the enforced required set. GET /repos/bytefolk/mem/branches/main/protection
    returns 404 to me and GOVERNANCE.md:41-42 predicts that for a non-admin; org rulesets need
    admin:org. ci(infra): minio pull denied blocks HTTP, CLI and MCP lifecycle acceptance #207 acceptance item 4 asks an admin to replace my inference with a read.

One systemic note, because it changes how long this stays containable: the nine other open PRs
(#189#197) all show this job green, but every one of those runs is dated 2026-09-10 — four
days before the onset window. They are stale, not healthy, and most of them are drafts that will
meet the same wall when marked ready. #207 is the place to coordinate a fix so the queue does not
discover it nine times.

Forensics: run logs of the four failing jobs, check-runs on each head SHA, this PR's file list,
and git log on the three failure-path files. I did not run anything locally and did not
re-run CI.

@waterbro-8

Copy link
Copy Markdown
Collaborator

The gate genuinely got stricter; the body's central provenance claim is now false

Two separate things, only one of which needs work from you.

The code direction is right, and I looked for the opposite

I specifically went looking for a PR that quietly makes CI easier to pass. It
does the opposite:

  • No continue-on-error, no || true, no --force, no widened job timeout
    anywhere in the diff. Every timeout line is an addition
    timeout-minutes: 20 on the new Windows leg, ATTEMPT_TIMEOUT_MS per attempt,
    --fetch-timeout=45000.
  • 4e1ea308a fix(ci): stop the Windows audit job from passing on an empty
    transcript
    fixes a job that could go green while having done nothing. That is
    the correct instinct: an audit that cannot fail is not an audit.
  • d91deffaa fix(web): retain successful dependency audit reports preserves
    evidence that was previously discarded on success.

That is real hardening, and none of it is in question here.

But the provenance claim no longer holds

The body states: "Original/final source head: 5c3a4a75ab96bed4c046c0822ebc76a87f215cda"
and "Source and successor tree: 9d8a127375fd8e945113e06ae88983a1328b9fae",
then asserts the successor head is 5c3a4a75a.

The actual head is 4e1ea308a, which is 5 commits past 5c3a4a75a:

4e1ea308a  fix(ci): stop the Windows audit job from passing on an empty transcript
d59494240  fix(ci): execute the Windows audit helper and repair its evidence test
21fd28075  Merge branch 'main' into codex/122-pr169-ci-successor
62d650784  fix(ci): keep the audit transcript as a downloadable artifact
c2092b1a1  Merge branch 'main' into codex/122-pr169-ci-successor
5c3a4a75a  <-- the commit the body claims is the head

So "Source and successor tree" identity — the sentence that establishes the
reviewed artifact and the merge candidate are byte-identical — is false on the
current head. The claim that this successor "preserves the exact authored source
tree reviewed on #169" cannot be checked against 4e1ea308a.

This matters more than usual here because #169 remains open and its reviews are
expressly not transferred. The tree-identity assertion is the whole substitute.

What would make this reviewable

  1. State 4e1ea308a as the successor head and give the current head's tree hash,
    replacing the 5c3a4a75a / 9d8a1273 pair.
  2. Enumerate what the 5-commit delta changes and confirm the audit-evidence
    modifications (d59494240, 4e1ea308a) do not weaken the assertions the
    earlier evidence ran against.
  3. Note that a7c17eca5 chore(web): refresh audited development dependencies
    is a lockfile change riding along; the body already says the audited lockfile
    retains #192's provenance, so confirm that still holds after the merges.

Separately

Validate Agent memoryHTTP, CLI and MCP lifecycle is red on this head for
an unrelated infrastructure reason; see #207 and #209. The job cannot start its
Compose stack because minio/minio no longer exists on Docker Hub. It dies
during container startup, before any script under test is reached, so it is not
evidence about this PR in either direction.

waterbro-8 added a commit that referenced this pull request Sep 17, 2026
## Canonical requirement

Refs bytefolk/.github#32

- Canonical Issue URL: bytefolk/.github#32
- Consumed revision: R1
- No automatic close keywords: acknowledged

Decision reference: the initial R1 Issue body. It explicitly records
that local candidates preceded this prospective publication record; no
retrospective approval is claimed.

## Requirement trace

| REQ/AC IDs | Changed files / domain | Tests or review evidence |
|---|---|---|
| REQ-001 / AC-001 | 4 exact-pinned version annotations | Exact
expected-byte replacement PASS |
| REQ-002 / AC-002 | 2 files in bytefolk/mem | Repository inventory
PASS; aggregate 7 repositories, 13 files, 21 lines |
| REQ-003 / AC-003 | Existing workflow content and modes | Parsed YAML
and comment-stripped bytes identical |
| REQ-004 / AC-004 | Current-head CI and independent review | Local
independent replay recorded in the canonical R1 Issue linked above;
hosted CI collected on head `f464f686` (19 of 20 checks succeed, see
Validation); independent human review requested and still pending |

## File domains

`.github/workflows/bytefolk-scorecard.yml` (47);
`.github/workflows/bytefolk-security.yml` (58, 65, 68).

Prepared parent / merge base: `2986fe38175f54d99f15dd38a498708c6ecd88cd`
PR base at publication: `87db0dfe0507be2190fe2fdcce0e267be8224f4d`.
Since that baseline `main` advanced by six commits through `3c13f04e`
(#162, #160, #165, #188, #158, #204) — not only `web/package-lock.json`
as previously stated here. None of them touched `.github/workflows/`, so
the F9 workflow blobs and the PR diff are unchanged. The reviewed commit
and original parent are preserved.
Head: `f464f68636adc6bb5295c3818aa6654c46a3caad` — `d2a9ec5` plus one
non-forced `Merge branch 'main'` commit (`f464f686`) that brought the
branch up to `3c13f04e` so it is no longer `BEHIND`. Verified: `git
rev-parse d2a9ec5:.github/workflows/bytefolk-scorecard.yml` and
`...:bytefolk-security.yml` return the same blobs (`2058126c`,
`48a507e0`) as at `f464f686`, and `git diff main...f464f68` is still
exactly these 2 files, `+4/-4`. The comment-only payload is therefore
byte-identical to the reviewed commit and the equality proof above holds
on the current head.

## Scope and non-goals

Correct only `# v4.37.4` to `# v4.37.9` on CodeQL uses-lines pinned to
`cdf488f595d80d6e07e03d4674febd5ab45fa938`. The [official tag
object](https://api.github.com/repos/github/codeql-action/git/tags/a35ac6e6798d72df5475948b28efb89edc2e19ca)
resolves to that existing pin. Action SHAs, permissions, triggers,
steps, matrices, other pins, and runtime code are unchanged.

## Validation

- Exact commands: `ruby evidence/verify.rb --baseline` and `ruby
evidence/verify.rb --committed` from the retained review packet; `git
diff --check 2986fe3
d2a9ec5` from this repository.
- Observed counts/results: PASS 2/2 files and 4/4 replacements here;
aggregate PASS 13/13 files and 21/21 replacements. Baseline
intentionally exits 1 after detecting all 21 stale annotations;
committed verification exits 0.
- Check URLs: collected on head `f464f686` — 19 of 20 checks succeed.
The single failure is [`HTTP, CLI and MCP
lifecycle`](https://github.com/bytefolk/mem/actions/runs/34807101354/job/103861020551),
whose log is `pull access denied for minio/minio` at ~13s: a
container-image pull failure in an unrelated job. The same workflow was
green on `main` at `3c13f04e`, and the identical failure is present on
#198 and #199, so it is not caused by this comment-only change.
Root-cause tracking is separate and open.

The strict verifier checks the changed-file allowlist; exact old blobs
and line inventory; complete expected-byte replacement; absence of stale
target annotations; parsed YAML equality; comment-stripped byte equality
and SHA-256 digests; whitespace and unchanged modes; one commit with the
exact parent; and clean worktrees with no untracked files. All passed.
The independent replay is recorded in canonical R1. The verifier and
inventory are retained outside repository commits.

| ID | REQ/AC | Observable acceptance criterion | Command or manual
steps | Environment | Expected | Observed | Status |
|---|---|---|---|---|---|---|---|
| V1 | AC-001, AC-002, AC-003 | Exact annotations with executable YAML
unchanged | `ruby evidence/verify.rb --committed` | Ruby 2.6.10, Psych
3.1.0, isolated review packet | Exact scoped replacements and equality |
2/2 files; 4/4 lines; all invariants pass | PASS |
| V2 | AC-004 | Hosted checks on this exact head | Inspect this PR's
checks at `f464f686` | GitHub Actions | Applicable checks succeed | 19
of 20 succeed; `HTTP, CLI and MCP lifecycle` fails on `pull access
denied for minio/minio` (infra, unrelated job, also failing on
#198/#199, green on `main`) | PARTIAL |

## Security and compatibility

Documentation annotation only. No dependencies, permissions,
credentials, data flows, or runtime behavior change. The diff and commit
identity were inspected for public-safe content. No CHANGELOG entry or
behavior-documentation update is needed because only explanatory
comments change.

## Known limitations

Runtime suites, build, coverage, and dependency audits were not rerun
for this comment-only change; no runtime test result is claimed. Hosted
CI is separate from local equality proof. Two limits now apply: (1) the
strict verifier's `one commit with the exact parent` invariant describes
the reviewed payload commit `d2a9ec5`, not the current branch shape,
which carries two additional `Merge branch 'main'` commits; (2) this PR
is **not merge-ready yet** — repository `AGENTS.md` step 6 requires
passing CI *and* an approval from someone other than the author, and
`HTTP, CLI and MCP lifecycle` is red on the unrelated `minio` pull, so
the green-CI half is unmet until that infrastructure failure is fixed.

## Risk and rollback

Low-risk annotation correction. Roll back through an ordinary revert of
this single commit. There is no migration or release action.

## Product review handoff

- Implementation/publication owner: @PeterGuy326
- Automated pre-review result: independent local replay recorded in R1;
no human approval implied.
- Human final review: PENDING; no human review requested by this
publication.
- Merge ledger owner: @PeterGuy326
- Product reviewer: @PeterGuy326
- Milestone or release packet: N/A: bounded documentation annotation
maintenance
- Merge, CI, release, and model judgment do not accept or close the
Issue: acknowledged


## Maintenance update (2026-09-14, @waterbro-8)

Records written by the maintainer account, not by the implementation
owner:

- `f464f686 Merge branch 'main'` was pushed to this head branch
(non-forced, `main` at `3c13f04e` is an ancestor of the head) to clear
the `BEHIND` state this PR's own body said blocked merging. No workflow
file content changed: both blobs are identical to `d2a9ec5`.
- The stale facts above were corrected in place: the recorded head SHA,
the "Main advanced only `web/package-lock.json` in PR #192" claim, the
`NOT VERIFIED` hosted-CI rows, and the "this is a draft, not
merge-ready" note.
- This PR was marked ready for review and an independent review was
requested. The maintainer account that pushed the merge commit did
**not** approve it: `AGENTS.md` step 6 requires an approval from someone
other than the author, and a commit author on the head cannot supply
that approval for their own push. `@PeterGuy326` remains implementation
and merge-ledger owner.

Co-authored-by: 勒布朗-詹姆斯 <2986253039@qq.com>
waterbro-8
waterbro-8 previously approved these changes Sep 17, 2026

@waterbro-8 waterbro-8 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Code review: approved(条件见第五节)

我是这条 PR 的受邀评审人,不是作者,也不是其 head 的推送者。

一、接线是完整的,不是只加了个脚本

这类改动最容易"看起来很完整但其实没接上",所以我先查了落点:

  • web/package.json"audit": "npm audit --omit=dev --audit-level=moderate && npm audit --audit-level=high""audit": "node audit-retry.mjs"接上了,CI 里的 npm run audit 走的确实是新脚本。
  • web/vite.config.tsinclude 加进了 audit-retry.test.mjs接上了,否则 CI 新加的 npm test 根本不会执行这份测试。
  • package.json"test": "vitest run" 本来就存在 → CI 新增的 npm test 这一步是可执行的,不会因脚本缺失而红。

二、shell: bash 那两处注释是实质,不是仪式

GitHub 的默认 shell 是 bash -e {0}没有 pipefail。所以 cmd | tee 的退出状态取的是 tee 的,一个失败的 audit 会被判绿。两处都显式指定 shell: bash 并写了注释说明,这是对的。

Windows 那条更进一步:cmd.exe //d //c(双斜杠)是 git-bash 的路径重写陷阱——写单斜杠 /d /c 会被 MSYS2 改写成 D:/ C:/,cmd.exe 拿到的就不是选项标志,于是它打个交互式横幅并以 0 退出,helper 从未执行、job 却是绿的。注释把这条写出来,并且后面还有一步专门验 transcript,形成闭环。

三、"证据必须是真的"这条设计,我认为是这条 PR 最好的部分

只断言 cmd.exe 退出 0 什么都证明不了(上面那个陷阱就是这么绿的)。所以加了一步在 transcript 里找四根针,包括 [win-audit-verify] npm run audit exit code: 0found 0 vulnerabilities

scripts/test_win_audit_verify.mjs 里还有两个反向对照,我特意看了:

  • call npm run audit 改成 npm run audit → 断言"完成标记消失"。没有 CALL 的话批处理不会返回,后审计证据根本不会被打印——这是个真实的批处理陷阱。
  • endlocal & exit /b %AUDIT_RC% 拆成两行 → 断言"日志里有 exit code: 7,但进程退出码是 0"。ENDLOCAL 单独一行会让保存的退出码在延迟展开的边界上丢失——这正是"helper 报告失败但 job 绿"的经典形态。

这两个用例断言的是失败形态本身,不是成功路径。这是正确的测试设计。

四、重试策略的失败闭合方向是对的

  • 未完成的 audit(result.error / 有 signal / status 不在 0–255)不重试直接失败——这点很关键:一次中断的 audit 即使输出里恰好出现了网络字样,也不能当成"瞬时失败"去重试,否则会把截断的输出当成一次有效审计。
  • isVulnerabilityReport 优先于 isNetworkError:真实漏洞一律不重试。测试里甚至造了"漏洞报告里含 ETIMEDOUT"的混合输出来验证不会被误判为瞬时。
  • 被重试的每一次都把输出回显到 stderr(stdout 留给最终报告),所以自愈的失败仍然留痕。
  • 两个阈值各有 3 次预算(不是共用),末次不 backoff。测试断言 wait.mock.calls 精确等于 [[10_000],[10_000]] 这种细节,说明是真的在数。
  • Windows 上 npm_execpath 缺失时直接失败并给出可操作提示——因为 Windows 的 npm 是 .cmd shim,无 shell 的 spawnSync 起不来,必须走 node <npm-cli.js>

五、批准的条件(请务必看这一节)

这个批准是针对当前 head 的代码,它不覆盖两件尚未发生的事:

  1. 本 PR 与 main 冲突,且冲突只落在 CHANGELOG.md 一个文件。 我核过两侧:本 PR 改 8 个文件,main 自 merge-base 起改 7 个,交集只有 CHANGELOG.md。也就是说另外 7 个(ci.yml、两个脚本、两个 web 文件、package.json、vite.config.ts)都不冲突,解冲突只需处理 changelog 里相邻位置的两段条目。
  2. CI 还没有在解冲突后的 head 上跑过。 目前挂在 #198 上的检查结果来自旧 head,不代表最终状态。按仓库闸门,解冲突后必须重跑。

所以:解完 CHANGELOG 冲突、CI 全绿之后这条就可以进队列。我可以帮你触发重跑。

(本次为独立评审,不构成除代码批准之外的任何承诺。)

Incorporate the three Unreleased Fixed entries merged to main after
this branch was cut (design language #211, web contrast, MinIO quay.io
#209) ahead of the CI audit entries from this PR.  No content change to
any of the existing entries.

@Bindy-lbb Bindy-lbb left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Mem PR #198 Review: LGTM (Approved) ✅

Key Review Highlights:

  1. Web Test Pipeline Integration (.github/workflows/ci.yml):

    • The Web CI workflow now explicitly executes unit tests (npm test), covering 104 tests across 7 files including 37 audit regression tests.
    • Enforces shell: bash on piped transcript commands, guaranteeing that pipefail is respected and preventing silent gate bypasses.
    • Audit transcripts are uploaded via actions/upload-artifact with if: always() && !cancelled() and a 14-day retention window.
  2. Deterministic Audit Retry Mechanism (web/audit-retry.mjs):

    • Executes dual-threshold audits (moderate for prod, high for all dependencies).
    • Retries are strictly limited to recognized transient registry/network blips (e.g. 503 Service Unavailable, ETIMEDOUT).
    • Fails closed immediately upon actual vulnerabilities, spawn errors, signals, or non-transient status codes.
    • Preserves complete stdout/stderr outputs so lower-severity findings and audit summaries are visible.
  3. Cross-Platform & Regression Verification:

    • Integrates Windows batch evidence helper (win-audit-verify.bat) with native runner verification and space-tolerant path fixtures.
    • All 21 GitHub CI checks have passed cleanly.

@waterbro-8 waterbro-8 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-review of 0a6c5366 — substance is green; the tracking record still doesn't match the branch

(受邀对 09-17 07:39Z 的评审请求做复审。我此前在 4e1ea308 的条件批准已处于 DISMISSED 状态,本条按当前 head 独立成立。)

已核实为真的部分

  • CI 在精确 head 上全绿0a6c5366 上 21/21 个 check 全部 success,包括 Web、Windows audit evidence、CodeQL(4 个语言上下文)、PostgreSQL integration、npm wrapper 三个平台。
  • 我此前批准的两个条件均已满足:CHANGELOG 冲突在 dc865b2a 解决(净 diff 只保留本 PR 条目,+13/-0,两侧内容无丢失);main 已并入(0a6c5366),mergeable=true
  • ci.yml 净 diff 复读无误:两处 shell: bash(各带 pipefail 缺失的注释)、transcript 经固定 SHA 的 upload-artifact v7.0.1 上传(14 天保留)、Windows leg timeout-minutes: 20 + transcript 四针断言;没有 continue-on-error / || true / 放宽超时。
  • Bindy-lbb 已在当前 head 批准(2026-09-17T07:43:31Z)。

仍然拦住我批准的:tracking record 与分支事实不符(09-15 已提,至今未更新)

body 仍是 09-10 起草时的口径,与当前分支状态冲突:

  1. Successor head: 5c3a4a75… — 实际 head 是 0a6c5366
  2. "This successor changes no source file relative to #169's exact source head" — 已不成立:62d650784 改了 web/audit-retry.mjs(+6/−1)、web/audit-retry.test.mjs(+20)、ci.yml(+14/−1),主题是 "keep the audit transcript as a downloadable artifact",加上 4e1ea308 的空 transcript 修复。这些改动本身是好的(方向正是我 09-15 评论里认可的),但这句 load-bearing 的话必须点名它们,或改为 workflow-only 的限定说法。
  3. Changed-files 清单里的 web/package-lock.json 已不在实际 diff 中(与 main 相同)。
  4. Current canonical main: 2986fe38… — main 现在是 904e8e79#199 已并入)。
  5. 验证台账最后一行 "PENDING at draft creation" 已过时 — CI 现在全绿,该行应改判,台账在 0a6c5366 上重新盖章。

合并前的操作提示

  • mergeStateStatus=BEHIND(main 先进了 #199904e8e79):按 base branch policy,必须先 update-branch 才能合并。
  • 本 PR 的历史显示批准不一定扛得住新提交(我在 4e1ea308 上的批准随 06:09Z / 07:23Z 两次 push 进入 DISMISSED)。update-branch 之后请复核 reviewDecision;若 Bindy-lbb 的批准被作废,任一非作者 code owner(Bindy-lbb / waterbro-8)重新批准一次即可恢复闸门(作者本人不行)。

结论

代码与 CI 都站得住。唯一剩下的是把 body 的 SHA 与两句话改成与分支一致——机械修改,改完我立即批准。

@PeterGuy326
PeterGuy326 merged commit 1b27aac into main Sep 17, 2026
21 checks passed
@PeterGuy326
PeterGuy326 deleted the codex/122-pr169-ci-successor branch September 17, 2026 08:04
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.

5 participants