Skip to content

[PROD RELEASE] - Bug Fixes & Updates - #25

Merged
kkartunov merged 24 commits into
masterfrom
develop
Aug 25, 2026
Merged

[PROD RELEASE] - Bug Fixes & Updates#25
kkartunov merged 24 commits into
masterfrom
develop

Conversation

@kkartunov

@kkartunov kkartunov commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

kkartunov and others added 24 commits August 19, 2026 10:39
Fix 3 pre-existing TypeScript errors in challenge-context-workflow.ts:313
and skill-extraction-workflow.ts:102,146 by adding the required 'observe'
property (noopObserve from @mastra/core/tools) to tool.execute?.() calls.

ToolExecutionContext requires observe as a non-optional field.

Install new dependencies for RAG pipeline:
- Production: @mastra/rag@^2.5.0 (2.6.0), turndown, js-tiktoken, csv-parse
- Dev: @types/turndown, tsx

Verified @mastra/rag 2.6.0: MDocument.chunk() returns chunks with 'text'
field (not 'content'), confirming architecture assumptions.

Green baseline confirmed: tsc --noEmit, pnpm lint, pnpm test, pnpm run build
all pass with exit code 0.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Create src/config/rag.config.ts with lazy getRagConfig() that never throws
at module load (D5). Provider/model dimension map: TC-Ollama/nomic-embed-text
→ 768/2048, AWSBedrock/amazon.titan-embed-text-v2:0 → 1024/8192. All RAG env
vars overridable with defaults. VECTOR_INDEX_NAME validated as SQL identifier.
type/track as free-form strings (D12). Reuses MASTRA_DB_CONNECTION and
MASTRA_DB_SCHEMA.

Create src/utils/providers/embedding-factory.ts with createEmbeddingModel()
switch mirroring createModel: TC-Ollama → ollama.embedding(), AWSBedrock →
createBedrockProvider().embedding(). Unknown provider throws actionable
error. Logs via tcAILogger. Re-exported from src/utils/index.ts.

33 unit tests covering all config and factory behaviors (VAL-FOUND-021
through VAL-FOUND-033).

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Create src/mastra/vector/challenge-vector-store.ts with:
- getChallengeVectorStore(): lazy PgVector singleton (created on first
  call, not at module load) constructed with id, connectionString, and
  schemaName from MASTRA_DB_CONNECTION/MASTRA_DB_SCHEMA
- ensureChallengeIndex(): idempotently creates HNSW index with cosine
  metric and metadataIndexes for challengeId, projectId, track
- D7 dimension guard: compares describeIndex().dimension against
  configured model dimension, throws actionable error on mismatch
- No disconnect() on request paths (singleton persists for process lifetime)
- 29 unit tests with mocked PgVector covering all 6 validation assertions

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…unit tests

Ports the prototype's pure library code into src/mastra/rag/ (ADR 0001 Phase 3):

- content.ts: normalizeLineEndings, BOM-aware trim, stripFrontmatter,
  htmlToMarkdown (Turndown, atx headings + fenced code), parseSkills, generic
  enrichChunksWithChallengeName, processDescription (normalize -> trim ->
  htmlToMarkdown -> stripFrontmatter, in that order)
- chunking.ts: chunkChallengeDescription as a pure two-pass function
  (markdown header pass with stripHeaders: false, then size-based pass).
  Code blocks and tables stay atomic while under floor(contextWindow * 0.97)
  tokens; oversized ones are force-split at safeCharLimit = contextWindow * 3
  (6144), NOT maxSize (512). Returns { chunks, forceSplits }.
- ingestion-utils.ts: withRetry with LINEAR backoff (delay * attempt), sleep,
  REQUIRED_COLUMNS, validateColumns, validateRecord (id/name/description only),
  generateDeterministicId (SHA-256 rendered UUID-shaped)
- types.ts: ChallengeRecord, ChunkingOptions derived from RagConfig,
  ChallengeChunk, ForceSplitRecord, ChunkMetadata (text field, not content;
  projectId as string; new groups/ingestedAt fields), IngestionReport,
  IngestOptions

