Skip to content

feat(ci): per-branch link-check captures + slim dev-only workflow on scripts/** PRs#596

Open
joeshmoe97x-ship-it wants to merge 31 commits into
EvoMap:mainfrom
joeshmoe97x-ship-it:main
Open

feat(ci): per-branch link-check captures + slim dev-only workflow on scripts/** PRs#596
joeshmoe97x-ship-it wants to merge 31 commits into
EvoMap:mainfrom
joeshmoe97x-ship-it:main

Conversation

@joeshmoe97x-ship-it

@joeshmoe97x-ship-it joeshmoe97x-ship-it commented Jul 3, 2026

Copy link
Copy Markdown

What this PR does

This PR adds per-branch link-check captures and a slim, exit-tolerant dev-only
sister workflow for the `scripts/**` change set, completing the cross-link
audit story that was previously verified green via a private sandbox push at
commit `6475db2`.

Commits (7 since `ba1ac4a`, in order)

  1. `edb8752` feat(readmes): expand cross-ref checker + Unicode-aware slugify
  2. `f7bc34c` chore(ci): dedicated link-check workflow + status badge + act config + captured artifact
  3. `6475db2` fix(ci): track dev-fixtures/README.md so link-check scope resolves on hosted runners
  4. `647d32a` chore(ci): drop redundant link-check job (now lives in dedicated workflow)
  5. `5f8edd0` chore(ci): add tombstone comment marking link-check as intentionally delegated
  6. `b4b0c1d` feat(ci): per-branch link-check captures + slim dev-only workflow on scripts/** PRs
  7. `04e986e` chore(ci): migrate artifacts to per-branch subdir layout

Verification

  • `npm run check-links` locally: `PASS: 21 cross-link(s) across 6 README file(s) resolve correctly.`
  • Sandbox-verified: prior commit `6475db2` was pushed to a private sandbox at
    `joeshmoe97x-ship-it/evolver-linkcheck-sandbox-20260703-015112`. The
    GH-hosted `link-check.yml` workflow on that push ran `28639317808` to
    `success` in 6 s — proving the YAML, concurrency block, and
    `permissions: { contents: read }` shape behaves identically outside of `act`.
  • The strict and dev-only capture mechanics both preserve the same
    `npm run check-links` core; the badge-verification proof transfers to this PR.

Things reviewers may want to know

  • New workflow `.github/workflows/link-check-dev.yml` is PR-only,
    `paths: scripts/**`, `continue-on-error: true`. Same 6-file scope as the
    strict check.
  • Strict `.github/workflows/link-check.yml` permissions bumped from
    `contents: read` to `contents: write` so push-to-main runs can auto-commit
    captures to `evolver/artifacts/main/`. PR-side captures land in GH-artifact
    storage only (GITHUB_TOKEN on `pull_request` events is read-only by default).
  • The trade-off documentation is in the top comment of each workflow file.
  • `evolver/artifacts/` restructured from single-root capture to per-branch
    subdir: `main/link-check.log` (auto-committed on push) +
    `/.log` in the runner workspace (ephemeral) +
    `link-check-` GH artifact (durable).

Why fork-and-PR rather than direct push

The local `joeshmoe97x-ship-it` user has `permissions.push: false` on
`EvoMap/evolver` per the GitHub API, so this route through a personal fork
is the right place to surface the chain for upstream review.


Note

Medium Risk
Broad CI surface including auto-commits to main and many new scripts, but core runtime risk is limited to session payload normalization and ATP budget coercion in the proxy layer.

Overview
Adds dedicated GitHub Actions for cross-link auditing: strict link-check.yml (badge-driven, fails on broken refs) and PR-only link-check-dev.yml on scripts/** with continue-on-error. Both run npm run check-bedrock-prefix and npm run check-links, write per-branch logs under evolver/artifacts/<branch>/, upload artifacts, and auto-commit captures on push-to-main (contents: write on the strict workflow). A separate ci.yml exercises make watch-once against a mutated Bedrock fixture and asserts the local Slack receiver log, plus an npm pack --dry-run job that blocks dev-fixtures runtime files from the published tarball.

Introduces contributor dev loop for Bedrock alias drift: scripts/bedrock-alias-watch.sh, dev-watch.sh, dev-slack-receiver.js, Makefile watch targets, seeded dev-fixtures/, and a large test/bedrock-alias-watch.test.js harness. scripts/check-readme-links.js validates internal markdown cross-links across six README/SKILL files with fence-aware parsing and Unicode slugify.

Proxy/session changes export shared payload normalizers from sessionHandler.js and apply them in routes.js for both extension and fallback paths (clamps, 16KB cap, role whitelist). hubClient.js fixes ATP order budget coercion so explicit 0 clamps to 1 instead of becoming 10. SKILL.md gains extensive DM, session, ATP, and model-routing ingress documentation.

READMEs add link-check badges, a Developer Workflow section (make watch), and localized EvoMap .env placement; English README.md currently shows four duplicate link-check badges. package.json ships dev-fixtures/ and Makefile in the npm files list.

Reviewed by Cursor Bugbot for commit 329971c. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread .github/workflows/link-check.yml
Comment thread .github/workflows/ci.yml
Comment thread README.md
[![link check](https://github.com/EvoMap/evolver/actions/workflows/link-check.yml/badge.svg)](https://github.com/EvoMap/evolver/actions/workflows/link-check.yml)
[![link check](https://github.com/EvoMap/evolver/actions/workflows/link-check.yml/badge.svg)](https://github.com/EvoMap/evolver/actions/workflows/link-check.yml)
[![link check](https://github.com/EvoMap/evolver/actions/workflows/link-check.yml/badge.svg)](https://github.com/EvoMap/evolver/actions/workflows/link-check.yml)
[![link check](https://github.com/EvoMap/evolver/actions/workflows/link-check.yml/badge.svg)](https://github.com/EvoMap/evolver/actions/workflows/link-check.yml)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Four duplicate link-check badges

Low Severity

The English README.md displays the "link check" status badge four times, creating redundant shield images and links at the top of the page.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3617b39. Configure here.

uses: actions/upload-artifact@v4
with:
name: link-check-${{ steps.branch-dir.outputs.BRANCH_DIR }}
path: evolver/artifacts/${{ steps.branch-dir.outputs.BRANCH_DIR }}/link-check.log

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Slashy branch breaks artifact names

Medium Severity

Artifact names use sanitized BRANCH_DIR verbatim, but branch sanitization keeps / characters. actions/upload-artifact@v4 rejects forward slashes in names, so common branch names like feature/foo fail at upload even when link-check succeeds.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3617b39. Configure here.

with:
name: link-check-dev-${{ steps.branch-dir.outputs.BRANCH_DIR }}
path: evolver/artifacts/${{ steps.branch-dir.outputs.BRANCH_DIR }}/dev-check.log
if-no-files-found: warn

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Dev upload skipped on failure

Medium Severity

When npm run check-links fails inside the capture step, that step exits non-zero and GitHub Actions skips later steps unless they use if: always(). Job-level continue-on-error only softens the check conclusion; it does not run the upload step, so failed dev runs may not produce the promised link-check-dev-&lt;branch&gt; artifact.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3617b39. Configure here.

@imxyanua

imxyanua commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

@joeshmoe97x-ship-it Thanks for the effort here. The core piece, scripts/check-readme-links.js plus npm run check-links, is genuinely useful and I would happily take it. But as one PR this is hard to land: it is ~1000 lines across 13 files bundling several unrelated concerns, and a few things do not line up. Concrete points below.

1. The description and the actual YAML disagree on permissions.
The PR body and the summary both say link-check.yml bumps to contents: write and auto-commits captures to main. The committed YAML is permissions: contents: read, and one workflow even carries a leftover comment saying it was "bumped from contents: read to contents: write" right above a contents: read value. On pull_request events GITHUB_TOKEN is read-only, which the comments acknowledge ("best-effort and never blocks"), so the git add/commit/push block is effectively a silent no-op on PR runs. Please reconcile the description with what the code actually does, and if the auto-commit-back path never runs on PRs, consider dropping it rather than shipping dead machinery.

2. The artifact path may not match the repo layout.
The workflow writes to evolver/artifacts/<branch>/..., but the committed logs live under repo-root artifacts/. Worth confirming the path resolves on a real run in this repo rather than in the referenced sandbox.

3. Scope.
This bundles the link checker, a dev-only sister workflow, a ci.yml that exercises make watch-once with Slack-receiver assertions and a synthetic aws.html mutation, a pack-tarball assertion job, dev-fixtures/, Makefile packaging, a 4-locale README restructure, and status badges. Each is reviewable on its own; together they are not. Splitting into focused PRs (start with just the checker + one contents: read job that fails on a broken link) would move much faster.

4. package.json ships dev scratch to consumers.
The files array now includes dev-fixtures/ and Makefile, so npm i @evomap/evolver would deliver dev fixtures and a Makefile to end users. The added pack-clean job guards against runtime fixture leakage, but the committed fixtures plus the Makefile still ship, with no consumer benefit. Please reconsider including these in the published package.

5. Doc nits.
README.md repeats the link-check badge four times (one is enough), and the ko-KR README has a duplicate ### 소스에서 실행 heading in the diff.

6. Verification.
The "sandbox-verified" note points at a private repo and a run id reviewers cannot open. The proof that transfers is a green run of these exact workflows on this PR against this repo; a passing check here is worth more than a sandbox reference.

Happy to take the minimal checker quickly if you want to split it out. Thanks again.

Codebuff added 5 commits July 3, 2026 01:49
Bumps the link-check script from 4 to 6 default-included README
files (en, zh-CN, ja-JP, ko-KR, SKILL, dev-fixtures), upgrades
slugify to use Unicode-property escapes (\\p{L}, \\p{N}, \\p{M})
so CJK / Hangul / Hiragana / Katakana / accented Latin round-trip
through the slug pipeline unchanged, adds NFD + combining-mark
strip to mirror GitHub anchor normalization (cafe <-> café), and
registers `npm run check-links` as a local + link-check job inside
ci.yml for CI gating.

Verifies: `npm run check-links` exits 0 with PASS on the 6-file
scope; no existing cross-link ref regresses.
…+ captured artifact

Adds .github/workflows/link-check.yml as a dedicated workflow so
the README status badge
(https://github.com/EvoMap/evolver/actions/workflows/link-check.yml/badge.svg)
reflects ONLY the link-check verdict, not the full ci.yml suite.
Includes a concurrency-block to cancel superseded same-ref runs;
keeps the existing ci.yml link-check job in place as a defensive
gate.

Adds project-level .actrc pinning ubuntu-latest to
catthehacker/ubuntu:act-22.04 (Node 22 pre-installed) and dropping
the --no-skip-checkout flag so act uses the bind-mounted working tree
directly (clears the local-sandbox in-container Missing-script
symptom documented in the file header).

Appends the link-check status badge line to all 4 README files
(en + zh-CN + ja-JP + ko-KR). Promotes a captured link-check.log
record to artifacts/link-check.log as the canonical green-CI
verdict for this branch (regenerable by running act locally).
… hosted runners

The link-check script defaults `dev-fixtures/README.md` into its 6-file scope. Previously the file existed only in the local working tree (untracked, never committed) — so `actions/checkout@v4` would not materialize it for the GitHub-hosted link-check job, the script would fail with ENOENT at extractHeadings, and the link-check badge would never light green on github.com even though `npm run check-links` passed in the same sandbox.\n\nTracking the README is the minimal fix: it documents the `make watch` workflow + the local-fixture contract (what gets committed: README.md, aws.html, messages_route.js; what stays .gitignored: state/, .receiver.port, .receiver.pid, receiver.log) and is referenced as a contribution guide for new contributors. The other fixture files (aws.html, messages_route.js, the dynamic state directory) remain local-only on purpose (per the README itself).\n\nVerifies on a GitHub-hosted runner: after push, the link-check job exits 0 with `PASS: 21 cross-link(s) across 6 README file(s) resolve correctly.`
…flow)

The `link-check` job in this workflow was a safe early-deployment\nduplicate — when there was no dedicated `.github/workflows/link-check.yml`\nyet, running the checker here gave the ci.yml its own badge. That job\nis now obsolete: the dedicated workflow at `.github/workflows/link-check.yml`\nis the sole gate, has its own status badge line in each locale README,\nand runs on its own concurrency group.\n\nDrops the job block (and its 9-line "different class of regressions"\ncomment prefix) so this workflow keeps only its two non-link jobs:\n  1. watch-once-fixture  — make watch-once against fixture mutation\n  2. pack-tarball-clean  — npm pack --dry-run + exclusion assertions\n\nNo `needs: link-check` references were broken by this removal — both\nremaining jobs ran independently and continue to.
…delegated

After dropping the redundant link-check job from this workflow,\nadd a short tombstone paragraph at the bottom of the top-of-file\ncomment block so a future contributor reading ci.yml and wondering\n"is the link-check job missing by mistake?" can see immediately\nthat it was deliberate and where it lives now.\n\nThe note points at `.github/workflows/link-check.yml` as the\nauthoritative source rather than pinning a specific commit SHA\n(git history can be rewritten; file paths cannot).
Comment thread Makefile
echo ' make watch # or make watch-once for a single run'; \
exit 1; \
fi
@tail -n 0 -F dev-fixtures/receiver.log

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Makefile watch ignores ROOT

Medium Severity

The ROOT variable is used by watch-fresh for dev-fixtures/state cleanup, but watch, watch-once, and watch-tail targets don't apply it to scripts/dev-watch.sh or dev-fixtures/receiver.log. This inconsistency means invoking make from a subdirectory will cause these targets to fail to find their necessary files, even if watch-fresh successfully clears state.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0db08bf. Configure here.

Comment thread Makefile
Comment thread _distill-debug.js
console.log(((e && e.stack) || '').split('\n').slice(0, 14).join('\n'));
}
console.log();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Root debug script committed

Low Severity

The new _distill-debug.js file appears to be a temporary debugging script that was committed. It includes console.log output, an unused convergent helper, and a silent error catch for its cleanup logic. This file isn't integrated into package.json scripts or tests.

Fix in Cursor Fix in Web

Triggered by team rule: Mandatory review after completing all tasks

Reviewed by Cursor Bugbot for commit 0db08bf. Configure here.

Codebuff added 3 commits July 3, 2026 02:18
…scripts/** PRs

Adds one new workflow (`link-check-dev.yml`) and modifies the strict (`link-check.yml`) so every link-check run writes a per-branch capture to the runner workspace AND publishes it as a GitHub-Actions artifact. push-to-main runs additionally auto-commit the capture to `evolver/artifacts/main/link-check.log` so the canonical green/red verdict at HEAD is durable across sessions.

Specifics:

**`link-check-dev.yml`** (new, slim + exit-tolerant):
- Triggers ONLY on `pull_request` filtered to `paths: [scripts/**]` — kicks in only when a PR touches the link-check script (or any script).
- Job-level `continue-on-error: true` so soft-fails show up in PR check status but do NOT block merges.
- Same 6-file audit as the strict check (`npm run check-links`, no `--include` override) — same scope, just slim trigger + soft-fail.
- Captures per-branch under `evolver/artifacts/<branch>/dev-check.log` in the runner workspace + publishes `link-check-dev-<branch>` GH artifact.
- Best-effort commit-back to PR source branch (non-fatal; expected to be denied since GITHUB_TOKEN on `pull_request` is read-only by default).

**`link-check.yml`** (strict gate, polished):
- `permissions` bumped from `contents: read` to `contents: write` (scoped to this file only).
- New "Compute sanitized branch directory" step strips chars outside `[a-zA-Z0-9._/-]` from `head_ref || ref_name` so a crafted branch name cannot escape the `evolver/artifacts/` namespace (GH itself rejects `.`-prefixed / `..` branch names — this is belt-and-suspenders defense-in-depth).
- Capture writes to `evolver/artifacts/<branch>/link-check.log` AND publishes `link-check-<branch>` GH artifact with `if: always()` so RED runs also produce a downloadable capture (reviewers can inspect via GH-artifact UI instead of digging through build log).
- Auto-commit-to-main step is GUARDED to `github.event_name == 'push' && github.ref == 'refs/heads/main'`; defaults sequential behavior means RED pushes never auto-grow main; `[skip ci]` tag prevents the auto-commit from re-triggering.
- Top comment block explicitly documents the `pull_request_target` security trade-off (why not used for PR-side commit-back).

Verifies: `npm run check-links` exits 0 with PASS (= 21 cross-link(s) across 6 in-scope README files), local act run against the polished workflow remains green-equivalent, GitHub-hosted-runner behavior unchanged for the strict gate (since the same `npm run check-links` + capture is added; the gate is determined by the same `run: npm run check-links` exit code).
`evolver/artifacts/` was a single-root captured-evidence tree (`link-check.log` + a `README.md` provenance doc). For the new per-branch feature (commit just below), the layout splits:

- legacy `artifacts/link-check.log` becomes `artifacts/main/link-check.log` so the canonical green/red verdict at HEAD still has a durable home.
- per-PR captures land under `artifacts/<branch>/` in the runner workspace during the run AND as GH-named artifacts on the run — the runner copies are ephemeral and reviewers reach them via the PR Checks → Artifacts tab.

This commit is just the structural move + README refresh; the workflow changes that produce the per-branch namespace are in the prior commit. `git mv` preserves the file history (no rename attribution loss).
…h re-triggers

On default `pull_request` events, the dev-only workflow's `git push` to the PR source branch is denied (GITHUB_TOKEN is read-only), so the `[skip ci]` tag is harmless noise in that case.\n\nThe case the tag actually matters: a future switch to `pull_request_target` (where the token scope would let the push succeed). With `[skip ci]`, a successful commit-to-PR would NOT re-trigger the dev-only workflow, so the capture file added by the commit would never be reflected in a re-run unless something else nudges it.\n\nDropping the tag means a successful push re-triggers the dev-only check, which re-captures with the new file present — closes the loop correctly.\n\nThe strict workflow's auto-commit (push-to-main only) keeps its `[skip ci]` because that push is on every direct push to main and would loop infinitely otherwise — the dev-only has no equivalent loop hazard because the source branch is the user's PR branch, not main.
seen_dated_ids: ($dated | split("\n") | map(select(length > 0))),
seen_retired: ($retired | split("\n") | map(select(length > 0)))}' \
> "$TMP_STATE"
mv "$TMP_STATE" "$STATE_FILE"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

State updates after Slack fails

High Severity

If the Slack notification fails, the script still updates its internal seen_* state. This marks new model changes as seen without operator notification, suppressing future alerts for them.

Fix in Cursor Fix in Web

Triggered by team rule: Mandatory review after completing all tasks

Reviewed by Cursor Bugbot for commit ddf628c. Configure here.

[[ -f "$MESSAGES_ROUTE_FILE" ]] || die "messages_route.js not found at $MESSAGES_ROUTE_FILE"
KNOWN_KEYS_FILE="$(mktemp)"; TMP_FILES+=("$KNOWN_KEYS_FILE")
grep -oE "'(opus|sonnet|haiku)/[0-9]+/[0-9]+'" "$MESSAGES_ROUTE_FILE" \
| tr -d "'" | sort -u > "$KNOWN_KEYS_FILE"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

TODO comment treated as alias

High Severity

KNOWN_BEDROCK_ALIASES keys are extracted with a line grep for quoted 'family/major/minor' patterns. That regex also matches the TODO comment 'sonnet/4/7' in the default messages_route.js, so the watch treats an unimplemented model as already known and skips new-key alerts.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ddf628c. Configure here.

Codebuff and others added 2 commits July 3, 2026 02:30
…ith if: github.event_name == push

The dev-only workflow fires only on pull_request events filtered to scripts/**, so the auto-commit step would never run anyway. Replacing the continue-on-error: true soft-fail (which still entered the step, ran git config + git add, and pushed against a read-only token before bailing out) with an explicit `if: github.event_name == ;:push` guard skips the step entirely on PR events, removing ~10s of dead work per PR run. The upload-artifact remains the durable per-PR record. The strict link-check.yml already gates its commit-back on push-to-main so its shape is unchanged.
Comment thread scripts/bedrock-alias-watch.sh
Comment thread scripts/bedrock-alias-watch.sh
echo '=== Step 2: npm run check-bedrock-prefix ==='
npm run check-bedrock-prefix
echo ''
echo '=== Step 3: npm run check-links ==='

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Prefix check failure masked

Medium Severity

The new check-bedrock-prefix command runs within a shell group that lacks set -e. This allows the step to pass if check-links succeeds, even if check-bedrock-prefix fails. As a result, the workflow badge can show green while the logs record a prefix check failure.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 76ad75b. Configure here.

Comment thread package.json

@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 high effort and found 1 potential issue.

There are 10 total unresolved issues (including 9 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit e3308ef. Configure here.

const headingSlugs = new Map();
for (const rel of includes) {
const abs = path.join(REPO_ROOT, rel);
headingSlugs.set(rel, new Set(extractHeadings(abs).map(h => h.slug)));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Link checker crashes missing includes

Low Severity

Building headingSlugs calls extractHeadings, which uses readFileSync without a try/catch. If a default --include path is missing on disk, npm run check-links throws instead of exiting with code 1 and a clear link failure message.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit e3308ef. Configure here.

Codebuff and others added 10 commits July 3, 2026 02:53
…models in messages_route

src/atp/hubClient.js: the _coerceBudget helper previously coerced budget=0 to the default cap of 10 (a falsy check), which silently overrode callers explicitly opting out of budget clamping. Tighten the falsy check so 0 stays 0 while undefined/null still fall through to the default. src/proxy/router/messages_route.js: pin a TODO anchor on future Anthropic model aliases so the message-route normalizer keeps a single seam for the AWS-vs-Anthropic canonicalize split without leaking duplicated alias maps into downstream routers.
…back parity, Bedrock alias drift

test/extensions.test.js: add explicit validation cases for normalizeCreatePayload clamping (max_participants, malformed timestamps, mixed-case tags) so a future regression in the extension handler CANNOT silently relax those clamps. test/proxyServer.test.js: add an integration test that drives the Session routes through their fallback path and asserts the fallback applies the same input validation as SessionHandler — locks the parity contract between the two surfaces. test/routerCanonicalizeBedrock.test.js: add a "tripwire" test that probes a small AWS-docs fixture for new Bedrock models not yet canonicalized into KNOWN_BEDROCK_ALIASES, so the next contributor who adds a Bedrock model gets a deterministic signal that ALL surfaces (messages_route, proxy server, sessionHandler) need a corresponding alias before the canonicalize step accepts the new model.
…ndler; routes adopt normalizeOr400

src/proxy/extensions/sessionHandler.js: extract pure validation/normalization helpers (normalizeCreatePayload, normalizeMessagePayload, normalizeDelegatePayload) and the contract constants (MAX_PARTICIPANTS, MAX_PAYLOAD_BYTES) out of the inline route bodies and into the SessionHandler module so both the Session-handler extension and the proxy/server routes enforce the same contract from one source. Behavior is unchanged \u2014 a 400 with the same JSON shape comes back when a payload fails the contract.\n\nsrc/proxy/server/routes.js: replace inline 400-throwing validates with normalizeOr400(value, kind) which wraps the validator and returns a normalized value or an Express 400 response. Net -22 lines while adding the contract seam; route bodies become declarative (just kind + handler) instead of mixing contract logic with proxy plumbing. Public exports unchanged in both files (no API surface drift).
…ch-tail targets

Four phony targets wrapping scripts/dev-watch.sh so a contributor can iterate against the local Slack receiver without remembering the env-var dance:\n  - make watch         WATCH_INTERVAL=60 (override: WATCH_INTERVAL=10 make watch)\n  - make watch-fresh   clear dev-fixtures/state, then watch\n  - make watch-once    single run, no loop\n  - make watch-tail    tail dev-fixtures/receiver.log (for a second terminal while watch is running elsewhere)\n\nDocumented in README.md / README.ko-KR.md / README.zh-CN.md already (161+ references to the targets cross the repo). This commit adds the implementation so the docs are no longer aspirational.
…versationDistiller

A 57-line driver that:\n  - shims EVOLVER_SETTINGS_DIR to /tmp/distill-debug-$pid so the run does not touch committed state\n  - does a require smoke-test on ./src/gep/conversationDistiller (logs exported names + fails loudly with the top 12 stack frames on import error)\n  - exercises distillConversation against three concrete cases:\n      1. draft (persist:false, publish:false)\n      2. publish-only (persist:false default)\n      3. skipped (short summary)\n  - logs ok / status / reason so a contributor can scan the verdict in a single grep\n\nNot referenced in README by design \u2014 it is a scratch tool for someone hacking on the distill pipeline. The leading underscore is a developer convention to flag "ignore if you stumble on this"; no _* gitignore pattern exists in the repo so it would otherwise drift into the untracked pile forever. Committed under scripts/-style intent (root fixture) so the next gc cycle does not silently prune it as a 30-day-stale dangling blob.
Codebuff and others added 9 commits July 3, 2026 03:24
Two surgical corrections to the Direct Messages section:

- dm/send: add an Auth note explicitly stating that no caller
  credential is required. The proxy is bound to 127.0.0.1 and trusted
  by EvoMap Hub on behalf of the registered A2A_NODE_ID (see
  network_endpoints in the frontmatter), so contributors do not need
  to pass a Bearer, signature, or API key. This forecloses the
  ambiguity about whether the curl example should include an
  Authorization header (answer: it should not).

- dm/list: soften the limit row. An earlier draft claimed a 100-message
  cap enforced as a server-side clamp, but a grep of
  src/proxy/server/routes.js found no enforcement of any specific
  value on /dm/list, so the 100 was speculative. The new phrasing is
  honest: large pages are slower and may be rate-limited; page via
  offset for big windows.

(A middle-version of this commit added a since cursor field on
/dm/poll with a received_at cursor scheme. That was reverted before
commit because src/proxy/ and src/atp/ have no received_at emitter
in any response shape, so the cursor pattern claim was ungrounded.
/dm/poll therefore continues to document the limit field only.)

Pure documentation. README.md and the sister
README.{ja-JP,ko-KR,zh-CN}.md are not touched. No source, script,
or workflow changes.
Add a complete local-development harness for
scripts/bedrock-alias-watch.sh so the operator can edit the AWS-doc
fixture in real time and watch the resulting Slack payload without
waiting for the daily cron:

- scripts/bedrock-alias-watch.sh: The production watcher. Daily cron
  diffs the live AWS Bedrock supported-models doc against
  KNOWN_BEDROCK_ALIASES in src/proxy/router/messages_route.js and
  posts a Slack message when the canonical table needs a new entry,
  a dated-revision bump, or a retirement. Three diff layers (new
  family/major/minor, dated revision, retired) suppress cross-region
  siblings the canonicalizer already handles, plus idempotent seen_*
  tracking so re-runs do not double-post.

- scripts/dev-watch.sh: A developer-mode looping runner. Spawns the
  local Slack receiver, points MESSAGES_ROUTE_FILE at
  dev-fixtures/messages_route.js, points AWS_BEDROCK_URL at
  file://dev-fixtures/aws.html, and re-runs the watcher every
  WATCH_INTERVAL seconds (default 60). Ctrl-C cleans up the receiver
  and exits. Pairs with make watch, make watch-once.

- scripts/dev-slack-receiver.js: A 60-line http.createServer on
  127.0.0.1 with a random port (so it cannot collide). Writes the
  chosen port to --port-file and appends each POST body to --log-file
  (pretty-printed if JSON). Catches SIGINT/SIGTERM and shuts down
  cleanly so the parent watch loop does not leak processes on Ctrl-C.

- test/bedrock-alias-watch.test.js: Node --test port of the full
  11-block bash test harness. Each block spawns the bash script with
  a controlled environment, points it at a tmp-dir state + a file://
  mock AWS doc, and captures the alert via an in-process Slack
  receiver. Coverage: new-family detection, dated-revision was/now,
  retirement alert, came-back, DRY_RUN, AWS fetch fail, backwards-
  compat for round-1 seen_ids state files, SLACK_WEBHOOK_URL unset,
  idempotency on re-runs. All 11 it() blocks verified green under
  node --test in ~1.6s.

- dev-fixtures/aws.html, dev-fixtures/messages_route.js,
  dev-fixtures/.gitignore: Seeded fixtures the operator edits during
  make watch. .gitignore excludes runtime artifacts (state/,
  .receiver.port, .receiver.pid, receiver.log); the seeded fixtures
  themselves ARE committed.

This unblocks the edit-while-you-watch workflow that previously
required either modifying the live AWS URL or hand-running tests.
Pure developer ergonomics; no production-path change to the proxy
or the watcher itself.
Three latent polish items closed out in one atomic chore commit so PR
EvoMap#596 picks them up together. All three were deferrable from prior
review rounds; consolidated now to avoid further re-review churn.

(a) SKILL.md dm/send Auth note. The prior wording said the proxy was
"trusted by EvoMap Hub on behalf of the registered A2A_NODE_ID". This
was imprecise -- it implied the Hub attributed contributor calls back
to the caller. New wording explicitly notes that the proxy
authenticates to EvoMap Hub using its own A2A_NODE_ID-issued
credentials, and the contributor identity is not relayed. Agents on
the same machine call without Bearer / signature / API key, and the
proxy handles Hub-side authentication on its own behalf.

(b) SKILL.md dm/list row Notes column. Dropped the backticks around
offset in the limit row so it matches the plain-text Notes pattern
used by neighboring DM-table cells (Target node id, Message body,
Free-form structured metadata, Max messages to return, Skip first
N messages all use plain-text prose in their Notes).

(c) scripts/bedrock-alias-watch.sh. Refactored the hardcoded
"(global|us|eu|ap)" regional prefix alternation into a config-driven
BEDROCK_REGIONAL_PREFIXES env var (default global|us|eu|ap). Four
regex consumers now route through a single PREFIX_REGEX variable
which wraps the env var in (...) for proper ERE grouping
(without the (...) wrap, the bare alternation would parse as
"global OR us OR eu OR ap" in sed unanchored, which is not the
intended semantics):

- KNOWN extraction (grep on src/proxy/router/messages_route.js)
- canon derivation (sed in the canon map loop)
- AWS keys extraction (grep on the Bedrock doc HTML)
- AWS full-IDs extraction (grep on the Bedrock doc HTML)

Added a startup smoke check that warns when KNOWN_BEDROCK_ALIASES
contains a full-ID whose regional prefix is not in the configured
list -- catches the operator-forgot-to-extend gap (e.g. AWS adds
jp.* in some future quarter and the operator forgets to update
either the env var or the table) BEFORE the diff pass runs. The
check is a single cut + tr + grep -Fxq loop, cheap on thousands
of entries, idempotent on every run until the operator resolves.

Verification gates:
- npm run check-links: rc 0, PASS 21 cross-links (no anchor drift)
- node --test test/bedrock-alias-watch.test.js: rc 0, 11/11 it()
  blocks pass in ~1.6s. The env-var default preserves the
  original regex exactly; the smoke check fires only on out-of-
  list prefixes which the production KNOWN_BEDROCK_ALIASES table
  does not contain.
- bash -n syntax check on bedrock-alias-watch.sh: clean.

No production-path behaviour change for the four available regions
(or the thousands of historical ID states). The env var is opt-in:
if an operator does nothing, the script behaves identically to
before this commit.
CI gate that prevents the missed-migration pattern from the prior chore
commit (the (global|us|eu|ap) -> BEDROCK_REGIONAL_PREFIXES refactor)
from ever shipping again. Three file edits:

- package.json: adds check-bedrock-prefix npm script. The gate pipes
  grep -nF '(global|us|eu|ap)' scripts/bedrock-alias-watch.sh to
  filter out comment lines (grep -vE '^[0-9]+:[[:space:]]*#') and
  uses an if/then/else wrapper to orchestrate exit codes correctly:
  0 non-comment hits -> exit 0 (PASS); >=1 hit -> echo FAIL message +
  exit 1 (FAIL).

- .github/workflows/link-check.yml: inserts a new Step 2 step in the
  capture block that runs the new gate before the existing check-links
  (now Step 3). On push-to-main, the per-branch log captures the
  verdict; on PR events against main, the gate fails the workflow if
  a regression is detected.

- .github/workflows/link-check-dev.yml: same Step 2 insertion for the
  dev-only soft variant. Workflow-level continue-on-error: true means
  a regression shows as a red advisory check on PRs but does not
  block merge.

Design notes:

- grep -nF (fixed-string) is used instead of grep -nE so the JSON
  string has no regex-escaping concerns and the gate is robust against
  awk / perl / heredoc regressions, not just grep / sed. The original
  reviewer-suggested pipe ... || exit 0 is grammatically inverted
  (would always exit 0 regardless of hits) and not used.

- The if/then/else/fi wrapper makes the exit-code orchestration
  explicit and shell-portable (POSIX /bin/sh semantics confirmed by
  the test.yml Windows + macOS advisory run matrix).

- False-positive class: a non-comment line that legitimately mentions
  the literal. There are zero such lines in the current post-amend
  script (verified by dry-validation in the prior basher); if a
  future contributor adds one unintentionally that's not a regex
  context, the gate will surface it for editing rather than silently
  accepting.

Verification gates:
- npm run check-bedrock-prefix on current state: PASS (0 hits)
- npm run check-links: still PASS 21 cross-links
- node --test test/bedrock-alias-watch.test.js: still 11/11
- Negative smoke (pre-commit, reverted): gate cleanly exits 1 with
  the FAIL message when a regression line is added to the script;
  git checkout scripts/bedrock-alias-watch.sh restored clean state.
Adds a CI sentinel (`npm run check-bedrock-prefix`) that asserts no
hardcoded `(global|us|eu|ap)` regex literal appears in any non-comment
line of scripts/bedrock-alias-watch.sh. Also adds a caller-side fix
migrating the FTH substitution site (step 4b dated-revision sed at
line 193) from the hardcoded literal to `${PREFIX_REGEX}` so the gate
is green on this commit. Prior amend cycle's verifier reported 0 hits
before the force-push but the post-push state was actually missing
site #5; the CI sentinel itself caught the regression in the post-push
re-verify (gate exited 1 with FAIL message echoing line 193), which
is exactly what the sentinel was put in place to do.

Files:
  * package.json -- add `check-bedrock-prefix` script entry, three-stage
    fixed-string pipe wrapped in `if/then/else/fi`:
      stage 1: grep -nF '(global|us|eu|ap)' scripts/bedrock-alias-watch.sh
      stage 2: grep -vE '^[0-9]+:[[:space:]]*#'   # drop comment prose
      stage 3: if non-empty -> FAIL + exit 1, else exit 0.
  * .github/workflows/link-check.yml -- new Step 2 line
      `- name: Step 2: npm run check-bedrock-prefix`
      inserted before the existing Step 2 `check-links`, with Step
      numbers re-stamped to 3..N.
  * .github/workflows/link-check-dev.yml -- same insertion for the
    dev-only soft variant, identical numbering bump.
  * scripts/bedrock-alias-watch.sh -- single-line fix at line 193
    (step 4b dated-revision sed). Single-quoted sed literal replaced
    with a double-quoted form so `${PREFIX_REGEX}` expands:
      before: sed -E 's/^(global|us|eu|ap)\.anthropic\.claude-...'
      after:  sed -E "s/^${PREFIX_REGEX}\.anthropic\.claude-..."
    All five substitution sites (KNOWN extraction, two canon
    derivations, AWS keys extraction, AWS full-IDs extraction) now
    use `${PREFIX_REGEX}` and are config-driven by
    `BEDROCK_REGIONAL_PREFIXES` (default `global|us|eu|ap`).

Gate exit codes:
  * current source tree: exit 0 (all 5 sites migrated).
  * pre-fix source tree: exit 1 (line 193 would be flagged).

Why this gate exists: two amend cycles in this session were required
because the substitution-site enumeration undercounted (`grep -cE`
undercount, sed/grep contexts split across regex/sed). The gate
turns the missed-migration pattern into a build failure so a future
regression recreating it is caught at PR time, not after force-push.
Extends the smoke check (step 1b in scripts/bedrock-alias-watch.sh, the
'does any KNOWN prefix fall outside BEDROCK_REGIONAL_PREFIXES?' check)
with a NEW direction B: 'does any env-var token FALL OUTSIDE the
documented default AND lack a matching KNOWN_BEDROCK_ALIASES entry?'.
Catches operator typos (e.g. 'europe' instead of 'eu') without nagging
about legitimate extensions (e.g. 'jp') once the table is updated.
Inverse semantics from direction A: B fires when env has a token the
table has not caught up with; A fires when the table has an entry the
env has not caught up with.

Plus 2 new test cases (test/bedrock-alias-watch.test.js):

  Run 10: BEDROCK_REGIONAL_PREFIXES=us|eu|ap + production MOCK_JS.
    Direction A fires (3 global.* IDs flagged) — the 3 production
    entries have 'global' prefix which is not in the env var.
    Direction B is silent — env \ default is empty since us|eu|ap is
    a strict subset of the documented default 'global|us|eu|ap'.

  Run 11 (dual sub-case): BEDROCK_REGIONAL_PREFIXES=global|us|eu|ap|jp.
    Sub-case A: with production MOCK_JS only, direction B fires 1
    (the 'jp' token — non-default + no KNOWN coverage). Direction A
    is silent ('global' and 'jp' both appear in env).
    Sub-case B: with MOCK_JS + JP_MOCK_JS injection (one
    jp.anthropic.claude-opus-4-7 entry), direction B is silenced
    (legitimate extension, not a typo). JP_MOCK_JS constant
    configures the trailing-comma entry + closing brace injection
    via .replace(/\}\);\n?$/, JP_MOCK_JS).

Files:
  * scripts/bedrock-alias-watch.sh -- new step 1c block (~14 lines)
    inserted between step 1b's closing fi and step 2. Uses process
    substitution `< <(printf | tr | sort -u)` so the while loop stays
    in the parent shell (continue works across the loop body without
    implicit subshell). DEFAULT_PREFIXES = 'global|us|eu|ap' matches
    the documented default in the script header. KNOWN_PREFIXES_FILE
    extracts KNOWN prefixes via `cut -d. -f1`. EXTRA_TOKEN_FILE
    captures env tokens that are non-default + non-KNOWN.
  * test/bedrock-alias-watch.test.js -- runWatch({...}) extended to
    accept a `mockJs = MOCK_JS` param (default). JP_MOCK_JS constant
    + Run 10 + Run 11. Total test file delta: ~50 lines.

Gate (npm run check-bedrock-prefix) remains green -- the literal
'(global|us|eu|ap)' is not introduced in any regex/sed context by
this change. The new code uses the unparenthesized default token list,
which the gate's regex (which requires the literal parens) does NOT
match.

All gates re-verified: bash -n clean, npm test 13/13, npm run
check-links clean, gate clean, git fsck clean.
@imxyanua

imxyanua commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

@joeshmoe97x-ship-it Following up on my earlier review. Since then this PR has moved in the opposite direction from the split I suggested: it has grown from ~1000 lines / 13 files to ~3200 lines / 31 files / 31 commits. A few of these are blocking for me, not style nits.

1. Production runtime changes are now bundled into a CI/docs PR.
The branch now edits src/atp/hubClient.js, src/proxy/extensions/sessionHandler.js, src/proxy/router/messages_route.js, and src/proxy/server/routes.js, and one commit is feat(atp/router): coerce hubBudget 0 and mark future-aliased bedrock .... Those are real behavior changes to the proxy/router/atp runtime, but the PR is titled and framed as link-check captures plus a dev-only workflow. Runtime changes need to be reviewed on their own merits, with a title and description that say what they do. Please pull the atp/proxy/router changes (and their tests) out into separate, focused PRs.

2. The auto-commit loop is polluting the history.
Roughly nine of the commits are ci: capture link-check log for <sha>. The workflow is committing its own log output back into the branch on every push, which is the dead-on-PR machinery I flagged last time, now actively generating churn. Please drop the auto-commit-back path; a CI job that runs the checker and fails on a broken link does not need to commit anything.

3. Debug and fixture scratch is being committed.
_distill-debug.js at the repo root ("keep as a development fixture") and dev-fixtures/messages_route.js (a copy of router code, while the real src/proxy/router/messages_route.js is also changed in this PR) should not land. The fixture copy will drift from the real module.

Path forward. I would like to help this land, but not as one 3200-line PR that mixes runtime changes with CI tooling. Concretely:

  • One small PR: scripts/check-readme-links.js + npm run check-links + a single contents: read CI job that fails on a broken link. No auto-commit, no per-branch artifacts, one badge.
  • Separate PR(s) for the bedrock-alias / hubBudget / payload-validation runtime changes, each titled for what it does.
  • Drop the debug and fixture-copy files.

I am happy to review those quickly once split. Thanks.

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