Skip to content

Wave 1+2: release blockers + hygiene/docs (WI-001..005, 013, 015, 017-021, 024)#43

Draft
calabamatex wants to merge 24 commits into
mainfrom
fix/wave-1-release-blockers
Draft

Wave 1+2: release blockers + hygiene/docs (WI-001..005, 013, 015, 017-021, 024)#43
calabamatex wants to merge 24 commits into
mainfrom
fix/wave-1-release-blockers

Conversation

@calabamatex

Copy link
Copy Markdown
Owner

Implements the v1.2 remediation workplan (agent-sentry/docs/remediation-plan.md), Waves 1–2. One commit per work item; each independently revertable.

Wave 1 (release blockers) — complete

  • WI-024 adopt v1.2 workplan as the single in-tree remediation plan
  • WI-001 bash CLI version from package.json + drift guards (contract test, sync-metadata:check)
  • WI-002 delete dead core/,audit/,tracing/ modules; tsconfig include = src/** only
  • WI-003 ⚠️ BREAKING: dashboard auth fails closed; shared timing-safe comparator; token out of logs; CLI --dev
  • WI-004 MAX_AUTO_SUGGEST_LEVEL = 5 — L6 never auto-suggested (owner decision, test-pinned)
  • WI-005 npm publish --provenance + environment-gated publish job

Wave 2 (hygiene + docs) — commits follow on this branch

WI-017 (EU AI Act claim), WI-018 (default-level doc + contract), WI-019 (0.5→0.6 migration guide), WI-021 (lint warnings → 0, CI-blocking), WI-013 (real e2e smoke), WI-015 (repo purge incl. .claude/ per owner decision).

Owner actions at merge/tag time

  • Delete legacy tags v4.0.0/v4.1.0-beta (WI-001 step 4): git push origin :refs/tags/v4.0.0 :refs/tags/v4.1.0-beta
  • Add required reviewers to the new npm-publish environment (WI-005)
  • Optional: configure npm Trusted Publishing to drop NPM_TOKEN

Note: full vitest suite runs in CI only (documented local OOM); targeted suites green locally per commit messages.

🤖 Generated with claude-flow

calabamatex and others added 6 commits July 2, 2026 18:07
Replaces the completed phased remediation plan with the reviewed,
agent-executable v1.2 workplan. Open items from the old plan ported:
Task 6.1 -> WI-103, 6.2 -> WI-025, 6.3/6.4 -> WI-026, history-shrink
note -> WI-015 item 8. Prior implementation notes remain in git history.

Co-Authored-By: claude-flow <ruv@ruv.net>
- bin/agent-sentry.sh: replace hardcoded VERSION="4.0.0" with runtime
  resolution from the co-located package.json (fallback: "unknown")
- sync-release-metadata.ts --check: fail on any hardcoded
  VERSION="<semver>" literal in the shell script
- doc-contracts: pin the no-hardcoded-semver invariant
- LEVEL_NAMES in sync script: add missing Level 6 (Risk Watch)
- CHANGELOG: fix note + versioning-history section (v4.x tags are
  AgentOps-era; deletion is a repo-owner action at push time)

Verified: 'bash bin/agent-sentry.sh version' prints 0.6.0-beta.1;
tests/contracts/doc-contracts.test.ts 10/10 green.

Co-Authored-By: claude-flow <ruv@ruv.net>
- remove core/, audit/, tracing/ (642 LOC outside src/, imported by
  nothing — all '../core/event-bus' imports resolve to src/core/)
- tsconfig include narrowed to src/**/*.ts; dist/ no longer ships
  dist/core|audit|tracing
- .npmignore entries for the removed dirs dropped
- diff check: stray core/event-bus.ts (singleton variant) had no
  behavior missing from src/core/event-bus.ts; nothing ported

Verified: build clean; dist/ contains only src/; contracts 21/21;
lint 0 errors (18 pre-existing warnings, tracked by WI-021).

Co-Authored-By: claude-flow <ruv@ruv.net>
… (WI-003)

BREAKING CHANGE: DashboardServer refuses to start without a token.
Set AGENT_SENTRY_DASHBOARD_TOKEN, pass { token }, use CLI --dev to
auto-generate one, or AGENT_SENTRY_NO_AUTH=1|true (unsafe, warned).

