Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
435d0fb
feat(agent): add boundary and isolation evaluation gates
DavidHLP Oct 1, 2026
adef778
test(agent): align evaluation fixtures with guarded model alias
DavidHLP Oct 1, 2026
f71c7a5
fix: harden account isolation evidence
DavidHLP Oct 1, 2026
0590e87
feat(app): preserve idempotent learning plan storage slice
DavidHLP Oct 2, 2026
f466d9c
fix(dev): pass container MySQL credentials via stdin
DavidHLP Oct 3, 2026
c470276
feat(agent): checkpoint explicit budget period lifecycle
DavidHLP Oct 3, 2026
6ef0028
feat(agent): checkpoint canonical period accounting seam
DavidHLP Oct 3, 2026
5a270e7
fix(agent): preserve SQLite locks in period accounting checkpoint
DavidHLP Oct 3, 2026
9e9d4b5
feat(agent): checkpoint explicit DAV58 period binding
DavidHLP Oct 3, 2026
5dd6e0c
docs: record DAV-58 boundary run failure
DavidHLP Oct 3, 2026
65c86a6
fix(agent): correct boundary predicates and guard live acceptance costs
DavidHLP Oct 3, 2026
dc342eb
fix(agent): validate observed tool traces and resume cumulative accep…
DavidHLP Oct 4, 2026
3a52bd1
fix(agent): require citation-free refusals for unavailable source req…
DavidHLP Oct 4, 2026
4fa42c7
fix(agent): recognize equivalent clarification and scoped failure den…
DavidHLP Oct 4, 2026
82554db
feat(agent): audit a single bounded DAV-58 budget continuation
DavidHLP Oct 4, 2026
e36f76c
fix(agent): audit the reviewed Btrfs budget binding migration
DavidHLP Oct 4, 2026
cf57fcd
test: reproduce inline references in source refusals
DavidHLP Oct 4, 2026
45e1c1a
test: cover www URLs and markdown images in refusals
DavidHLP Oct 4, 2026
bd6b0a9
test: cover edge cases in source refusal references
DavidHLP Oct 4, 2026
9b27ea9
test: isolate quoted reference refusal regression
DavidHLP Oct 4, 2026
e95fd74
fix: reject inline references in source refusals
DavidHLP Oct 4, 2026
4c96464
Merge remote-tracking branch 'origin/main' into agent/first-delivery
DavidHLP Oct 5, 2026
60c07cc
Merge remote-tracking branch 'origin/main' into agent/first-delivery
DavidHLP Oct 5, 2026
2218bc1
Merge remote-tracking branch 'origin/main' into agent/first-delivery
DavidHLP Oct 5, 2026
073e8c8
fix: enforce agent budget and offline contract safety gates
DavidHLP Oct 5, 2026
b9d5801
fix: scope Nacos metadata writes to workload identities
DavidHLP Oct 5, 2026
1733278
feat(agent): add gated durable learning workflows
DavidHLP Oct 6, 2026
3da2f11
fix: migrate backend to patched Spring Boot 4 baseline
DavidHLP Oct 6, 2026
17245bb
fix: pin patched Tomcat 11 and Jackson 3 dependencies
DavidHLP Oct 6, 2026
214c204
fix: exclude the incoherent Boot gRPC module at its source
DavidHLP Oct 7, 2026
a3c6061
fix: bind agent acceptance to executed source and citation-free refusals
DavidHLP Oct 7, 2026
35eab1d
fix: separate U04 SQL reservations from sequential guard envelope
DavidHLP Oct 7, 2026
a0c3f19
fix: preserve citations for source code concept explanations
DavidHLP Oct 7, 2026
b76b73c
fix: scope source concept qualifiers to the requested phrase
DavidHLP Oct 7, 2026
4000b48
test: cover uncited non-refusal source answers
DavidHLP Oct 7, 2026
a1cdf46
fix: require refusal for unavailable source requests
DavidHLP Oct 7, 2026
399f091
test: cover locked audit ledger recovery copies
DavidHLP Oct 7, 2026
450d54c
feat: rehearse locked accounting recovery without paid authorization
DavidHLP Oct 7, 2026
ff2e933
docs: explain audit-only accounting recovery rehearsal
DavidHLP Oct 7, 2026
0e3e5b1
test: cover autonomous confirmation without fabricated human evidence
DavidHLP Oct 7, 2026
31b079f
fix: make acceptance confirmation autonomous without human claims
DavidHLP Oct 7, 2026
0ec4f07
fix: require affirmative autonomous review before confirmation
DavidHLP Oct 7, 2026
1f1bf0c
docs: distinguish autonomous working decisions from user evidence
DavidHLP Oct 7, 2026
4fdf766
fix: accept documented skip-install startup option
DavidHLP Oct 8, 2026
060ca86
fix: address agent workflow review failures
DavidHLP Oct 8, 2026
dd1e156
fix: distinguish verified status facts from source diagnosis
DavidHLP Oct 8, 2026
3a35ff3
test: specify conservative acceptance recovery boundaries
DavidHLP Oct 8, 2026
24df095
test: require explicit inputs for offline recovery planning
DavidHLP Oct 8, 2026
f51f432
Merge remote-tracking branch 'origin/agent/first-delivery' into agent…
DavidHLP Oct 8, 2026
aad5491
feat: validate conditional offline acceptance recovery plans
DavidHLP Oct 8, 2026
f327bdc
docs: distinguish conditional recovery costs from authorization
DavidHLP Oct 8, 2026
d8a9696
fix: reject ambiguous recovery snapshots and unlocked guards
DavidHLP Oct 8, 2026
6410575
fix: bound Nacos young generation within heap
DavidHLP Oct 8, 2026
5f766fa
feat: bind revalidation to cumulative accounting
DavidHLP Oct 8, 2026
01d6c77
fix: interpret retrieved topic questions without over-refusal
DavidHLP Oct 8, 2026
81d6b0a
fix: declare the enforced answer length in evaluation prompts
DavidHLP Oct 8, 2026
f2f62f4
feat: preserve sealed accounting for acceptance budget extension
DavidHLP Oct 8, 2026
8e170ae
test: complete bound evaluation recording fixture
DavidHLP Oct 8, 2026
d33f7f8
fix: retry concurrent budget WAL initialization
DavidHLP Oct 8, 2026
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
1 change: 1 addition & 0 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ services:
MYSQL_SERVICE_DB_PARAM: "characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true"
JVM_XMS: 256m
JVM_XMX: 512m
JVM_XMN: 128m
NACOS_AUTH_ENABLE: "true"
NACOS_AUTH_TOKEN: ${NACOS_AUTH_TOKEN:?NACOS_AUTH_TOKEN is required}
NACOS_AUTH_IDENTITY_KEY: ${NACOS_AUTH_IDENTITY_KEY:?NACOS_AUTH_IDENTITY_KEY is required}
Expand Down
9 changes: 7 additions & 2 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ UltiCode 已形成五个 Data Owner 与两个不持有业务表的 Worker:
| Worker | `backend-judge` | 消费 Judge Streams,执行沙箱,回写 Submission verdict |
| Worker | `backend-search` | 消费 `SearchDocumentChanged`,维护 MeiliSearch 派生索引 |
| Profile | `backend-core` | opt-in parent process; assembles Owner child contexts and does not own business tables |
| Standalone | `services/agent` | opt-in Python Agent runtime; U01 read-only loop plus bounded synthetic retrieval/sourced analysis, executable keyword-evaluation tooling, and an evaluation-only vector comparison (one embedding model + single-node Qdrant, `e2e_vector_comparison.py`) that never replaces the keyword main path; licensed-corpus, real-model evaluation, and isolation evidence are tracked in the U02 Linear tasks |
| Standalone | `services/agent` | opt-in Python Agent service; one LangGraph read-only model/tool kernel, U03 workflow orchestration and private workflow state; no UltiCode business-table ownership |

