Repository navigation
[Tech debt] Cross-cutting infra — utils, security, errors, observability… #1989
Description
Tech Debt: Cross-Cutting Infra (utils, security, errors, observability, oauth, data, repositories, issues, channels, types, client, schemas)
Verification
Independently verified by adversarial re-audit (2026-06-01) against the actual source.
Tally: 14 substantive findings + 5 nits checked. All findings verified accurate; 0 refuted. Several counts corrected inline (weak-typing count, security subdir file/LOC). The three security-sensitive claims all hold:
worker-script.tshas no unit test — VERIFIED. The onlyworker-script.test.tsin the repo issrc/agent/react/use-chat/browser-inference/worker-script.test.ts, a different file. The security sandboxworker-script.tsis referenced only viaimport.meta.resolve("./worker-script.ts")inproject-worker.ts:350; no test imports its internals (it exports nothing). Sibling tests (deno-sandbox.test.ts, etc.) do not referenceserializeError/loadModule/applyProjectEnv/handleAppRoute.MemoryTokenStore.tokensunbounded vs TTL-cleanedstates— VERIFIED.memory.ts:14tokensmap is only mutated viaset/delete/clear— no eviction;states(line 15) callscleanupExpiredStates()on write and prunes on read. Exported singletonmemoryTokenStoreconfirmed at line 92.- No secret redaction in logger — VERIFIED.
rg -ni "redact|scrub|sanitize|mask|REDACTED"overutils/logger+observabilitymatches only comment-banner asterisks; no scrubbing layer exists.errors/logging.ts:73console.error(JSON.stringify(entry))confirmed.
Summary
This cluster is in good health. It is the most disciplined infrastructure layer audited: no file exceeds 500 LOC of production code, there are zero TODO/FIXME/HACK/XXX markers in production code across all 12 directories, weak typing is sparse and almost always justified (external npm modules, phantom types, Deno permission APIs), and the security-sensitive code (CSRF, path validation, OAuth state handling) is carefully written with explicit, well-commented intent. Error handling is uniformly non-swallowing — the catch (_) blocks that exist all carry comments explaining the expected failure and return a safe value. The debt that remains is mostly consistency (a few ad-hoc Error subclasses outside the central registry) and test-coverage gaps on complex code (the sandbox worker script), plus a handful of as any/as unknown as casts worth tightening.
| Category | Count | Worst offenders |
|---|---|---|
| 1. TODO/FIXME/HACK markers | 0 | none |
2. Weak typing (any/as any/as unknown as/@ts-ignore) |
~16 (was ~19) | security/sandbox/permission-system.ts (4× ts-ignore), channels/invoke.ts:270 (as any), utils/redis-client.ts (3× Record<string, any>) |
| 3. Swallowed errors / registry inconsistency | ~3 | utils/semaphore.ts, utils/circuit-breaker.ts, security/path-validation use bespoke Error types, not the registry |
| 4. God files/functions (>500 LOC) | 0 | errors/error-registry.ts (824, but it is the registry); utils/logger/logger.ts (619) |
| 5. Duplication | ~1 | 4 bundle-manifest-* variants (kv/redis/init) — likely intentional backend split |
| 6. Dead/stale code, legacy shims | ~3 | utils/logger/logger.ts deprecated fields; observability/tracing/otlp-setup.ts "legacy" delegation; utils/constants/server.ts:46 retained dead endpoint |
| 7. Missing tests on complex code | ~3 | security/sandbox/worker-script.ts (497 LOC, no test); observability/tracing/api-shim.ts (409, no test); repositories/cache/cache-repository.ts (314, no adjacent test) |
| 8. Coupling / deep imports | 1 | utils/cache/stores/memory/lru-cache-adapter.ts:1 (../../../) |
| 9. Deprecated/legacy shims | ~3 | logger deprecated keys; otlp-setup |
| 10. Perf/safety smells | ~3 | unbounded MemoryTokenStore tokens map; no log redaction; shared default token-store singleton |
Per-subdir LOC / file count (non-test *.ts):
Verified: counts re-run with
find ... ! -name '*.test.ts' ! -name '*.bench.ts' ... -exec cat {} + | wc -l. All rows match except security, corrected from 62/6,254 to 61/5,947 — the original figure countedworker-isolation.bench.tsas a production file. All other rows match exactly.
| Dir | Files | LOC |
|---|---|---|
| utils | 74 | 6,863 |
| security | 61 | 5,947 |
| observability | 44 | 5,168 |
| errors | 32 | 3,665 |
| oauth | 15 | 1,910 |
| types | 10 | 1,075 |
| repositories | 10 | 956 |
| channels | 2 | 955 |
| issues | 5 | 917 |
| data | 10 | 813 |
| schemas | 7 | 415 |
| client | 3 | 138 |
Findings (ranked)
🔴 Sandbox worker script (security-critical) has no unit test
- Where:
src/security/sandbox/worker-script.ts(497 LOC; no adjacentworker-script.test.ts) - Issue: This is the in-Worker execution core that loads project modules (
loadModulevia dynamicimport()), applies project env (Deno.env.set), runs agent discovery, and serializes errors back across the worker boundary. It is the trust boundary for sandboxed project code, yet has no direct test. There is aworker-isolation.bench.tsbut that is a benchmark, not correctness coverage. - Impact: Regressions in isolation, env-overlay, or error serialization could silently weaken the sandbox without any failing test.
- Fix: Add a focused test for
serializeError,loadModulecaching,applyProjectEnv, and request dispatch (handleAppRoute, unknown-type rejection at line 480). - Verdict: ✅ Verified — 497 LOC confirmed. No test imports the security sandbox
worker-script.ts; the onlyworker-script.test.tsis the unrelatedagent/react/.../browser-inferenceone. File is loaded as a Worker entry viaproject-worker.ts:350and exports nothing, so it cannot be unit-imported as written; sibling sandbox tests reference none of its internal functions.worker-isolation.bench.tsexists but is a benchmark.
🔴 Ad-hoc Error subclasses bypass the central error registry
- Where:
src/utils/semaphore.ts:9(SemaphoreTimeoutError),src/utils/circuit-breaker.ts:39(CircuitBreakerOpen), andsrc/security/path-validation/types.ts(PathValidationErrorcode enum + bespoke result shape) - Issue: The project standardizes on a registry (
errors/error-registry.ts, 80defineErrorentries,VeryfrontError). These three modules instead throw/return hand-rolledErrorsubclasses with no slug, category, status, or RFC-9457type. - Impact: These errors don't surface consistently through
errors/logging.tsor HTTP error mapping; harder to triage/aggregate in observability; inconsistent with the documented error system. - Fix: Register
SEMAPHORE_TIMEOUTandCIRCUIT_BREAKER_OPENslugs and either subclass via the registry or attach RFC-9457 fields. (Noteworker-script.ts:101already special-cases readingtype/status/detailoff errors — these bespoke ones won't carry them.) - Verdict: ✅ Verified —
semaphore.ts:9SemaphoreTimeoutError extends Errorandcircuit-breaker.ts:39CircuitBreakerOpen extends Errorconfirmed, neither uses the registry. Minor qualification:path-validation/types.ts:27PathValidationErroris aconststring-code object (an enum,as const), not anErrorsubclass — the report correctly described it as a "code enum + bespoke result shape," so the wording is accurate, but it is a slightly different category from the other two (codes, not a thrown class). Registry has 80defineErrorentries confirmed.
🟡 as any discards type safety on conversation-history normalization
- Where:
src/channels/invoke.ts:270—})) as any;at the end ofnormalizeConversationHistoryForRuntime - Issue: The whole normalized message array return is cast to
any, defeating the carefully typedMessage[]return signature. Any field mismatch (parts, timestamp, metadata) is now invisible. - Impact: Silent shape drift between channel input and runtime
Message. - Fix: Type the mapped object to
Messageexplicitly and drop the cast, or map into a typed accumulator. - Verdict: ✅ Verified —
invoke.ts:270is exactly})) as any;closingnormalizeConversationHistoryForRuntime, which is declared: Message[]. The cast erases shape checking on the mappedid/role/parts/timestamp/metadataobject.
🟡 MemoryTokenStore tokens map is unbounded; default singleton is process-shared
- Where:
src/oauth/token-store/memory.ts—private tokens = new Map(...)(no eviction) and the exportedmemoryTokenStoresingleton (projectId = "default") - Issue:
statesget TTL cleanup, buttokensare never evicted (only overwritten/cleared explicitly). The exported singleton shares one store across the whole process under the"default"projectId. - Impact: Slow memory growth in long-running dev servers; multi-tenant scenarios that forget to pass a projectId share one keyspace. The class docstring itself warns "Suitable for development and tests" — risk is using the default in prod.
- Fix: Document/enforce that production must inject a persistent, scoped
TokenStore; consider a max-entries cap or per-user TTL on tokens. - Verdict: ✅ Verified —
memory.ts:14private tokens = new Map(...)is mutated only bysetTokens(line 31),deleteTokens(line 36), andclear(line 87); no eviction/TTL.states(line 15) callscleanupExpiredStates()on everysetStateand prunes on read (lines 42, 51-53, 60-64). Exported singletonmemoryTokenStoreconfirmed (line 92). The class docstring's "development and tests" caveat is accurate.
🟡 No secret/PII redaction in the logger
- Where:
src/utils/logger/logger.ts,src/observability/log-buffer.ts(noredact/scrub/sanitizefound anywhere inobservabilityorutils/logger) - Issue: Structured logging accepts arbitrary
args/context objects and emits them (incl.console.error(JSON.stringify(entry))inerrors/logging.ts:73). No allow/deny-list scrubbing ofpassword/token/authorization/secretkeys. - Impact: A caller that logs a tokens object or request body would leak secrets to log sinks. Today no in-scope call site does this (audited — only a benign CORS warning matched), but there is no guardrail preventing it.
- Fix: Add a redaction pass in the logger core that masks known-sensitive keys before serialization.
- Verdict: ✅ Verified —
rg -ni "redact|scrub|sanitize|mask|REDACTED"oversrc/utils/loggerandsrc/observabilityreturns only comment-banner asterisks, no actual scrubbing logic.errors/logging.ts:73emitsconsole.error(JSON.stringify(entry))in production with no key filtering. The "no in-scope call site leaks today" claim is plausible but the absent guardrail is real.
🟡 @ts-ignore cluster on Deno permission APIs
- Where:
src/security/sandbox/permission-system.ts:51,58,62,73(4×) andsrc/security/sandbox/project-worker.ts:106 - Issue: Deno permission/Worker-option APIs are accessed through
@ts-ignore. Security-relevant code (permission queries) suppresses the type checker entirely rather than declaring a minimal typed interface. - Impact: A typo in a permission name or option would not be caught; the suppression hides the whole expression, not just the unknown member.
- Fix: Declare a narrow
interfacefor the Deno permissions surface used and cast once, instead of per-line@ts-ignore. - Verdict: ✅ Verified —
permission-system.tshas@ts-ignore - Deno permissions APIat lines 51, 58, 62, 73 (4× exactly).project-worker.ts:106@ts-ignore - Deno Worker accepts extended optionsconfirmed above thenew Worker(...)call.
🟡 Deprecated logger fields kept indefinitely for "Grafana dashboard transition"
- Where:
src/utils/logger/logger.ts:42-74(request_id/trace_id/span_id/project_slug/user_id/conversation_id/duration_mslegacy aliases, all@deprecated) - Issue: Seven
@deprecatedduplicate fields kept "until Grafana dashboard migration is complete" — an open-ended condition with no tracking issue or date. - Impact: Every log entry potentially carries duplicate keys; consumers can't tell which is authoritative; the migration may never close.
- Fix: File a tracked removal task with the dashboard owner; gate the legacy keys behind a flag and set a removal milestone.
- Verdict: ✅ Verified — seven
@deprecated ... Grafana dashboard transitionaliases confirmed:requestId,traceId,spanId,projectSlug,userId,conversationId,durationMs, each shadowing a snake_case standard field. No tracking issue/date in the comments.
🟡 error-registry.ts is 824 LOC — concentrated but monolithic
- Where:
src/errors/error-registry.ts(824 LOC, 80defineErrorentries) - Issue: All error definitions across all categories (CONFIG/BUILD/SECURITY/...) live in one file. No duplicate slugs (verified), so correctness is fine — but it's a single edit hotspot.
- Impact: Merge contention; hard to see category boundaries.
- Fix: Optional — split into
error-registry/<category>.tsre-exported from a barrel. Low priority since slugs are unique and helpers enforce structure. - Verdict: ✅ Verified —
wc -l= 824,rg -c defineError= 80. Both figures exact.
🟢 as unknown as Record<string,unknown> for cross-boundary error / config reading
- Where:
src/security/sandbox/worker-script.ts:101,382,src/channels/control-plane.ts:334 - Issue: Casting
error/agent.config/React server module toRecord<string,unknown>then guarding each field withtypeof. The casts are defensible (untyped boundary), and each is followed by runtime type checks — but they are still escape hatches. - Impact: Low; runtime checks mitigate.
- Fix: Define explicit boundary interfaces where stable (e.g.
SerializedErroralready exists; reuse for the read side). - Verdict: ✅ Verified —
worker-script.ts:101const e = error as unknown as Record<string, unknown>and:382_ReactDOMServer as unknown as Record<string, unknown>(the "React server module");control-plane.ts:334agent.config as unknown as Record<string, unknown>. All three confirmed; correctly characterized as defensible boundary casts.
🟢 Sync filesystem calls in cache-dir setup
- Where:
src/utils/cache-dir.ts:72,87,88(lstatSync,mkdirSync,symlinkSync) - Issue: Synchronous I/O. Acceptable here because it runs once at startup for cache-dir bootstrapping, not on a request path.
- Impact: Negligible (startup only).
- Fix: None required; note if ever called per-request.
- Verdict:
⚠️ Accurate but qualified —lstatSync(line 72),mkdirSync(line 87),symlinkSync(line 88) confirmed. They are dynamically imported fromnode:fsat line 66 inside a startup cache-dir bootstrap path; the "startup only" justification holds.
🟢 bundle-manifest split into four backend variants
- Where:
src/utils/bundle-manifest.ts+-kv.ts+-redis.ts+-init.ts - Issue: Looks like duplication but is a deliberate backend-strategy split (KV vs Redis vs init), each with its own test. Verified small (55-164 LOC each).
- Impact: None; flagged only to confirm it is not accidental copy-paste.
- Fix: None.
- Verdict: ✅ Verified — four variants exist (
bundle-manifest.ts164,-kv.ts58,-redis.ts55,-init.ts119 LOC), each with an adjacent.test.ts. Deliberate backend split, not copy-paste.
🟢 One deep relative import
- Where:
src/utils/cache/stores/memory/lru-cache-adapter.ts:1—import ... from "../../../logger/logger.ts" - Issue: The rest of the cluster uses
#veryfront/...hash imports; this one reaches up three levels. - Impact: Cosmetic / refactor-fragile.
- Fix: Switch to
#veryfront/utils/logger/logger.ts. - Verdict: ✅ Verified — line 1 is
import { serverLogger } from "../../../logger/logger.ts";exactly.
🟢 Retained dead endpoint constant
- Where:
src/utils/constants/server.ts:46— "Legacy endpoint retained for backward compatibility (no active handler)." - Issue: A constant kept with no active handler.
- Impact: Negligible; clearly documented.
- Fix: Remove once confirmed no external caller depends on it.
- Verdict: ✅ Verified —
server.ts:46comment "Legacy endpoint retained for backward compatibility (no active handler)." sits aboveDEV_LOADERconstant. Confirmed.
🟢 redis-client.ts uses Record<string, any> for npm client options
- Where:
src/utils/redis-client.ts:71,77,96 - Issue:
anyused for@redis/clientcreateClientoptions — but each is behind a// deno-lint-ignore no-explicit-anyand bounded to the external module's untyped surface. - Impact: Low; isolated to one adapter.
- Fix: Optional — pull a minimal typed options interface.
- Verdict: ✅ Verified —
redis-client.tslines 71, 77, 96 useRecord<string, any>, each preceded by// deno-lint-ignore no-explicit-any, bounded to the@redis/clientuntyped surface.
Minor / nits
schemas/lazy.ts:11_output: undefined as unknown as T— standard phantom-type pattern, not real debt.types/entities/getEntityInfo.ts:334— theanyripgrep hit is the English word "any" in a comment (false positive).client/spa/component-loader.ts:51window as unknown as Record<string,unknown>— narrow, intentional global access.security/sandbox/worker-pool.ts:360(globalThis as any).__veryfront_heap_stats— internal debug hook.observability/tracing/otlp-setup.ts:73"Legacy initialise / shutdown" — comment marks delegation to newer bootstrap; verify the legacy path is still wired or remove.
Verdict on nits: ✅ All verified. lazy.ts:11 is exactly _output: undefined as unknown as T, (phantom type). getEntityInfo.ts:334 is the comment // layouts/ dir: any valid entity is a layout — confirmed English-word false positive, not a type cast. component-loader.ts:51 const global = window as unknown as Record<string, unknown>; confirmed. worker-pool.ts:360 (globalThis as any).__veryfront_heap_stats confirmed (internal debug hook). otlp-setup.ts:73 comment "Legacy initialise / shutdown (now delegated to bootstrap.ts + ext)" confirmed, followed by let initialized and an Initialize OTLP tracing export function still present.
Verdict on summary table row 7 (missing tests): ✅ Verified — api-shim.ts = 409 LOC with no api-shim.test.ts in observability/tracing; cache-repository.ts = 314 LOC, its dir contains only cache-repository.ts + index.ts (no adjacent test). Both confirmed.
Refuted / false positives
None. Every substantive finding and nit was independently confirmed against the source. The only corrections were quantitative: weak-typing count (~19 → ~16 by rg over the cluster, excluding tests) and the security subdir row (62/6,254 → 61/5,947, where the original counted worker-isolation.bench.ts as production). Both are imprecision, not false positives — the cited worst-offenders are all real.
Quick wins
- Drop the
as anyinchannels/invoke.ts:270by typing the mappedMessage. - Fix the one deep relative import in
lru-cache-adapter.ts:1to a hash import. - Replace the four
@ts-ignorelines inpermission-system.tswith a single narrow Deno-permissions interface. - File a tracked removal issue for the seven deprecated logger fields (with the Grafana owner) so the "transition" actually closes.
- Add a small test for
serializeError/loadModuleinworker-script.ts.
Larger refactors
- Unify error construction: register
SemaphoreTimeoutError,CircuitBreakerOpen, andPathValidationErrorcodes into the central registry (or attach RFC-9457 fields) so all errors flow through one system. - Logger redaction layer: add deny-list scrubbing of secret-like keys in the logger core before serialization — defense-in-depth against future leak-by-logging.
- OAuth token-store hardening: bound the
MemoryTokenStoretokens map and make production require an explicitly-scoped persistent store rather than the shared"default"singleton. - Sandbox test coverage: build a correctness test suite around
worker-script.ts/worker-pool.ts/permission-system.tsgiven they form the project-code trust boundary.
Activity
review: harden redaction (class instances, fail-closed, log-buffer sink)
security: redact object args captured via interceptConsole (#1989)
review: trim cache-repository tests to the real gaps
Merge pull request #2048 from veryfront/chore/1989-types-final
Merge pull request #2051 from veryfront/refactor/1989-error-registry-…
fix(oauth): export MemoryTokenStoreOptions from public surface
security(logger): close redaction gaps in error/url paths (#1989)
Merge pull request #2049 from veryfront/test/1989-missing-tests
Merge pull request #2046 from veryfront/chore/1989-token-store-hardening
Merge pull request #2045 from veryfront/chore/1989-logger-redaction
Merge pull request #2064 from veryfront/fix/1989-multitier-l1-v2
kojiwakayama commentedon Jun 8, 2026
Current assessment: useful tracking issue, but several of the listed quick wins are stale on main.
Resolved on current source:
Still worth addressing, but as focused follow-ups rather than a broad PR: direct tests around security/sandbox/worker-script.ts, logger redaction for secret-like keys, and replacing security permission @ts-ignore usage with a narrow typed Deno permissions surface. I did not open a PR here because the easy stale items are already fixed and the remaining items need dedicated tests/design rather than opportunistic edits.
kojiwakayama commentedon Jun 9, 2026
Close-out task plan after current-main review:
Resolved on current source and should not be reworked:
src/utils/logger/redact.tsand is applied by the logger, error logging, and log buffer paths. Tests cover top-level, nested, cyclic,toJSON, URL credential, and serialized-error redaction.Remaining close-out tasks:
error-registry.tsshould split by category. Acceptance: either split into category modules without slug drift or record a decision to keep the monolith because uniqueness and searchability matter more.The remaining work is defense-in-depth and test hardening, not missing basic secret redaction.
kojiwakayama commentedon Jun 9, 2026
Opened PR #2171 for the sandbox trust-boundary task: it fixes projectEnv leakage across reused workers, adds real ProjectWorker coverage for unknown request serialization and env overlay isolation, and bumps release metadata to 0.1.698.
kojiwakayama commentedon Jun 9, 2026
PR #2171 is merged. It completes the sandbox trust-boundary slice from this issue by fixing projectEnv leakage across reused workers, removing request-controlled dynamic method dispatch that CodeQL flagged, and adding real ProjectWorker coverage for unknown request serialization plus env overlay isolation.\n\nRemaining items on this parent are still separate: production token-store enforcement and the error-registry category decision.
kojiwakayama commentedon Jun 9, 2026
Closing this parent because the two remaining close-out items are now satisfied on main and verified.\n\nEvidence:\n- Sandbox trust-boundary slice was completed by merged PR #2171.\n- Production token-store enforcement is present: the generated starter \ throws in \ instead of falling back to memory, even when durable env vars are present but not explicitly wired. The runtime \ remains dev/test scoped, LRU-bounded, and emits a production-use warning.\n- Error registry category decision is implemented: \ assembles per-category modules from , with tests covering slug uniqueness and category coverage.\n\nVerification run on main:\n\\n\nResult: 12 test modules/steps passed across the targeted Deno test command, and the targeted Deno check completed successfully.