- new src/utils/timing-safe.ts: timingSafeStringEqual extracted from
  mcp/auth.ts (behavior-preserving); both MCP access-key and dashboard
  bearer/SSE-query checks now share the one comparator
- dashboard server no longer logs the token (was in start() info log);
  startup logs only auth: token|disabled
- CLI dashboard: --dev flag prints the generated token; tokenless start
  without --dev exits non-zero with a clear message
- tests: fail-closed constructor, NO_AUTH bypass, devMode round-trip,
  equal-length wrong token (bearer + query), comparator unit tests
- docs: dashboard-guide gains Authentication section incl. the SSE
  query-param exposure trade-off and mitigations

Verified: build + lint clean; dashboard/utils/mcp/cli suites 436/436;
no ===/!== comparisons against this.token remain in src/dashboard/.

Co-Authored-By: claude-flow <ruv@ruv.net>
Owner decision (2026-07-02): Risk Watch (L6, [experimental]) is never
auto-suggested. getNextLevel()'s bound is now the named constant
MAX_AUTO_SUGGEST_LEVEL = 5 with rationale comment; docstring corrected
('max auto-suggested level', not 'max'); behavior test-pinned at
levels 4/5/6; enablement-model.md states the intent.

Verified: enablement + contracts suites 73/73; build clean.

Co-Authored-By: claude-flow <ruv@ruv.net>
- npm publish --provenance with id-token: write (Sigstore attestation
  linking the package to the workflow run)
- publish job gated behind the npm-publish GitHub environment; owner
  can add required reviewers in Settings -> Environments for manual
  approval before publishes
- NODE_AUTH_TOKEN retained until npm Trusted Publishing (OIDC) is
  configured on npmjs.com (owner action, tracked in WI-005)

Verified: workflow YAML lints clean.

Co-Authored-By: claude-flow <ruv@ruv.net>
@vercel

vercel Bot commented Jul 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agent-sentry Ready Ready Preview, Comment Jul 2, 2026 11:45pm
agent-sentry-5jxd Ready Ready Preview, Comment Jul 2, 2026 11:45pm

calabamatex and others added 6 commits July 2, 2026 18:28
A hash-chained log supports Article 12 record-keeping; it does not
confer compliance and no assessment backs the phrase. Both instances
(root README, planning synopsis) now say 'designed to support ...
record-keeping requirements'. Also: 'tamper-proof' -> 'tamper-evident'
(hash chains detect tampering; they do not prevent it).

Co-Authored-By: claude-flow <ruv@ruv.net>
…cs (WI-018)

Deeper than the planned doc fix — the doc was accurately describing a
code bug: src/mcp/tools/health.ts fell back to level 3 when no config
was readable, while the shipped config, README, and package docs all
say 2 (risk-score.ts correctly used 2).

- new exported DEFAULT_ENABLEMENT_LEVEL = 2 in enablement/engine.ts;
  health.ts and risk-score.ts fallbacks use it (no bare literals)
- enablement-model.md "Default Level Rationale" rewritten for Level 2;
  notes init's deliberate start-at-1 behavior for new projects
- contract test pins constant == shipped config == documented default
  and rejects bare fallback literals in both tools
- health.test.ts engine mock gains the new constant

Verified: contracts + enablement + mcp/tools suites 159/159; build clean.

Co-Authored-By: claude-flow <ruv@ruv.net>
- new docs/migration-0.5-to-0.6.md: MCP HTTP auth-required breaking
  change, dashboard fail-closed change (0.6.0-beta.2), CORS wildcard
  opt-in, NO_AUTH escape hatch, env-var summary, upgrade checklist
- mcp-integration.md: three verified-false statements fixed (claimed
  open access without key, wildcard CORS without key, XOR comparison
  loop — actual: fail-closed, wildcard refused, crypto.timingSafeEqual
  via shared util)
- linked from both READMEs and CHANGELOG [Unreleased]

Verified: contracts 22/22.

Co-Authored-By: claude-flow <ruv@ruv.net>
- unused imports/vars removed: fs+ensureDirectorySafe (config.ts),
  fs (enable.ts), TodoItem (handoff-templates.ts), EnforcementAction
  (enforcement/engine.ts), actualPort (transport.ts)
