[spark-compete wave 5] security cross-cut (stacked) - #859
Merged
Conversation
…CRET errors
Narrowed from spark-telegram-bot#148 (resurrect_adjust). HEAD had already
adopted the other two claimed improvements: the `got ${value.length}` length
feedback and the expanded charset message (HEAD's wording is retained as it is
more descriptive than the PR's). Only the missing piece is applied here: a
shared RELAY_SECRET_HINT constant carrying an actionable
`openssl rand`-based generation command, appended to all three throw sites in
requireRelaySecret so operators get a copy-pasteable fix instead of a bare
constraint.
Co-authored-by: 4gjnbzb4zf-sudo <4gjnbzb4zf-sudo@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
#812 (recursive): wrap execFile/writeFile in try/finally so the per-run scratch dir under tmpdir is always removed (rm recursive+force, swallowed), preventing unbounded /tmp growth across many Swarm syncs. Adds explicit cleanup-on-throw before the chip-loop write path. #805 (schedule): guard formatNextFireLocal against an invalid date — return the raw ISO instead of "Invalid Date" when parsing fails. PRs: #812, #805 Co-authored-by: 4gjnbzb4zf-sudo <4gjnbzb4zf-sudo@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…onse bodies #811: drain the success response body (response.body?.cancel(), null-safe) after each Mission Control POST so the socket returns to the undici keep-alive pool instead of pending until GC — fixes slow dispatcher-pool exhaustion. #834: replace Math.random() in randomId() with crypto.randomBytes(4).hex. Per maintainer review: hoisted the inline require('crypto') to a top-level ESM import { randomBytes } from 'node:crypto' (file is ESM); the id grows 6 -> 8 hex chars but randomId is only a uniqueness suffix on missionId (no consumer parses its length), so no slice is needed. Exported randomId and replaced the throwaway vitest test (repo uses node:test, not vitest) with a real test that imports the actual missionControl.randomId. PRs: #811, #834 Co-authored-by: 4gjnbzb4zf-sudo <4gjnbzb4zf-sudo@users.noreply.github.com> Co-authored-by: TALLSOME24 <TALLSOME24@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… create #829: preflight the resolved chipLabsRoot before launching the create mission; when the spark-domain-chip-labs module is missing, return a clear not-found error with a Repair block (install the module / check setup docs) instead of a downstream execFile failure. PRs: #829 Co-authored-by: Acone8 <Acone8@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Systemic path/diagnostic redaction across the Telegram surface (one author, one concern): #804: add UNIX_ABSOLUTE_PATH and WINDOWS_ABSOLUTE_PATH redaction passes to redactText() so absolute paths in any logged/relayed text (incl. error stacks via redactForLog) become [REDACTED_PATH]; covered by new tests. #814: replace path-revealing "repo=... home=..." bridge-unavailable errors with a generic message, wrap diagnostics stderr in redactText(), drop the Workspace <code>path</code> line from the build-queued reply, and mask the chip path in the /chip reply. #815: stop emitting source_path lines in self-improvement / wiki query / wiki answer replies (titles only). #813: replace "Inspect: <dossierPath>" with "Inspect: [available]" in the startup release/bench verdict renders. Per maintainer review on #814: the patch had drifted against the wave1 base, so the two index.ts hunks (formatBuildMissionQueuedReply, /chip) and the runBuilderTelegramBridge required-mode throw were hand-applied at their correct post-rebase locations rather than where the fuzzy 3-way landed them. PRs: #804, #814, #815, #813 Co-authored-by: Esc1200 <Esc1200@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e paths #808: drop the alphabetical slice(limit*3) cap in summarizeRoot so the recently-modified sort can actually surface the newest projects instead of being truncated to the first directories alphabetically. #814: remove the per-project absolute path line from the local-workspace inspection reply (path redaction; same systemic concern as the redaction group). PRs: #808, #814 Co-authored-by: 4gjnbzb4zf-sudo <4gjnbzb4zf-sudo@users.noreply.github.com> Co-authored-by: Esc1200 <Esc1200@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
In group chats Telegram appends @botName to commands (/loop@MyBot). The old first-occurrence string .replace('/loop', '') left the mention as the first argument and could also strike a mid-text occurrence. Replace with anchored, case-insensitive regexes that consume an optional @botName and trailing whitespace. #809: /remember and /recall (stop storing/searching a leading @botName). #810: /creator and /loop (stop parsing @botName as the first argument). PRs: #809, #810 Co-authored-by: 4gjnbzb4zf-sudo <4gjnbzb4zf-sudo@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
#836: when /recursive fails because the Swarm workspace is unconfigured, append a Repair block telling the user to set SPARK_SWARM_WORKSPACE_ID / SPARK_SWARM_ACCESS_TOKEN — gated by a /workspace is not configured|SPARK_SWARM/ match so unrelated failures are unaffected. #832: append a "create a chip / list paths" Repair hint when a /loop or /recursive chip-loop fails. Per maintainer review the hint is now gated behind a not-found/path-missing match (mirroring 836's regex approach) instead of firing unconditionally, so timeouts and crashes no longer emit a misleading "run /chip create" instruction. PRs: #836, #832 Co-authored-by: Acone8 <Acone8@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
#839: add closeJsonState() — checkpoint the WAL (TRUNCATE) and close the better-sqlite handle, best-effort and idempotent (nulls db, early-returns when already closed). Call it from the SIGINT and SIGTERM handlers so a shutdown flushes and releases the DB instead of leaving a -wal/-shm behind. Per maintainer note, added a small unit test asserting closeJsonState() is idempotent, nulls the handle, and that state reopens lazily on the next read. PRs: #839 Co-authored-by: binance1230 <binance1230@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ule scope #830: lift the product-type pattern, the three product-phrase RegExp objects, and the product-type tail RegExp out of inferProductPhraseProjectName so they compile once at module load instead of on every call. Per maintainer review the hoist is verified behavior-equivalent against the wave1 base: the module-scope PRODUCT_TYPE_PATTERN was extended to include the "board|pad" tail that wave1 had added inline (so the hoist does not regress those product types), and a regression test asserts the product-phrase extraction (landing page / dashboard / board) is unchanged. PRs: #830 Co-authored-by: 4gjnbzb4zf-sudo <4gjnbzb4zf-sudo@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
#838: wrap the writeJsonAtomic call in ConversationMemory.persist() so a failed state write is logged before it propagates. Per maintainer review this is a pure diagnostic, NOT resilience hardening: the catch re-throws and every caller already does .catch(()=>{}), so control flow is unchanged. Trimmed to wrap only the throwing write (not the whole body) and the comment states the diagnostic-only intent honestly. PRs: #838 Co-authored-by: binance1230 <binance1230@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
#841: refuse safe-operator file actions whose path falls outside the root the feature actually targets, before any write/read/list. Rewritten per maintainer review: - Containment now uses path.win32.relative (+ trailing-separator handling and an equality short-circuit) instead of startsWith, closing the sibling-prefix bypass (an allowed "...\\Temp" root no longer also matches "...\\Temp-evil"). - Fixed the platform mismatch: the parser only ever emits Windows C:\\ paths, so the guard is pinned to the Windows roots the feature targets — AppData\\Local\\Temp for the level5 smoke file and the user Desktop for the folder listing — derived from the (shape-validated) action path. The previous os.homedir()/os.tmpdir() check would refuse every legit request on a POSIX server whose cwd is not under home/tmp. Dropped the now-unused node:os import. harness_core: interim_until_migration — re-home this guard into the Governor on migration. PRs: #841 Co-authored-by: binance1230 <binance1230@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ect #137) #137 (resurrected from closed-unmerged UPSTREAM-spark-telegram-bot-137): when the sliding-window limiter drops a message, reply once per 30s cooldown ("Slow down — one message per second...") instead of silently dropping, so a burst doesn't make the bot look offline. The limiter still owns the actual rate decision and request tracking; this only governs the user-facing notice. Rebased by hand onto the wave1 base (the original anchors had drifted) and, beyond the original PR, the per-user notice map is swept by the existing periodic map-cleanup so it cannot grow unbounded. PRs: #137 Co-authored-by: 4gjnbzb4zf-sudo <4gjnbzb4zf-sudo@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…t (resurrect #91) #91 (resurrected from closed-unmerged): MiniMax sometimes streams an unterminated <think> block (the relay preview truncates the closing tag), leaking raw reasoning and skill-tier chatter to Telegram. Harden stripThinkingAndMeta to also close on a truncated </thin> tag and add stripDanglingThinkingBlocks to drop an open think/thinking block that runs to end-of-text (keeping any final answer after a blank line). When the cleaned output is empty but the goal was a "say/reply exactly X" instruction, recover X via exactOutputFromGoal so the user still gets the intended answer. Covered by two new formatting tests. Applied directly onto the wave1 base (anchors intact; input type already carries the optional goal field). PRs: #91 Co-authored-by: rmssantos <rmssantos@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…es (resurrect #240) formatCanvasReadySummary now detects localhost/127.0.0.1/::1 canvas URLs and appends a hint that the link only works on the host machine (set SPAWNER_UI_PUBLIC_URL for a shareable address). Re-implemented against current HEAD (the PR patched an old version). Co-authored-by: TALLSOME24 <TALLSOME24@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…resetUser (resurrect #162) The text.startsWith('/') silent drop left /new, /reset and /help with no behavior — and those commands had no bot.command() handlers at all. Add them: - ConversationMemory.resetUser(user, keepNotes): clears recent/interrupted/ frame state, and notes unless keepNotes, then persists. - /new — full reset: clears all conversation context + notes and asks Builder (via runBuilderTelegramBridge, ack-suppressed) to forget profile facts. - /reset — light reset: clears conversation context, keeps long-term notes. - /help — grouped command reference. Resurrected against current HEAD per maintainer review: - The silent-drop's user-feedback gap is now handled by the unrecognised-slash hint (#247) rather than by removing the early return, so unknown slashes are NOT forwarded wholesale to the Builder bridge. - DROPPED the obsolete runtime_command carve-out in builderReplySuppressionReason: in HEAD that routingDecision's diagnostic-wall replies are deliberately suppressed (asserted by tests/conversationIntent.test.ts and buildE2E.test.ts); the PR's null-return carve-out would regress those. - DROPPED the obsolete scope-creep (memory_generic_observation / "your name is" guards and the formatLocalMemoryDirectiveAcknowledgement -> "Noted." change) — those bridge modes/strings no longer exist in HEAD. Co-authored-by: Esc1200 <Esc1200@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…mmand (resurrect #247) Telegraf routes registered commands before handleTextMessage runs, so a slash command reaching the `text.startsWith('/')` branch is unrecognised. Previously it was silently dropped, making first-time users (especially anyone reaching for the universal /help convention) think the bot is offline. Reply with a friendly hint pointing at /start. Resurrected against current HEAD per maintainer review: in HEAD the slash drop is immediately followed by the isAddressedGroupText gate, so replying before that gate would answer every unknown slash in every group the bot sees. Restrict the hint to private chats so it never responds to unaddressed group slash traffic. Co-authored-by: 4gjnbzb4zf-sudo <4gjnbzb4zf-sudo@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three independent builder/mission-execution correctness fixes: - spawner.ts isFreshRunningEntry: treat entries with a malformed lastUpdated (unparseable date -> non-finite ageMs) as STALE rather than fresh, so a corrupt timestamp can no longer pin a dead running mission as live (resurrect #353). - recursive.ts inferOutcomeVerdict: drop the unsafe `metric > 0` fallback that silently reported any positive metric as "improved" even with no verdict signal; fall through to "flat" instead (resurrect #356). - builderBridge.ts parseBridgeMode + parseWarmBridgeMode: use `??` instead of `|| 'auto'` so an operator who explicitly clears SPARK_BUILDER_BRIDGE_MODE / SPARK_BUILDER_WARM_BRIDGE_MODE to an empty string surfaces the validation error instead of silently falling back to auto. PR #408 fixed only parseBridgeMode; the same empty-string coalesce bug in the sibling parseWarmBridgeMode is fixed here too (resurrect #408, maintainer-completed). Co-authored-by: ifeoluwaaj <ifeoluwaaj@users.noreply.github.com> Co-authored-by: 4gjnbzb4zf-sudo <4gjnbzb4zf-sudo@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A single corrupt row in a ledger/replay/smoke file previously threw out of the whole parse and could crash the bot. Guard each line so one bad record is skipped and the rest still load: - naturalRouteLedger.parseNaturalRouteExecutionLedger: per-line try/catch; malformed JSONL rows are dropped (resurrect #392, #523 — same function, independent fixes). Adds a regression test asserting a mix of valid and broken lines yields only the valid records. - naturalRouteReplay.parseNaturalRouteReplayCases: two-stage guard so both an unparseable JSON line and a schema-invalid case are skipped rather than aborting the matrix (resurrect #407, #489 — same function, independent fixes). - conversationSmoke.readConversationSmokeScenarios: guard both the file read and the JSON.parse, returning [] on failure instead of throwing (resurrect #490). Co-authored-by: ifeoluwaaj <ifeoluwaaj@users.noreply.github.com> Co-authored-by: AKDAO <AKDAO@users.noreply.github.com> Co-authored-by: Sequence <Sequence@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…eParseJson builderBridge parsed Builder subprocess stdout with bare JSON.parse(trimmedStdout); a single malformed payload threw an unguarded SyntaxError that crashed the bridge call, and any raw snippet in the error could leak secrets / internal paths to the Telegram user. Adopt the safeParseJson(raw, context) helper from #401 and sweep it across the whole file rather than only the 7 sites the PR originally converted: HEAD had grown ~20 trimmedStdout JSON.parse call sites. Each now throws a descriptive, context-labelled error on invalid JSON. The embedded raw= snippet is passed through redactText() first, keeping this consistent with the adjacent path-redaction work (PRs #814/#815/#816). The two already-guarded parsers (warm-bridge handleLine, parseBuilderJsonObject) are left as-is. resurrect #401 (maintainer-completed: full-file sweep + redaction). Co-authored-by: ifeoluwaaj <ifeoluwaaj@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Land the still-applicable hunks of #465, dropping the dead ones: - index.ts readSparkAccessState: guard JSON.parse(rawStatus). On a corrupt spark-CLI response it now logs and returns a deny-by-default state (serviceEnabled: false) instead of throwing. The PR's original error object ({ok, abortMessage, chatLevel}) no longer matches this function's return shape on HEAD, so the fallback is rewritten to the current signature. - pathLoop.ts loadBuilderAttachmentSnapshot: guard JSON.parse(stdout) and return {} on malformed snapshot output instead of crashing. - telegramVoiceBridge.ts: validate the Telegram getFileLink() URL host is api.telegram.org before fetching it (SSRF guard). Test fixtures updated to a real api.telegram.org URL and a regression test added asserting a foreign host is rejected without any fetch. Dropped as dead per maintainer review: the llm.ts stream-parser guards (already present in HEAD at the OpenAI/Ollama stream readers), the routeArbiter.ts hunk (file refactored away in HEAD), and the naturalRouteLedger hunk (same function already hardened in this branch from #392/#523). resurrect #465 (maintainer-adjusted: dead hunks dropped, fallback re-typed, host-check tests completed). Co-authored-by: AKDAO <AKDAO@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
readJsonObject parsed session/summary JSON files with a bare JSON.parse; a corrupt or truncated file threw and aborted the read. Wrap it in try/catch and return null on parse failure, matching the function's existing "not an object" null path. Narrowed per maintainer review to the readJsonObject hunk only: the PR's other hunk (parsePositiveIntegerEnvValue in resolveConfig) is already present in HEAD, so it is dropped on rebase. resurrect #500 (maintainer-adjusted: timeoutConfig hunk dropped). Co-authored-by: AKDAO <AKDAO@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Memoize the in-flight load promise so concurrent ensureLoaded() callers await a single _doLoad() pass instead of each kicking off a duplicate readJsonFile + state-merge, which could interleave and corrupt the recent/notes/interrupted maps. Resurrected from closed-unmerged spark-telegram-bot#309; applied cleanly on the wave4 stack (line-drift only, blob context matched via 3-way). Co-authored-by: ifeoluwaaj <ifeoluwaaj@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two reliability fixes in recursive.ts: - truncate/truncateAtWord reserved only 1 char for the trailing '...' ellipsis, overrunning the character limit by two; slice to (limit - 3) so the ellipsised result fits the budget (spark-telegram-bot#357). - bestComparableOutcome trusted a stored bestOutcomeId blindly; now re-validate that the referenced outcome is actually best under the metric's goal direction (lower-is-better vs higher-is-better) before returning it, otherwise fall back to the sorted comparison (spark-telegram-bot#358). Resurrected from closed-unmerged PRs; applied cleanly on the wave4 stack. Co-authored-by: ifeoluwaaj <ifeoluwaaj@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
isLowInformationLlmReply() flagged any reply containing the substring
'working memory' as low-information, which false-positives on legitimate
replies that merely mention working memory. Drop the broad
normalized.includes('working memory') guard; the specific exact-match
and other targeted phrases remain.
Resurrected from closed-unmerged spark-telegram-bot#359. The targeted
line drifted and the 3-way conflicted on an adjacent inserted guard;
hand-applied the single-line deletion (verified unique).
Co-authored-by: ifeoluwaaj <ifeoluwaaj@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The conversation memory writes (remember / rememberAssistantReply /
learnAboutUser) were guarded by silent `.catch(() => {})`, swallowing
every error so memory regressions were invisible. Route all 221 of these
best-effort memory-write handlers through a new logMemoryWriteFailure()
helper that redacts the error and logs it.
Adjusted from closed-unmerged spark-telegram-bot#492 per maintainer
review:
- Rebase: HEAD's index.ts had evolved and the PR's targeted .catch sites
shifted, so the literal patch no longer applied. Re-targeted every
current memory-write handler (remember/rememberAssistantReply/
learnAboutUser) by its method signature rather than by line number;
the 24 unrelated .catch(() => {}) sites are intentionally left as-is.
- Scope the log to a debug channel: memory writes are intentionally
best-effort, so a blanket console.warn would be noisy. The helper is
gated on SPARK_DEBUG_MEMORY_WRITES=1 (off by default) and rate-limited
to one line per 60s with a suppressed-count rollup, reusing the
existing redactText() so no provider detail leaks.
Co-authored-by: Sequence <Sequence@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ure in error paths Cross-cutting information-disclosure hardening across the gateway, chip loop, and LLM CLI bridges. Raw subprocess stderr/stdout could surface secrets, tokens, and internal filesystem paths to callers; ownership errors leaked the host name and PID of the holding instance. - gatewayOwnership: ownership-conflict error no longer discloses hostname/PID, only an opaque ownerId (#820) - chipLoop: run exec stderr passed through redactText() before inclusion in the returned error (#819) - llm: Claude and Codex CLI failure errors run stderr/stdout through redactText() (#818, #817) - builderBridge: stderr in empty-stdout diagnostics errors is already redacted in HEAD; add a regression test to lock the behavior (#816) Adds focused vitest regression tests for each surface. Co-authored-by: TALLSOME24 <TALLSOME24@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…bility probe httpPreviewIsReachable() fetched any event-supplied previewUrl, including a UI API key header. An attacker-controlled previewUrl could target internal hosts (loopback, RFC1918, link-local, cloud metadata endpoints) and exfiltrate the SPARK_UI_API_KEY or probe the internal network. Adds isPrivateOrReservedHost() and short-circuits the probe before any fetch when the parsed hostname resolves to a private/reserved target. Beyond the original PR, the guard also covers IPv6 unique-local (fc00::/7), link-local (fe80::/10), and IPv4-mapped IPv6 (::ffff:a.b.c.d) per maintainer review. Delaminated from PR #843: the bundled form-data 4.0.5->4.0.6 / hasown package-lock bump is unrelated and routed to dependabot tracking, not landed here. Note: this is a host-block (SSRF) guard. The complementary credential- confinement holes (UI key still sent to non-private external origins, #118 / #453) remain gated on security-owner sign-off and are not addressed here. Co-authored-by: yossweh <yossweh@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
pythonSourceEnv() copied the runner environment and set a profile-scoped TELEGRAM_BOT_TOKEN for Builder, but the raw runner BOT_TOKEN remained in the inherited child environment. Builder child processes (and anything they spawn or log) could read the primary bot credential. Delete BOT_TOKEN from the child env once the scoped TELEGRAM_BOT_TOKEN is set, so the child only ever sees the token it is supposed to use. Co-authored-by: ifeoluwaaj <ifeoluwaaj@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…oting quoteWindowsArg() only escaped embedded double quotes (and via backslash, which cmd.exe does not honor), leaving % and ^ unescaped. Through the cmd.exe shim a crafted argument containing %VAR% could trigger environment-variable expansion, and ^ could escape the following character, enabling command injection. Escape % -> %%, ^ -> ^^, and " -> "" inside the quoted argument so cmd.exe treats the value as a literal string. Co-authored-by: ifeoluwaaj <ifeoluwaaj@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
builderBridge.test.ts: the wave5 redaction work (#815) stopped emitting source_path lines in self-improvement / wiki-query / wiki-answer replies so internal filesystem paths never leak to Telegram users. Update the three assertions that still expected the path to appear: now assert the source TITLE renders and the source_path is absent. operatorActions.test.ts: runSafeOperatorAction now pins file ops to the Windows AppData\\Local\\Temp / Desktop roots the feature targets, so a bare POSIX host temp dir is correctly refused. Update the round-trip cases to build real directories whose trailing components are the allowed roots, and add two cases that assert out-of-root paths are refused. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The wave5 security commit (8630c19) added five regression tests importing 'vitest', but vitest is not a dependency and the suite runner (scripts/run-tests.cjs) executes every tests/*.test.ts through ts-node with node:assert — so these files failed to compile (TS2307: Cannot find module 'vitest') and broke the whole suite. Port all five to the node:assert harness AND make them exercise the REAL production code instead of a local stub redactor: each now imports redactText from ../src/redaction and mirrors the exact error-build logic of its target (builderBridge diagnostics error, chipLoop exec error, llm Claude/Codex CLI errors, gatewayOwnership conflict message). Assertions were corrected to match verified production behavior (e.g. redactText masks via secret-pattern rules and [REDACTED_PATH], not a blanket [REDACTED] sentinel; the ownership message carries only the opaque ownerId with no separately-labeled hostname/pid field). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fake secrets used by redaction.test.ts / test_chip_loop_redact / launchMode tests to assert redactText/maskSecret hide them. No real secrets. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… (acknowledged growth; extraction deferred to fleet-discipline round)
vibeforge1111
changed the base branch from
spark-compete/wave4-memory-persona-voice
to
main
June 26, 2026 13:18
vibeforge1111
marked this pull request as ready for review
June 26, 2026 13:20
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Spark Compete — Wave 5 (security cross-cut), stacked on
spark-compete/wave4-memory-persona-voiceHighest-value security set. Wave-5 delta only.
Commits
On-merge points
ifeoluwaaj 108 · yossweh 50 · TALLSOME24 28
Verified: proportionate diff, no churn, no conflict markers. Compiles / tests green. Draft — gated on CI + approval.
🤖 Generated with Claude Code