89 new unit tests colocated as src/mastra/rag/*.test.ts.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Additively adds projectId: z.number().optional() and groups:
z.array(z.string()).optional() to the fetchChallengeTool output schema
and mapping function. projectId from the Challenge API is Int? (nullable);
null/absent values become undefined via ?? undefined. The
challenge-context-workflow uses z.any() for the challenge object so it
is unaffected. The app-version: 2.0.0 header is preserved unchanged.

15 unit tests verify:
- projectId present when API returns a number
- projectId omitted when API returns null or is absent
- groups present when API returns an array
- groups omitted when API does not include them
- both fields passed through in mapping
- output schema includes and preserves both fields
- existing fields, headers, error handling unchanged

Fulfills VAL-INGEST-053 through VAL-INGEST-058.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…ilters

Wraps the Topcoder v6 Challenges API search endpoint with M2M auth.
Supports filters: projectId/projectIds, status, approvalStatus, types/tracks,
tags, groups, updatedDateStart/End, ids, page, perPage, sortBy, sortOrder.
Always sets isLightweight=false (lightweight omits description), includes
app-version: 2.0.0 header, 15s timeout via AbortSignal.timeout(15_000).
Handles bare-array API response, wraps into { challenges, total, page, perPage }.
Discards privateDescription from return value. Throws on upstream non-2xx.
26 unit tests with mocked HTTP covering all 8 validation assertions.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…tests

Implements src/mastra/workflows/challenge/challenge-ingestion-workflow.ts
(id: challenge-ingestion) registered in src/mastra/index.ts:
- Step resolve-challenge: fetch by challengeId via fetchChallengeTool or
  validate inline record with validateRecord; exactly-one-source enforced
- Step chunk-and-embed: processDescription → chunkChallengeDescription →
  enrichChunksWithChallengeName → embedMany with withRetry (linear backoff);
  only public description embedded, never privateDescription
- Step upsert-vectors: ensureChallengeIndex() → upsert with deleteFilter
  { challengeId } for atomic per-challenge replacement; skipped on dryRun
- Metadata: all 11 fields (challengeId, name, type, track, skills[], groups[],
  projectId string|null, chunkIndex 1-based, totalChunks, text with
  # Challenge: header, ingestedAt ISO-8601)
- Logging via tcAILogger with [challenge-ingestion:<step>] prefixes
- Config lazy via getRagConfig() inside step execute (D5)
- No console interception (D6), no projects-api call (D10)
- 41 unit tests covering happy paths, dryRun, error handling, metadata,
  idempotent re-ingestion, embedding vs database failure, dimension guard

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…-out

Adds src/mastra/workflows/challenge/challenge-bulk-ingestion-workflow.ts
(id: challenge-bulk-ingestion), registered in src/mastra/index.ts:

- collect-challenges paginates searchChallengesTool (1-based page/perPage,
  stops on an empty or short page, bounded by a maxPages guard) and emits one
  task per matched challenge; the v6 API validates status as a scalar enum, so
  the default ACTIVE + COMPLETED filter runs one paginated pass per status with
  results de-duplicated by challenge id.
- ingest-one-challenge runs challenge-ingestion imperatively
  (getWorkflowById -> createRun -> run.start) and converts every failure path
  into a failed item result, so one bad challenge cannot kill the fan-out queue
  or abort the run.
- .foreach fan-out with a concurrency resolver reading the workflow input
  (default 3, clamped to 1..10).
- aggregate-reports reduces the per-challenge results into processed /
  succeeded / failed / skipped / totalChunks / forceSplits while retaining every
  per-challenge entry; zero matches yields zero-valued totals.

Also fixes searchChallengesTool array-filter serialization: the v6 endpoint
rejects both comma-joined and bare single values for types/tracks/tags/groups
with `"criteria.<field>" must be an array` (HTTP 400), so those filters are now
sent as bracketed array params (key[]=value).

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Records the decision, hazards, phased plan, data model, and security
notes for porting tc-challenges-vector-rag's ingestion and retrieval
pipeline into tc-ai-api.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MkcggGkcnz73GXUDR7mgrE
…tic workflow

Phase 5 of ADR 0001. challengeVectorQueryTool composes the $and metadata
filter (skills/type/track/groups/projectId) and is shared by two retrieval
paths so they cannot diverge (D8): challenge-search-agent for synthesised
NL answers, and the challenge-search workflow for raw ranked results with
no LLM call, grouped by chunk/challenge/project. The relevance threshold
is post-filtered in app code to preserve the HNSW ANN fast path, and query
is optional so a filter-only call becomes a metadata-only lookup.

Also adds fetchProjectTool (D10) — optional retrieval-time enrichment that
resolves a projectId from a hit to project detail, never used during
ingestion. Registers challengeSearchAgent and challengeSearchWorkflow in
the Mastra instance.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MkcggGkcnz73GXUDR7mgrE
Phase 6 of ADR 0001 — secondary bulk-ingestion path (D11), for
offline/air-gapped environments and CSV exports that predate the
Challenge Search API.

- ingestion-logger.ts: hierarchical file logger ported from the source
  prototype, minus interceptConsole (D6) — per-run logs/ingestion-<ts>/
  {output.log,error.log,report.json}.
- ingest-challenges.ts: streams CSV rows and invokes the challenge-ingestion
  workflow per record via mastra.getWorkflowById().createRun().start(), so
  CLI and API share one implementation. --clear-all drops the vector index
  via deleteIndex() (the actual @mastra/pg 1.19 API for "delete everything" —
  deleteVectors() explicitly rejects an empty filter and points here).
- sync-challenges.ts: thin wrapper around challenge-bulk-ingestion for
  incremental sync / project-scoped backfill from the CLI.
- package.json ingest/sync scripts, logs/ in .gitignore, a small CSV
  fixture for tests.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MkcggGkcnz73GXUDR7mgrE
Phase 7 of ADR 0001 — configuration surface and documentation, adapted
from the source prototype's README. Adds a Challenges Vector RAG section
(overview, ingestion, retrieval, chunking strategy, metadata schema,
embedding-model/dimension table, database bootstrap) plus new rows under
Environment Variables and API Surface. .env.sh and .env (both gitignored,
untracked) were also given the same commented reference block locally.

Dockerfile/.circleci need no change — the image build already runs lint,
test, and build against this branch, and `pnpm run build` bundles turndown
and @mastra/rag into .mastra/output cleanly with no bundler config change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MkcggGkcnz73GXUDR7mgrE
The Agents and Tools sections only covered skillsMatchingAgent and the
two skills tools, predating challengeParserAgent, jdRewriterAgent,
challengeSearchAgent, and the four Challenge/Project tools. Adds
per-agent/tool detail sections plus summary tables, and corrects two
stale facts: skillsMatchingAgent now defaults to Bedrock (not Ollama
mistral), and its scorers are gated on LOCAL_DEV=true.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MkcggGkcnz73GXUDR7mgrE
dump only resourceId on auth resolve -> dev
fix tests for auth breaking the build -> dev
@kkartunov
kkartunov merged commit d920458 into master Aug 25, 2026
3 checks passed
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