- dead readMemoryFiles() removed (handoff.ts)
- any -> unknown with typed access: enable.ts, init.ts, health.ts (2)
- require() -> static imports: init.ts (child_process), health.ts
  (fs, path), risk-score.ts (fs; stale eslint-disable dropped)
- SIGINT/SIGTERM handlers wrapped (no-misused-promises, server.ts)
- lint script: eslint src/ --max-warnings 0
- FOLLOWUPS item 3 marked resolved (count was stale at 16; actual 18)

Verified: build clean; lint 0 errors 0 warnings under --max-warnings 0;
mcp/cli/contracts/enforcement suites 410/410.

Co-Authored-By: claude-flow <ruv@ruv.net>
smoke-test-install previously only require()'d the package and
syntax-checked entry points. Now, against the packed artifact:
- npx agentsentry init --json && health --json: exit 0 + status key
- scripts/mcp-smoke.cjs: spawns the installed MCP server over stdio,
  JSON-RPC initialize -> tools/list (asserts agent_sentry_check_context
  present) -> tools/call (asserts estimated_tokens in result)

Verified locally against a real npm pack + fresh install:
init exit 0, health status=healthy, MCP smoke OK (11 tools).

Co-Authored-By: claude-flow <ruv@ruv.net>
Owner decision (2026-07-02, recommendation confirmed by proceed):
.claude/ purged + gitignored.

Removed from tracking:
- website/ (contained only .claude-flow daemon logs)
- root package-lock.json (stub: empty packages, no root package.json)
- .claude/ (268 files of generic claude-flow scaffolding) + .mcp.json
  -> gitignored; CONTRIBUTING.md documents regenerate-locally policy;
  committed CLAUDE.md verified to contain no .claude/ references
- agent-sentry/dashboard/agent-sentry-dashboard.html (unreferenced
  static snapshot; src/dashboard/html.ts authoritative) — live docs
  (root README, quick-start, dashboard-guide) now point at
  'agentsentry dashboard'; planning/ docs left as design history
- agent-sentry/models/all-MiniLM-L6-v2.onnx git-LFS pointer (133 B;
  oid matches the SHA-256 pinned in embeddings.ts — runtime downloads
  and verifies the same artifact; tokenizer.json remains shipped)

.gitignore: + .claude/ .mcp.json website/ **/.claude-flow/
Deferred (owner sign-off required): git filter-repo history shrink of
the previously-tracked coverage/ artifacts (WI-015 item 8).

Verified: build + lint (--max-warnings 0) clean; contracts, embeddings,
health suites 45/45.

Co-Authored-By: claude-flow <ruv@ruv.net>
calabamatex and others added 5 commits July 2, 2026 18:49
Protection existed but required phantom contexts ('build', 'test')
that no workflow reports — only admin bypass made merges possible.
Now (via gh api, documented in CONTRIBUTING.md):
- required checks: build-and-test (18/20/22), lint, security,
  smoke-test-install, doc-validation (strict)
- linear history required; force-push/deletion blocked
- PR review requirement removed per owner decision (solo maintainer;
  re-enable at >=1 when a second maintainer joins)
- repo setting: auto-delete merged head branches (WI-016 partial)

Co-Authored-By: claude-flow <ruv@ruv.net>
- downloadFile: manual res.pipe(file) replaced with promisified
  stream.pipeline + a Transform cap-guard — an over-cap or mid-stream
  error now destroys response, guard, and write stream together (the
  old code could hang the promise forever on a response error, since
  pipe() does not propagate errors, and strand a partial file)
- new downloadAndVerify: download to <dest>.download-<rand>, verify
  SHA-256, then rename into place; destPath is never observable
  partially written or unverified (previously a corrupt destPath from
  a crash would be trusted by the next ensureLoaded existsSync check)
- redirect/error responses now drained (res.resume) to free sockets
- tests (real local HTTP server): happy path, redirect, mid-stream
  socket kill, over-cap stream without content-length, checksum
  mismatch, connect error — each asserts no partial file and no
  orphaned temp files

Verified: new suite 6/6; full tests/memory 357 passed / 11 skipped
(Supabase-gated, matches baseline); build + lint clean.