`services/agent` is an independent Python service module, not a Maven reactor module or an Owner/Worker. It calls existing Auth/App HTTP contracts, keeps identity server-side, and must project tool results before they reach a model. Its current retrieval slice is limited to checked-in agent-authored synthetic Markdown; it does not ingest public user solutions. It is not started by the default `dev-lite`/`dev-full` scopes until its runtime, readiness, and secret wiring are explicitly added.
`services/agent` is independent of Maven and the Owner/Worker topology. It calls existing Auth/App and LearningPlan HTTP contracts; it never connects to business databases or reads Owner Entities/Mappers. The checked-in retrieval corpus is agent-authored synthetic material, not user source or licensed corpus. The Agent is not started by default `dev-lite`/`dev-full` scopes.

`judge-runtime` 是共享执行依赖,不是进程。Contract modules 在 `services/api/`;共享平台能力在 `services/platform/`。跨 Owner 通过 provider-owned contract 或 consumer-owned port 协作,不共享 Entity、Mapper 或业务 Service。
`services/agent` 的依赖规则是 `Agent -> existing Auth/App HTTP contracts`;它不得连接业务数据库、读取 Owner Entity/Mapper、共享 Java 业务实现或绕过服务端身份/授权。当前仓库只包含 agent-authored synthetic fixtures;未来真实 corpus 必须来自自有或明确授权资料,公开题解不自动获得 corpus/模型外发许可。
Expand Down Expand Up @@ -145,6 +145,11 @@ Notification 是 notifications、preferences、delivery ledger 和 email 的唯
#### Judge / Search

