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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,5 @@ data/reports/*.docx

# MindSpider 入库运行时产物(每次跑 CLI 可重建)
data/storage/news/**

backend/.tmp-tests/
24 changes: 22 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ pnpm dev

### 受控 WEBSEC-101 课程场景数据

`websec-101-showcase-v5` 是仅供本地开发、比赛演示和明确授权测试数据库使用的显式 seed profile。它写入真实的课程、教学班、选课、作答、学习路径、可恢复辅导记录、资源、作业、AgentRun/Evidence 和治理关系,现有 API、权限和审计会照常消费这些实体。
`websec-101-showcase-v7` 是仅供本地开发、比赛演示和明确授权测试数据库使用的显式 seed profile。它写入真实的课程、教学班、选课、作答、学习路径、可恢复辅导记录、资源、作业、AgentRun/Evidence 和治理关系,现有 API、权限和审计会照常消费这些实体。

该 profile 包含 32 个虚构课程花名学生,以及复用本地登录账号的 1 名 demo 课程学习者。固定课程资料属于 `curated-demo`,外部链接保持 `external-preview` 来源边界;它们不是实时模型输出、平台自有视频或真实在校学生数据。seed 不会在应用启动时执行,并且当 `APP_ENV` 为 `production`、`prod` 或 `release` 时会拒绝运行。

Expand All @@ -416,7 +416,27 @@ SECUREHUB_ALLOW_SHOWCASE_SEED=1 uv run python -m app.db.seeds.seed_showcase_cour
SECUREHUB_ALLOW_SHOWCASE_SEED=1 uv run python -m app.db.seeds.seed_showcase_course verify
```

`verify` 应输出 `valid: True`,并报告 manifest、质量门、对象数和关系链检查结果。需要清理时,仅能在同一类受控环境中显式执行 profile-scoped reset;它只删除该 profile 所有的稳定 ID,不会替代备份、迁移验证或浏览器验收:
#### 从已有 v5/v6 展示数据升级到 v7

在同一类授权数据库中,普通升级只需重新执行上面的 `seed`、再执行 `verify`;**不要为升级执行 `reset`**。`seed` 会按该 profile 的稳定标识幂等协调受控对象,不以清理无关工作区数据的方式升级。Windows PowerShell 与 macOS/Linux 均从 `backend/` 目录按下列顺序运行:

```powershell
$env:SECUREHUB_ALLOW_SHOWCASE_SEED = '1'
uv run python -m app.db.seeds.seed_showcase_course seed
uv run python -m app.db.seeds.seed_showcase_course verify
```

```bash
export SECUREHUB_ALLOW_SHOWCASE_SEED=1
uv run python -m app.db.seeds.seed_showcase_course seed
uv run python -m app.db.seeds.seed_showcase_course verify
```

v7 会为 `demo-student@securehub.local` 补齐持久化的 36 道冻结综合评估题、开放 submission、个人资源/Evidence 和 `assessment_demo_draft`。课程页的“一键填充 36 道题”只会预填可编辑草稿;仍须由学习者显式提交,且评估反馈与 `outcome_evaluator` 能力画像回流继续受真实 API、Evidence、Provider 和 QualityCheck 约束,不会用默认成绩或固定雷达图替代。

在已实际执行的 Windows 本地受控库中,`verify` 返回 `manifest: websec-101-showcase-v7`、`valid: True`、`demo_assessment_questions: 36` 与 `demo_assessment_drafts: 1`。这只证明该受控 seed 数据已写入并通过其关系检查;不代表 Provider、浏览器 E2E 或生产环境已经验收。其他平台必须在自身授权环境重新执行 `seed` 与 `verify` 后才可记录为已验证。

需要清理时,仅能在同一类受控环境中显式执行 profile-scoped reset;它只删除该 profile 所有的稳定 ID,不会替代备份、迁移验证或浏览器验收:

```powershell
cd backend
Expand Down
38 changes: 37 additions & 1 deletion backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ APIs and permissions consume them normally. It is not a startup seed, is
disabled when `APP_ENV` is `production`, `prod`, or `release`, and must never
be run against production by default.

The `websec-101-showcase-v5` profile includes 32 fictional course aliases and
The `websec-101-showcase-v7` profile includes 32 fictional course aliases and
one existing demo-course learner. Its fixed teaching material and external
references remain labelled as `curated-demo` / `external-preview`; they are
not a claim of live model generation, platform-owned video content, or real
Expand Down Expand Up @@ -119,6 +119,42 @@ SECUREHUB_ALLOW_SHOWCASE_SEED=1 uv run python -m app.db.seeds.seed_showcase_cour
quality-gated items, relationship chains, and coverage states; it does not
substitute for PostgreSQL migration or browser end-to-end checks.

### Upgrade an existing v5/v6 showcase database to v7

For an ordinary upgrade of an already authorised local, competition-demo, or
test PostgreSQL database, run `seed` and then `verify` again from this
`backend/` directory. **Do not run `reset` for this upgrade.** The seed
reconciles its controlled profile by stable IDs and does not use a reset to
remove unrelated workspace data.

```powershell
$env:SECUREHUB_ALLOW_SHOWCASE_SEED='1'
uv run python -m app.db.seeds.seed_showcase_course seed
uv run python -m app.db.seeds.seed_showcase_course verify
```

```bash
export SECUREHUB_ALLOW_SHOWCASE_SEED=1
uv run python -m app.db.seeds.seed_showcase_course seed
uv run python -m app.db.seeds.seed_showcase_course verify
```

v7 adds persistent, frozen 36-question comprehensive-assessment items, an
open submission, personal resources/Evidence, and an `assessment_demo_draft`
for `demo-student@securehub.local`. The course-page “fill all 36 questions”
control only pre-fills that editable draft. A learner must still submit it
explicitly; assessment feedback and the `outcome_evaluator` capability update
remain subject to the real API, Evidence, Provider, and QualityCheck path.
No default score or fixed radar chart is used as a substitute.

The executed Windows local controlled-database run returned
`manifest: websec-101-showcase-v7`, `valid: True`,
`demo_assessment_questions: 36`, and `demo_assessment_drafts: 1`. This is
seed/relationship evidence only; it is not Provider, browser E2E, or
production acceptance. On macOS/Linux, execute the same `seed` and `verify`
commands in the authorised target environment before recording that platform
as validated.

### Profile-scoped reset

Reset is deliberately separate and must never be run in production. After
Expand Down
7 changes: 7 additions & 0 deletions backend/app/agents/outcome_evaluator/skills/run_assessment.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ class RunAssessmentOutput(SkillOutput):
capability_delta keyed by capability dimension, any weak knowledge-point IDs,
and a next_recommendation. Do not put these fields inside an extra assessment
object. The evidence linkage is owned by the server.
When the input contains a ``server_verified_published_submission``
``assessment_summary``, its ``scoring.objective_floor_score`` was computed by
the server from the frozen assessment version and the durable learner
submission. It is a lower bound for the complete paper: never return a score
below it or describe the submission as incomplete solely because an answer was
bounded for prompt transport. Use linked Evidence only to assess the remaining
subjective content; do not invent subjective credit or a weakness.
When evidence is insufficient for a claimed weakness, keep the feedback to
what the submitted assessment actually supports instead of inventing a claim.

Expand Down
5 changes: 5 additions & 0 deletions backend/app/api/v1/endpoints/assessment.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ async def assessment_run(
input_payload={
"answers": payload.answers,
"quiz_artifact_id": payload.quiz_artifact_id,
"context": (
{"assessment_assignment_id": str(payload.assessment_assignment_id)}
if payload.assessment_assignment_id is not None
else {}
),
"domain": product.domain,
},
mode=payload.mode,
Expand Down
26 changes: 25 additions & 1 deletion backend/app/api/v1/endpoints/teaching.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
GradeDecisionDTO,
GradeOverrideRequest,
ObjectiveScoreDTO,
QuizCandidateAvailabilityDTO,
QuizCandidateFilterRequest,
QuizCandidatePrepareRequest,
QuizCandidatePreviewDTO,
QuizReviewDecisionDTO,
Expand Down Expand Up @@ -72,9 +74,12 @@


def _raise_domain_error(exc: TeacherProductionError) -> None:
detail: dict[str, object] = {"code": exc.code, "message": exc.message}
if exc.detail is not None:
detail.update(exc.detail)
raise HTTPException(
status_code=exc.status_code,
detail={"code": exc.code, "message": exc.message},
detail=detail,
) from exc


Expand Down Expand Up @@ -323,6 +328,25 @@ async def review_course_quiz_item(
_raise_domain_error(exc)


@router.post(
"/teacher/production/courses/{course_id}/quiz-candidates/preflight",
response_model=QuizCandidateAvailabilityDTO,
)
async def preflight_course_quiz_candidates(
course_id: UUID,
payload: QuizCandidateFilterRequest,
session: SessionDep,
user: RequiredCurrentUserDep,
) -> QuizCandidateAvailabilityDTO:
try:
return await TeacherProductionService(session).preflight_quiz_candidates(
actor=user, course_id=course_id, payload=payload
)
except TeacherProductionError as exc:
await session.rollback()
_raise_domain_error(exc)


@router.post(
"/teacher/production/courses/{course_id}/quiz-candidates/prepare",
response_model=QuizCandidatePreviewDTO,
Expand Down
Loading
Loading