Co-Authored-By: claude-flow <ruv@ruv.net>
New shared src/utils/unicode-normalize.ts (normalizeForMatching):
NFKC + zero-width (ZWSP/ZWNJ/ZWJ/WJ/BOM) + bidi-control stripping.
Applied in:
- enforcement matchesRule: action + substring patterns normalized
- scanForSecrets: content normalized before pattern matching
  (newline-preserving, so line numbers unchanged — test-pinned)

Attack tests added (enforcement + scanner): zero-width splices,
fullwidth Latin, NFC/NFD variants, RTL-override wrapping.
Verified the tests detect the vulnerability: 6/18 fail with the
normalization stashed, 18/18 pass with it. Normalization is an
identity on plain ASCII (test-pinned), so existing golden-file eval
behavior is unchanged by construction (run-evals.sh needs yq,
unavailable locally; evals are not CI-gated — noted).

Suites: security/primitives/enforcement 153/153; build + lint clean.

Co-Authored-By: claude-flow <ruv@ruv.net>
Audit (new tests/security/redos.test.ts) found two genuinely
vulnerable patterns, not just fuzz noise: the SQL-injection
detectors' unbounded '.*?' gaps backtracked ~840ms on 500KB of
'execute(' + quote-flood input.

- rewrote both with delimiter-excluding bounded runs
  ([^$\n]{0,500} / [^+\n]{0,500}) — the run can't contain the
  delimiter it searches for, so no re-scan; 840ms -> 6.4ms at 1MB.
  Detection unchanged (template-literal + concat cases still caught,
  clean queries still pass) — verified.
- MAX_SCAN_BYTES (1MB) input cap applied in scanForSecrets and the
  scan_security handler (result flags 'truncated') — defense-in-depth
  bounding all patterns, present and future.
- SECRET_PATTERNS / SECURITY_PATTERNS exported for the audit.
- test policy: static recheck hard-fails on EXPONENTIAL complexity;
  polynomial is gated by the authoritative timing test (every pattern
  vs 1MB adversarial input, <100ms) since recheck's Java-less fuzz
  backend inflates polynomial degree for multi-bounded-quantifier
  patterns that are near-linear in V8.
- recheck added as devDependency.

Verified: security/primitives/scan-security suites 197/197; redos
audit 58/58; build + lint clean.

Co-Authored-By: claude-flow <ruv@ruv.net>
…025)

The coordinator header flatly stated 'No CAS/compare-and-swap — race
conditions possible', but the default SQLite path already uses atomic
CAS (INSERT OR IGNORE + fencing token, src/memory/providers/
sqlite-provider.ts:370) and a contended-race test proves exactly one
of two concurrent acquirers wins (tests/coordination/atomic-locks).

WI-025 asked for either an atomic guard or an honest @experimental
marker; the guard already exists — this fixes the inverted honesty.
Header and README capability row now describe both tiers accurately:
atomic by default, event-sourced best-effort only when no
atomic-capable provider is wired. No code change.

Verified: coordination suite 82/82; build + lint clean.

Co-Authored-By: claude-flow <ruv@ruv.net>
calabamatex and others added 5 commits July 2, 2026 19:28
…ise.all (WI-009)

Audit of all 6 Promise.all sites in src/:
- enrichment.ts enrichEvent: CONVERTED to allSettled. Providers are
  independent; one throwing previously discarded ALL enrichment for
  the event. Now merges fulfilled results, logs rejections by index.
- batch.ts searchBatch/listBatch: KEPT (public batch API, positional
  return types with no error channel — allSettled would be a breaking
  shape change; callers wanting isolation call search/list directly).
- benchmark.ts concurrent R/W: KEPT (a failed task invalidates the
  throughput measurement — must surface).
- supabase-base.ts aggregate ×2: KEPT (every count/timestamp feeds one
  OpsStats; partial = silently-wrong aggregate).
Each retained site now carries a one-line justification comment.

Tests: failing-provider-doesn't-discard-others + all-providers-fail-
resolves-empty. enrichment + batch suites 60/60; build + lint clean.

