release: freeze multi-scene RC-04 candidate

This commit is contained in:
2026-09-20 06:13:41 +08:00
parent af091cdc4f
commit 2770d73818
500 changed files with 120223 additions and 16171 deletions
@@ -1,6 +1,6 @@
# Contract OpenAPI
This package is the source of truth for the V2 contract platform HTTP contract.
This package publishes the V2 contract platform HTTP contract. Runtime structure is reconciled against an isolated service export; manually maintained explanations and explicit schema refinements are retained.
Primary file:
@@ -13,3 +13,33 @@ Rules:
- Contract Web should consume this contract through generated or typed clients.
- Contract API implementation must match this contract.
- Contract API calls AI Platform only through SDK/OpenAPI clients, not direct provider adapters.
The 2026-09-09 local synchronization contains 282 paths, 328 operations and 450 component schemas: 440 runtime schemas plus 10 `Documented_*` components supporting seven explicit refinements of unconstrained runtime schemas. These refinements are reported separately from exact structural equality and fingerprinted under `x-yuqei-sync`.
The previous `POST /api/v1/auth/login` publication has no matching registered runtime route. Its definition remains in `x-yuqei-legacy-contract`, outside active `paths`; the appendix does not assert that a login endpoint or flow is available. Documentation from replaced fields remains under `x-yuqei-unmatched-documentation`. Historical `nullable` syntax is normalized to OpenAPI 3.1 null unions.
No generated SDK or consumer was replaced in this batch. Runtime operationId changes are recorded for later client migration. See [synchronization rules and check commands](../../../../common/tooling/openapi/sync.README.md) and [before/after evidence](../../../../common/governance/evidence/batch2-openapi-sync-contract.json). Re-export from current source before claiming current-source consistency; a historical runtime JSON is not proof for newer source.
TP06.10 increment: adds the current-tenant `GET /api/v1/admin/product-analytics/scene-quality`, privacy-minimized template recommendation events, and `GET /api/v1/identity/platform-access`. Apply additive migration `20260909_0078` before running the new analytics ORM/event writer. Prompt management in Contract Web checks the live platform permission on every BFF request; this does not extend authentication of direct AI service calls. The new quality view distinguishes candidate origin, withdrawal, reversal, missing information, and failures; its rates are not legal acceptance or pure AI success rates. Historical synchronization counts above predate this increment.
TP06.12 increment: optional `DraftingCreateRequest.journey_id` binds only an existing same-tenant/user/template `draft_guide_started` receipt. Workspace `journey_timing` records server-observed preview, required-input completion and candidate-content delivery checks; existing workspaces stay unmeasured. `GET /api/v1/admin/product-analytics/drafting-stage-timing` is a current-tenant platform-admin aggregate, with all start journeys in the denominator, explicit missing observations and a separate historical failure-journey count. This is not browser-visible time, legal approval, signing readiness or a release acceptance metric. No database migration is required for these additive workspace JSON fields. Run `yuqei-infra/scripts/tp06-12/sync-journey-openapi.py --check` with the repository Python paths to verify these scoped fragments; it does not certify all earlier OpenAPI refinements.
TP06.24.1/TP06.24.2 increments add candidate semantic-review creation and action routes.
The action route retains the runtime `{action}` path parameter; the current
implementation accepts only `refresh`, `cancel`, `retry`, and `repair`. Its request binds
the expected workspace revision and candidate body hash. It does not expose a
separate automatic-repair endpoint. `DraftingCandidateRequest.auto_repair` defaults to
`false` for omitted API input; the ordinary drafting UI presents an initially checked,
editable option and discloses the extra tasks before submission. When enabled, the
coordinator may create at most one repair generation and
records its parent/review linkage and continuation error without overwriting the parent.
The workspace response includes the frozen request,
attempt history, deterministic assessment, and server-validated execution
evidence. A passed model opinion alone is not legal approval or adoption
authorization. Run
`yuqei-infra/scripts/tp06-24-1/sync-semantic-review-contract-openapi.py`
without `--update` to check the isolated memory/no-database runtime fragment.
When a reviewed candidate is adopted, `ContractVersion.drafting_review_reference`
may expose only the review, job and audit IDs plus request/output/assessment and
receipt hashes. Historical versions return `null`; the API does not expose the
rest of internal version metadata through this field.
File diff suppressed because it is too large Load Diff
@@ -1,5 +1,43 @@
# Yuqei Design System
Shared V2 design tokens and workflow primitives.
Shared V2 design tokens, workflow primitives, and V4 shell components.
This package starts intentionally small. It holds product language, layout constants, tokens, and workflow metadata before it grows into a component library.
## V4 Shell Components
组件为纯展示组件(不引入运行时依赖),通过 `transpilePackages` 由消费方编译。样式类名与品牌令牌由消费方全局样式表提供(见 `apps/contract-web/src/app/globals.css` 的 V4 层,令牌定义见 `src/tokens.ts`)。
### SiteHeader
置顶网站导航骨架。品牌与导航链接组件由消费方注入(Next.js 应用传 `next/link`)。
```tsx
import Link from "next/link";
import { SiteHeader } from "@yuqei/design-system";
<SiteHeader
brand={{ href: "/workspace", mark: "钰", title: "钰麒合同", subtitle: "AI 合同平台" }}
nav={[{ href: "/workspace", label: "工作台", active: true }]}
link={Link}
actions={<>{/* 搜索、CTA、用户菜单 */}</>}
onToggleNav={openDrawer}
toggleIcon={<Menu size={20} />}
/>
```
### MemberHero
会员主页问候区:衬线标题 + 数据概览 + 行动按钮。
```tsx
import { MemberHero } from "@yuqei/design-system";
<MemberHero
title="下午好,张法务"
kicker="8 月 28 日 星期五 · 法务"
lead="合同在这里开始……"
actions={<Link className="button button-primary" href="/legal-documents">起草合同</Link>}
stats={[{ key: "pending", value: 3, label: "待我处理" }]}
/>
```
@@ -1,2 +1,5 @@
export * from "./tokens";
export * from "./workflow";
export * from "./site-header";
export * from "./member-hero";
export * from "./risk-card";
@@ -0,0 +1,52 @@
import type { ReactNode } from "react";
/**
* 会员主页 Hero(V4 外壳)。
* 深松墨底 + 衬线问候 + 右侧数据概览;按钮/图标由消费方以节点传入,
* 样式类名(member-hero …)由消费方全局样式表提供。
*/
export type MemberHeroStat = {
key: string;
value: string | number;
label: string;
};
export function MemberHero({
kicker,
title,
lead,
actions,
stats,
titleId = "member-hero-title",
statsLabel = "今日概览"
}: {
kicker?: ReactNode;
title: string;
lead?: ReactNode;
actions?: ReactNode;
stats: MemberHeroStat[];
titleId?: string;
statsLabel?: string;
}) {
return (
<section aria-labelledby={titleId} className="member-hero">
<div className="member-hero-copy">
{kicker ? <p className="member-hero-kicker">{kicker}</p> : null}
<h1 className="member-hero-title" id={titleId}>{title}</h1>
{lead ? <p className="member-hero-lead">{lead}</p> : null}
{actions ? <div className="member-hero-actions">{actions}</div> : null}
</div>
{stats.length > 0 ? (
<ul aria-label={statsLabel} className="member-hero-stats">
{stats.map((stat) => (
<li key={stat.key}>
<strong>{stat.value}</strong>
<span>{stat.label}</span>
</li>
))}
</ul>
) : null}
</section>
);
}
@@ -0,0 +1,79 @@
import type { ReactNode } from "react";
/**
* 风险卡(审查结果与公开样例共用骨架)。
* 「风险 — 依据 — 建议 — 置信度」的统一呈现:
* header = 风险标题 + 标签(严重度/置信度等),facts = 依据与建议条目,
* children 用于审查引用列表等页面专属扩展。样式类名(risk-card …)
* 由消费方全局样式表提供,令牌见 tokens.ts。
*/
export type RiskCardTagTone = "neutral" | "success" | "warning" | "danger";
export type RiskCardTag = {
key?: string;
label: string;
tone?: RiskCardTagTone;
};
export type RiskCardFact = {
key: string;
term: string;
node: ReactNode;
};
const tagToneClass: Record<RiskCardTagTone, string> = {
neutral: "tag",
success: "tag tag-success",
warning: "tag tag-warning",
danger: "tag tag-danger",
};
export function RiskCard({
title,
tags = [],
detail,
facts = [],
footer,
children
}: {
title: ReactNode;
tags?: RiskCardTag[];
detail?: ReactNode;
facts?: RiskCardFact[];
footer?: ReactNode;
children?: ReactNode;
}) {
return (
<article className="risk-card">
<header className="risk-card-header">
<strong className="risk-card-title">{title}</strong>
{tags.length > 0 ? (
<span className="risk-card-tags">
{tags.map((tag, index) => (
<span
className={tagToneClass[tag.tone ?? "neutral"]}
key={tag.key ?? `${tag.label}-${index}`}
>
{tag.label}
</span>
))}
</span>
) : null}
</header>
{detail ? <p className="risk-card-detail">{detail}</p> : null}
{facts.length > 0 ? (
<dl className="risk-card-facts">
{facts.map((fact) => (
<div className="risk-card-fact" key={fact.key}>
<dt>{fact.term}</dt>
<dd>{fact.node}</dd>
</div>
))}
</dl>
) : null}
{footer ? <footer className="risk-card-footer">{footer}</footer> : null}
{children}
</article>
);
}
@@ -0,0 +1,99 @@
import type { ComponentType, ReactNode } from "react";
/**
* 置顶网站导航(V4 外壳)。
* 纯展示组件:导航与品牌链接通过 link 注入(Next.js 应用传 next/link),
* 样式类名(site-header / site-nav / brand-block …)由消费方全局样式表提供,
* 品牌令牌见 tokens.ts。
*/
export type HeaderLinkComponent = ComponentType<{
href: string;
className?: string;
"aria-current"?: "page";
children: ReactNode;
}>;
export type SiteHeaderBrand = {
href: string;
mark: string;
title: string;
subtitle: string;
};
export type SiteHeaderNavItem = {
href: string;
label: string;
active?: boolean;
};
export function SiteHeader({
brand,
nav,
link,
actions,
onToggleNav,
toggleIcon,
toggleLabel = "打开导航",
toggleControls,
toggleExpanded,
navLabel = "主导航"
}: {
brand: SiteHeaderBrand;
nav: SiteHeaderNavItem[];
link: HeaderLinkComponent;
actions?: ReactNode;
onToggleNav?: () => void;
toggleIcon?: ReactNode;
toggleLabel?: string;
toggleControls?: string;
toggleExpanded?: boolean;
navLabel?: string;
}) {
const Link = link;
return (
<header className="site-header">
<div className="site-header-inner">
{onToggleNav ? (
<button
aria-controls={toggleControls}
aria-expanded={toggleExpanded}
aria-label={toggleLabel}
className="site-nav-toggle"
onClick={onToggleNav}
title={toggleLabel}
type="button"
>
{toggleIcon}
</button>
) : null}
<Link className="brand-block site-brand" href={brand.href}>
<span aria-hidden="true" className="brand-mark">{brand.mark}</span>
<span className="brand-copy">
<span className="brand-title">{brand.title}</span>
<span className="brand-subtitle">{brand.subtitle}</span>
</span>
</Link>
{nav.length > 0 ? (
<nav aria-label={navLabel} className="site-nav">
{nav.map((item) => (
<Link
aria-current={item.active ? "page" : undefined}
className={`site-nav-link ${item.active ? "site-nav-link-active" : ""}`}
href={item.href}
key={item.href}
>
{item.label}
</Link>
))}
</nav>
) : null}
{actions ? <div className="site-header-actions">{actions}</div> : null}
</div>
</header>
);
}
@@ -1,32 +1,52 @@
/**
* 钰麒合同 V4 品牌令牌(与 apps/contract-web globals.css 的 V3/V4 层保持一致)。
* 深松墨 + 印章金 + 松石绿 + 暖纸;显示字为中文衬线栈。
*/
export const brandTokens = {
color: {
ink: "#172033",
muted: "#667085",
line: "#d9e0ea",
canvas: "#f6f8fb",
surface: "#ffffff",
primary: "#2457d6",
primarySoft: "#eaf0ff",
success: "#16794c",
successSoft: "#e7f6ef",
warning: "#b35b00",
warningSoft: "#fff3df",
danger: "#bd2d2d",
dangerSoft: "#fdecec",
accent: "#0b7a75",
accentSoft: "#e4f5f3"
ink: "#17201d",
inkSoft: "#35433e",
muted: "#6a726c",
line: "rgba(53, 67, 62, 0.16)",
canvas: "#f6f5ef",
surface: "#fffdf8",
surfaceElevated: "rgba(255, 253, 248, 0.96)",
primary: "#0f6f63",
primaryDark: "#0a524b",
primarySoft: "#e8f3ef",
success: "#1f7459",
successSoft: "#e9f5ee",
warning: "#9a5d18",
warningSoft: "#fff3dd",
danger: "#b3313d",
dangerSoft: "#fff0f1",
accent: "#b86f2d",
accentSoft: "#fff0dc",
pine: "#10251f",
sealGold: "#e9ad55",
sealGoldDeep: "#d4923a",
paper: "#f6f5ef"
},
font: {
body: 'system-ui, -apple-system, "PingFang SC", "Microsoft YaHei", "Segoe UI", sans-serif',
display:
'"Noto Serif SC", "Source Han Serif SC", "Songti SC", "STZhongsong", "SimSun", serif'
},
radius: {
sm: "6px",
md: "8px"
md: "8px",
lg: "10px",
pill: "999px"
},
shadow: {
panel: "0 10px 30px rgba(16, 24, 40, 0.08)"
panel: "0 14px 36px rgba(23, 32, 29, 0.06)",
card: "0 18px 48px rgba(30, 37, 34, 0.09)",
glow: "0 22px 56px rgba(233, 173, 85, 0.32)"
},
layout: {
sidebarWidth: "248px",
topbarHeight: "64px",
contentMaxWidth: "1440px"
headerHeight: "68px",
contentMaxWidth: "1200px",
drawerWidth: "min(86vw, 350px)"
}
} as const;