Judge 通过 Redis Streams 异步接收 Submission outbox,使用 Problem facts 和沙箱控制执行,失败留在 PEL 或进入 DLQ。Search 只消费 allowlisted、版本化事件并更新 MeiliSearch;删除是 tombstone,业务写路径不得直写索引。
#### Agent

The Agent is a standalone consumer of existing Auth/App/LearningPlan HTTP contracts; it never connects to business databases or adds model-controlled write tools. The only bounded model/tool loop is `agent_service.graph`; `agent_loop.run_tool_loop` delegates to it. The checkpointed workflow graph dispatches server-validated actions and reuses that read-only loop for analysis. Offline scripted-model injection and guarded live-provider analysis use the same workflow and kernel. Live analysis is fail-closed unless the evidence-bound U02 gate, expected budget period, shared guard, and authorized `u03_analysis` purpose are valid; its citation judge uses the separate `u03_citation_judge` purpose.

Drafts and events live in canonical private Agent SQLite state; LangGraph checkpoints are resumable control state. Confirming a draft is not a Java write; only explicit save can persist a LearningPlan through Java. Uncertain writes remain `unknown` and reconcile through the original idempotency key. `/auth/me` is the identity source; unsafe Agent requests require the access/CSRF pair and matching header. Confirm/save/recover routes are registered only with valid evidence-bound U02 gate material; model calls additionally require the current gate and budget authorization. Nonempty citations are checked against same-run retrieval, document integrity, and support/derivability; answer text also passes bounded source-fact and tool-trace checks. These are fail-closed safeguards, not a claim of perfect semantic verification. The candidate and evidence-bound U02/U03/U04 release sequence is documented in [Development and testing](DEVELOPMENT.md#u02-u03-u04-immutable-acceptance-chain); it does not itself authorize production deployment.

### 依赖规则

Expand Down
463 changes: 454 additions & 9 deletions docs/DEVELOPMENT.md

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions docs/OPERATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

`docker/docker-compose.yml` 是基础配置;`docker/docker-compose.dev.yml` 只在 loopback 暴露开发端口;`docker/docker-compose.prod.yml` 不发布 MySQL、Redis、Nacos 或 backend 端口,前端仅作 HTTPS edge。不要直接用 PM2/Maven 启动 owner runtime,以免绕过 manifest、migration、readiness 和 rollback gate。

Nacos 的 512 MB 堆显式使用 128 MB 年轻代,避免镜像默认的 `JVM_XMN=512m` 占满堆空间;调整堆上限时应同时检查年轻代和容器内存限额。

### 生产发布前

Docker Verify builds each service locally (`load: true`, no push) and runs pinned Trivy v0.74.0 against that local image. Docker Publish pushes digest-only candidates: it does not assign normal `sha-*` or `v*` release tags while individual service gates are running.
Expand All @@ -28,6 +30,8 @@ Trivy JSON reports are uploaded per candidate even when scanning fails; a failed

Runtime images retain pinned base-image digests and apply `apk upgrade --no-cache` for Alpine security updates. Both Verify and Publish bypass cache only for the final `runtime` (backend) or `production` (frontend) stage, so cached upgrade layers cannot retain newly vulnerable packages; builder caches remain enabled. Backend dependencies use Spring Boot's BOM with narrowly scoped security overrides; verify final dependency resolution and image scan results before release.

Backend services retain Java 17 and use the Spring Boot 4 BOM for Spring Framework 7 and Tomcat 11. Existing JSON wire and persisted payload contracts remain on the documented `spring-boot-jackson2` compatibility module, selected by `spring.http.converters.preferred-json-mapper=jackson2`; Jackson 2 security overrides use `jackson-2-bom.version`, not the Jackson 3 BOM property. Do not mix Spring Framework 7 into a Boot 3 parent or retain a Tomcat 10 override under Boot 4. Dependency upgrades must pass owner-context, serialization, security and RPC checks as well as all nine image scans; reverting code must not rewrite application data or applied migrations.

`host-deploy` 在任何 migration、Redis ACL materialization、Judge sandbox provisioning 或 Compose mutation 前检查:

- approved source commit;
Expand Down Expand Up @@ -322,6 +326,8 @@ backup、restore-drill、prune 使用同一 fenced database lease(`admin:owner

可选的 `docker/docker-compose.observability.yml` 提供 loopback-only、digest-pinned 的 OpenTelemetry Collector、Prometheus、Alertmanager、Grafana、Tempo 和 Loki。HTTP Owner 暴露 metrics;无 HTTP 的 Judge/Search worker 通过 Micrometer OTLP 输出。日志携带 trace/span 关联,Grafana 可从 Loki 跳转 Tempo。

Boot 4 tracing binds OTLP export settings under `management.opentelemetry.tracing.export.otlp`; the existing `MANAGEMENT_OTLP_TRACING_ENDPOINT` and `MANAGEMENT_OTLP_AUTHORIZATION` environment inputs remain unchanged. The shared observability module enables the tracing integration and validates the effective endpoint/header pair before export: a configured authorization header requires HTTPS. OTLP metrics configuration and the opt-in Collector overlays remain separate from tracing.

生产 telemetry receiver、存储、保留周期、通知 webhook、阈值调优和真实流量 SLO 由外部运维平台负责;仓库 overlay 默认不启动,也不公开 management endpoint 或 secret。

### 关键指标
Expand Down
94 changes: 93 additions & 1 deletion docs/REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,103 @@
| --- | --- | --- |
| Auth | `http://localhost:9101` / `/auth/**` | `backend-auth` |
| Admin | `http://localhost:9102` / `/admin/**`、`/moderation/**` | `backend-admin` |
| App | `http://localhost:9103` / `/users`、`/problems`、`/contests`、`/solutions`、`/forum`、`/search`、`/ws/**` | `backend-app` |
| App | `http://localhost:9103` / `/users`、`/problems`、`/contests`、`/solutions`、`/forum`、`/learning-plans/**`、`/search`、`/ws/**` | `backend-app` |
| Notification | `http://localhost:9105` / `/notifications/**` | `backend-notification` |
| Submission | internal `9106` / Dubbo `20886` | `backend-submission` |
| Judge | internal Dubbo `20884` | `backend-judge` |

`POST /learning-plans` 为当前已认证、有效且未封禁的用户保存已确认的学习计划。请求必须携带规范 UUID 格式的 `Idempotency-Key`,且来源提交必须属于当前用户。相同 key 和请求内容重试时返回原记录;同一 key 携带不同内容时返回 HTTP 409(业务码 `40900`)。`GET /learning-plans/{id}` 和 `GET /learning-plans/by-key/{key}` 仅返回当前用户拥有的记录。

### Agent workflow API

The opt-in Python Agent service is separate from Java `/learning-plans`: it owns local workflow
drafts, while App Java owns confirmed LearningPlan records. Factory: `agent_service.app.create_app(...)`.
Thread IDs are server-generated. Every operation checks the authenticated owner; a body field
cannot select a user, model, graph node, or checkpoint.

| Operation | Request body | Purpose |
| --- | --- | --- |
| `POST /agent/threads` | `{"sourceSubmissionId": "...", "question": "..."}` | Verify caller-owned source, create thread and offline draft |
| `GET /agent/threads/{thread_id}` | — | Read caller's canonical workflow and draft |
| `GET /agent/threads/{thread_id}/events?after=N` | — | Read bounded event page; does not resume workflow |
| `POST /agent/threads/{thread_id}/analyze` | `{}` | Explicit analysis request through the shared graph; requires injected offline model factory or live model authorization |
| `PUT /agent/threads/{thread_id}/draft` | `{"draftVersion": N, "title": "...", "content": "..."}` | Compare version, update draft, invalidate confirmation |
| `POST /agent/threads/{thread_id}/confirm` | `{"draftVersion": N, "paramsDigest": "...", "confirm": true}` | Persist expiring confirmation for exact owner/version/payload; no Java write |
| `POST /agent/threads/{thread_id}/save` | `{"confirmationId": "..."}` | Explicitly dispatch the confirmed payload to Java |
| `POST /agent/threads/{thread_id}/recover` | `{"retry": false}` or `{"retry": true}` | Reconcile by original Java idempotency key; retry only when explicitly requested and all guards pass |
| `POST /agent/threads/{thread_id}/cancel` | `{}` | Fence later work; cannot roll back a dispatched Java write |

Workflow states include `draft`, `analyzing`, `awaiting_confirmation`, `confirmed`, `saving`,
`saved`, `unknown`, `failed`, and `cancelled`. Agent SQLite is canonical for drafts, workflow state,
and events; LangGraph checkpoints are resumable control state only. `saved` requires an accepted
Java record response. A timeout, lost response, or uncertain process restart remains `unknown`;
reconcile by the same idempotency key before any retry. A by-key lookup is treated as not found only
for HTTP 404 with business code `40400`; transport/server errors remain unresolved. Retry is never
automatic: `retry:true` is an explicit action and still requires an eligible unexpired confirmation,
matching payload, no cancellation, and the exact not-found result. Never mint a replacement key.

GET thread responses expose `threadId`, `runId`, `status`, `sourceSubmissionId`, `question`, a
versioned `draft` (`title`, `content`, `facts`, `hypotheses`, `citations`, `citationChecks`,
`sampleScope`), `paramsDigest`, `analysis`, a redacted `confirmation` summary, and `receipt`
(`planId`, `cancelRequested`, `failureReason`). They do not expose owner IDs or the business key.
Draft facts are derived from the validated metadata projection; source code and test data are not
included. `sampleScope` marks the checked-in synthetic corpus. Initial offline-draft citations only
establish source traceability; model-answer citations, when present, are added only after same-run
retrieval, document-integrity, support, and derivability checks.
The Agent keeps the business idempotency key private. Its recovery action uses that original key
for owner-scoped by-key reconciliation; a user-facing saved-record readback uses
`GET /learning-plans/{planId}` with the returned `planId`.
Use the GET `paramsDigest` and current `draftVersion` in the confirmation request. The returned
confirmation summary contains its id, action, bound version/digest, and expiry; it never contains
the idempotency key.
Events return `{events, next}` with a bounded page; `after` must be between 0 and
`2^63 - 1`. Invalid or foreign submission sources return `404 source_not_owned`
during creation and analysis. Authorization failures during Java save leave the outcome
`unknown` and permit explicit recovery after session and ownership checks; payload and
idempotency rejections remain terminal. Learning-plan routes are also registered in the
opt-in Core App context.

Agent responses use `{code,message,data,traceId}` with a server-generated `traceId`; upstream
response bodies are not exposed. Request bodies require `application/json`, are strict and bounded
to 128 KiB, use canonical UUIDs and exact integer versions, reject extra/duplicate JSON keys and
non-finite numbers, and enforce question length 2–200 plus Java-compatible nonblank title/content
limits of 200/16,000 Unicode code points. Event reads are bounded and read-only.

#### Identity, CSRF, and gate

`/auth/me` is the sole principal source; accept only nonempty `data.user.id` with `is_active=true`
and `is_banned=false`. Each request gets an isolated client with access cookie held only in memory.
Reads require one access cookie. Malformed access/CSRF cookie values return 401/403 before
client construction. Unsafe calls require exactly one access cookie, one CSRF cookie,
and a constant-time match with `X-CSRF-Token`; ambiguous duplicate Cookie/header input fails before
upstream HTTP. If an unsafe request includes `Origin`, it must match the configured trusted origin.
Request bodies reject identity fields. The service does not refresh sessions, set login cookies, or
configure cross-origin access.

The evidence-bound `ulticode-u02-gate-v1` loader checks candidate head/base and referenced DAV-58,
DAV-53, budget-audit, and prior-five evidence against their SHA-256 digests. Confirm, save, and
recovery routes are registered only when that gate validates. Live model requests additionally
require the expected budget period, shared budget guard, and policy-authorized purpose:
`u03_analysis` for the analysis model and `u03_citation_judge` for citation judgments. The
authorized model and guarded transport use the existing period/lane limits; missing or invalid
authorization fails closed with `503 model_budget_blocked` before a provider request. This is not
evidence that a real acceptance run has passed.

The workflow runs analysis through the shared checkpointed action graph and read-only model/tool
kernel. Answer text must pass bounded source-fact, negation-aware boundary, source-reference, and
tool-trace checks; those safeguards do not guarantee perfect semantic correctness. Nonempty
citations must exactly match evidence retrieved during that run, pass document-integrity checks,
then receive positive support and derivability judgments. A citation that was not retrieved in-run,
fails integrity/judging, or has unknown judge usage rejects analysis. Empty citations are allowed
only when the answer itself satisfies the boundary checks. Offline scripted-model/MockTransport
tests are not live acceptance evidence.

Without a valid U02 gate, confirm/save/recover routes are not mounted; requests to those paths use
the normal not-found response envelope. A valid gate alone does not authorize model calls.

For the opt-in U02/U03/U04 candidate-freeze, gate, and acceptance-bundle commands, see the
[immutable acceptance workflow](DEVELOPMENT.md#u02-u03-u04-immutable-acceptance-chain).

浏览器通常通过前端 Nginx/gateway 访问 `/api`;不要把内部 Dubbo、数据库、Redis、Nacos 或 worker 端口发布到公网。

浏览器侧统一经 `packages/http-client` 发起调用,成功结果只暴露业务 payload:`Result.code` 为 0 返回 `data`,非 0 以 `ApiError` 拒绝,非 `Result` envelope 返回 payload 本身(含 Blob),不提供原始 transport response。`apiDownload` 返回 `Promise<void>`,以 object URL 与临时 `<a download>` 元素触发浏览器保存;URL 创建后无论 DOM 步骤成功与否都会移除元素并 revoke URL,清理失败不覆盖原始错误。
Expand Down
2 changes: 1 addition & 1 deletion init-db/baseline/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ This runs the full incremental migration set (`baselineOnMigrate=false`).

Adoption requires per-schema `flyway baseline` at the auto-detected max
versions (shared `20260822120000`, `auth` `20260821100000`,
`admin` `20260822120001`, `app` `20260811180000`,
`admin` `20260822120001`, `app` `20261001120000`,
`notification` `20260815100200`, `submission` `20260817000000`):

```bash
Expand Down
Loading
Loading