Co-Authored-By: claude-flow <ruv@ruv.net>
The per-IP limiter keyed on socket.remoteAddress, collapsing all
clients behind a NAT/proxy into one bucket and resettable by IP
rotation. New deriveRateLimitKey(req):
- authenticated -> per-key bucket (sha256 of x-agent-sentry-key), so
  two keys sharing an IP are isolated and a key can't be evaded by
  IP rotation
- AGENT_SENTRY_TRUST_PROXY=1|true -> first X-Forwarded-For hop;
  header ignored by default (can't be forged to spoof buckets)
- else -> socket peer address
Middleware uses it; defensive against missing headers.

Documented in docs/configuration.md (env-var table refreshed with the
0.6 auth/CORS/sqlite vars too). Tests: default-ignores-XFF,
trust-proxy-honors-XFF, per-key isolation (two keys one IP; same key
different IPs), key-beats-XFF precedence.

Verified: full mcp suite 151/151; build + lint clean.

Co-Authored-By: claude-flow <ruv@ruv.net>
…r (WI-011)

- AGENT_SENTRY_SQLITE_SYNCHRONOUS selects NORMAL (default) | FULL;
  invalid values fall back to NORMAL. Documented in memory-model.md
  with the WAL/power-loss trade-off and the hash-chain torn-tail
  detection note.
- Fixed latent bug: QueryOptimizer.optimizeConnection() hardcoded
  'synchronous = NORMAL' and ran AFTER the provider set the pragma,
  so a FULL choice was silently reset. synchronous is now solely
  owned by SqliteProvider.initialize().
- tests: provider honors NORMAL/FULL/invalid; optimizer no longer
  touches synchronous (preserves a caller's FULL).

Verified: sqlite + optimizer suites 33/33; build + lint clean.

Co-Authored-By: claude-flow <ruv@ruv.net>
…(WI-023)

run-benchmark.ts wrote benchmarks/baseline.json unconditionally, so
the documented command dirtied a tracked file every run. Now:
- default: benchmarks/results-<timestamp>.json (gitignored)
- --update-baseline: deliberately overwrites baseline.json
Header documents baseline.json as an informational, platform-labeled
reference (the regression test uses hardcoded thresholds, not this
file). baseline.json already embeds its capture platform/CPU/Node in
the 'system' block.

Verified: results-*.json is gitignored; baseline.json stays clean on
a default run; flag logic + tsc checked.

Co-Authored-By: claude-flow <ruv@ruv.net>
Measured baseline ~85.7% lines. Raised the lines/statements floor to
84 in both vitest.config.ts and the CI --coverage flag (kept in sync).
functions stays 85, branches stays 75 — branches not raised now to
avoid a floor I can't verify locally (full-suite coverage OOMs); the
full CI coverage run on this PR is the check. FOLLOWUPS item 4 updated
with the remaining 85 / branch-ratchet steps.

Co-Authored-By: claude-flow <ruv@ruv.net>
calabamatex and others added 2 commits July 2, 2026 19:43
WI-026 audit findings:
- demock (part a): recall-context/capture-event already drive a real
  in-memory MemoryStore (:memory:); only shared-store singleton is
  mocked — verified, no change needed (done in prior beta-hardening).
- flaky timing (part b): PR #41's pollUntil/waitFor helper cleared the
  coordinator sleeps; one straggler remained (receive-since test used
  a fixed 10ms sleep before the second send). Replaced with a wait
  until the wall clock passes the cutoff — deterministic ordering,
  not a magic delay.

Verified: coordinator suite 24/24, green 3x consecutively.

Co-Authored-By: claude-flow <ruv@ruv.net>
New supabase-integration CI job runs the integration + e2e smoke
tests (which already self-skip on missing creds) by mapping
SUPABASE_TEST_URL/SUPABASE_TEST_KEY secrets to SUPABASE_URL/
SUPABASE_SERVICE_ROLE_KEY. When the secret is absent (forks, or before
a test project is provisioned) the job prints a skip notice and exits
0 — never a red X on forks.

docs/supabase-setup.md gains a CI Integration Testing section;
FOLLOWUPS item 5 updated with the remaining per-run-schema isolation
refinement.

Verified: ci.yml YAML lints clean.

Co-Authored-By: claude-flow <ruv@ruv.net>
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.

1 participant