Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 24 additions & 41 deletions .github/workflows/contract-drift.yml
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
name: Contract Drift
# Fails the PR if widget's oRPC contract mirrors (src/sdk/routes.ts,
# src/sdk/schema.ts) have drifted STRUCTURALLY from the source of truth in
# Marketrix-ai/api (routes.ts, schema.ts). "Structural" = presence of routes,
# exported schema identifiers, and discriminated-union event tags — comments,
# import paths, and whitespace are ignored.
#
# Widget is a strict SUBSET of the api contract: it intentionally omits the
# dashboard-only `simulationIssue*` routes and `SimulationIssue*` schemas. The
# drift script is run with --allow-subset, which tolerates those (and only
# those) being absent. Anything else missing — or anything EXTRA in the widget
# mirror — is still drift. See scripts/check-contract-drift.mjs for the known
# field-level gap.
# Fails the PR if widget's scoped SDK mirror (src/sdk/contract.ts + contracts/*)
# has drifted from the source of truth in Marketrix-ai/api (sdk/widget.ts +
# contracts/*). Uses the same sync-consumers.mjs generator in --check mode so
# the gate and the write path are provably equivalent.
#
# TOKEN REQUIREMENT
# -----------------
# This workflow reads two files from the PRIVATE Marketrix-ai/api repo, so it
# needs a token with read access. Create a repo or org secret:
# This workflow sparse-checkouts the relevant paths from the PRIVATE
# Marketrix-ai/api repo, so it needs a token with read access. Create a
# repo or org secret:
#
# CONTRACTS_READ_TOKEN = fine-grained PAT with "Contents: read" on
# Marketrix-ai/api (read-only, no write scopes).
Expand All @@ -25,9 +18,7 @@ name: Contract Drift
on:
pull_request:
paths:
- src/sdk/routes.ts
- src/sdk/schema.ts
- scripts/check-contract-drift.mjs
- src/sdk/**
- .github/workflows/contract-drift.yml
workflow_dispatch:
schedule:
Expand Down Expand Up @@ -57,34 +48,26 @@ jobs:
exit 1
fi

- name: Fetch api contract source (routes.ts, schema.ts) from Marketrix-ai/api@dev
- name: Sparse-checkout api contracts + sync generator from Marketrix-ai/api@dev
env:
GH_TOKEN: ${{ secrets.CONTRACTS_READ_TOKEN }}
run: |
set -euo pipefail
mkdir -p .api-src
for f in routes.ts schema.ts; do
echo "Fetching api/${f}@dev ..."
gh api "repos/Marketrix-ai/api/contents/${f}?ref=dev" \
-H 'Accept: application/vnd.github.raw' > ".api-src/${f}"
if [ ! -s ".api-src/${f}" ]; then
echo "::error::Fetched empty .api-src/${f} — check the token has Contents:read on Marketrix-ai/api and that the file exists on dev."
exit 1
fi
done

- name: Check routes drift (subset mode)
run: |
node scripts/check-contract-drift.mjs \
--api .api-src/routes.ts \
--mirror src/sdk/routes.ts \
--kind routes \
--allow-subset
git -C .api-src init
git -C .api-src remote add origin "https://x-access-token:${GH_TOKEN}@github.com/Marketrix-ai/api.git"
git -C .api-src config core.sparseCheckout true
printf 'contracts/\nscripts/sync-consumers.mjs\nsdk/\n' > .api-src/.git/info/sparse-checkout
git -C .api-src fetch --depth=1 origin dev
git -C .api-src checkout FETCH_HEAD
if [ ! -f ".api-src/scripts/sync-consumers.mjs" ]; then
echo "::error::sync-consumers.mjs not found in .api-src — check token and branch."
exit 1
fi

- name: Check schema drift (subset mode)
- name: Check contract drift (sync-consumers --check)
run: |
node scripts/check-contract-drift.mjs \
--api .api-src/schema.ts \
--mirror src/sdk/schema.ts \
--kind schema \
--allow-subset
node .api-src/scripts/sync-consumers.mjs widget \
--check \
--api-root .api-src \
--dest src/sdk
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "@marketrix.ai/widget",
"version": "3.8.3",
"type": "module",
"sideEffects": false,
"main": "./dist/widget.mjs",
"module": "./dist/widget.mjs",
"types": "./dist/src/index.d.ts",
Expand Down
204 changes: 0 additions & 204 deletions scripts/check-contract-drift.mjs

This file was deleted.

2 changes: 1 addition & 1 deletion src/context/TaskContext.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';

import type { WidgetEvent } from '../sdk/schema';
import type { WidgetEvent } from '../sdk';
import { StreamClient, type StreamStatus } from '../services/StreamClient';
import { toolExecutionService } from '../services/ToolService';
import type { ChatMessage, InstructionType, TaskProgress } from '../types';
Expand Down
2 changes: 1 addition & 1 deletion src/context/__tests__/sseReducer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/
import { describe, expect, it } from 'vitest';

import type { WidgetEvent } from '@/sdk/schema';
import type { WidgetEvent } from '@/sdk';
import type { ChatMessage } from '@/types';
import { hasThinkingMarker } from '@/utils/chat';

Expand Down
2 changes: 1 addition & 1 deletion src/context/sseReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* Keeping this pure makes the previously-untestable ~180-line SSE handler unit
* testable and removes the nested setState-within-setState that hid bugs.
*/
import type { WidgetEvent } from '../sdk/schema';
import type { WidgetEvent } from '../sdk';
import type { ChatMessage, InstructionType } from '../types';
import {
addProgressLine,
Expand Down
16 changes: 16 additions & 0 deletions src/sdk/contract.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { activityLogCreate } from './contracts/activityLog';
import { agentGet } from './contracts/agent';
import { applicationGet } from './contracts/application';
import { chatCreate } from './contracts/chat';
import { widgetGetDefaults, widgetMessage, widgetSearch, widgetStream } from './contracts/widget';

export const widgetContract = {
activityLogCreate,
agentGet,
applicationGet,
chatCreate,
widgetGetDefaults,
widgetSearch,
widgetMessage,
widgetStream,
};
44 changes: 44 additions & 0 deletions src/sdk/contracts/activityLog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { oc } from '@orpc/contract';
import { z } from 'zod';

import { paginatedListOf, PaginationSchema } from './common';
import { ActionLogCreateSchema, ActionLogEntitySchema, ActionLogTypeSchema } from './entities';

// ---- procedures ----

export const activityLogCreate = oc
.route({
method: 'POST',
tags: ['Activity Log'],
path: '/log',
summary: 'Create new activity log entry',
description: 'Records user or system action for auditing and tracking purposes',
})
.input(ActionLogCreateSchema)
.output(ActionLogEntitySchema);

export const activityLogSearch = oc
.route({
method: 'GET',
tags: ['Activity Log'],
path: '/log',
summary: 'Search and filter activity logs',
description: 'Returns list of activity logs matching search parameters (workspace, type)',
})
.input(
z
.object({
workspace_id: z.coerce.number().optional(),
type: ActionLogTypeSchema.optional(),
application_id: z.coerce.number().optional(),
})
.extend(PaginationSchema.shape),
)
.output(paginatedListOf(ActionLogEntitySchema));

// ---- domain aggregate ----

export const activityLogRoutes = {
activityLogCreate,
activityLogSearch,
};
Loading
Loading