Skip to content

Commit 7fb2f93

Browse files
hyobanclaude
andcommitted
Merge remote-tracking branch 'origin/main' into deploy/konwledge
Adapt the KnowledgeFS branch to main's refactors: - Move KnowledgeFS membership hooks from the removed account_service into WorkspaceRepository/WorkspaceMemberService within the same transaction. - Route Classic-to-Agent upgrade endpoints through a new KnowledgeUpgradeApplicationService behind console_account_admission, and carry creator_ids and knowledge_fs_upgrade through the dataset list service. - Adopt main's data-source credential repository/gateways in KnowledgeFS initial-source preview, tasks, and datasource backwards invocation. - Take main's catalog-driven difyctl v2; the hand-written KnowledgeFS CLI commands are dropped since the server catalog now drives commands. - Port dify-ui API renames (AlertDialogFooter, DrawerClose + IconButton), the lazy workflow panel map, the dataset card inline menu, and the new date picker into the reorganized knowledge UI. - Restore main's external knowledge endpoint JSON probe and missing oxlint suppressions lost in earlier merges; regenerate contracts and OpenAPI docs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 parents 971dab3 + 1ed388d commit 7fb2f93

2,676 files changed

Lines changed: 113310 additions & 120894 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 8 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,15 @@
1-
# Rule Catalog — Architecture
1+
# Architecture review
22

3-
## Scope
4-
- Covers: controller/service/core-domain/libs/model layering, dependency direction, responsibility placement, observability-friendly flow.
3+
The [API agent guide](../../../../api/AGENTS.md) owns package boundaries. In particular, `api/core/` is migration-only: do not propose new files or extracted implementations there.
54

6-
## Rules
5+
## Transport and domain boundaries
76

8-
### Keep business logic out of controllers
9-
- Category: maintainability
10-
- Description: Controllers should parse input, call services, and return serialized responses. Business decisions inside controllers make behavior hard to reuse and test.
11-
- Suggested fix: Move domain/business logic into the service or core/domain layer. Keep controller handlers thin and orchestration-focused.
12-
- Example:
13-
- Bad:
14-
```python
15-
@bp.post("/apps/<app_id>/publish")
16-
def publish_app(app_id: str):
17-
payload = request.get_json() or {}
18-
if payload.get("force") and current_user.role != "admin":
19-
raise ValueError("only admin can force publish")
20-
app = App.query.get(app_id)
21-
app.status = "published"
22-
db.session.commit()
23-
return {"result": "ok"}
24-
```
25-
- Good:
26-
```python
27-
@bp.post("/apps/<app_id>/publish")
28-
def publish_app(app_id: str):
29-
payload = PublishRequest.model_validate(request.get_json() or {})
30-
app_service.publish_app(app_id=app_id, force=payload.force, actor_id=current_user.id)
31-
return {"result": "ok"}
32-
```
7+
Controllers own request parsing, service calls, and response serialization. Trace business decisions to their service or domain owner; flag controller policy when it duplicates or bypasses that owner. Move extracted behavior to an appropriate owner outside `core/`.
338

34-
### Preserve layer dependency direction
35-
- Category: best practices
36-
- Description: Controllers may depend on services, and services may depend on core/domain abstractions. Reversing this direction (for example, core importing controller/web modules) creates cycles and leaks transport concerns into domain code.
37-
- Suggested fix: Extract shared contracts into core/domain or service-level modules and make upper layers depend on lower, not the reverse.
38-
- Example:
39-
- Bad:
40-
```python
41-
# core/policy/publish_policy.py
42-
from controllers.console.app import request_context
9+
## Dependency direction
4310

11+
Transport layers may depend on services and domain contracts. Domain code must not import controller or request context. Pass validated actor, tenant, and resource data explicitly instead of reaching upward for it. Check existing owners and import-linter contracts before proposing a shared abstraction.
4412

45-
def can_publish() -> bool:
46-
return request_context.current_user.is_admin
47-
```
48-
- Good:
49-
```python
50-
# core/policy/publish_policy.py
51-
def can_publish(role: str) -> bool:
52-
return role == "admin"
13+
## Business-agnostic libraries
5314

54-
55-
# service layer adapts web/user context to domain input
56-
allowed = can_publish(role=current_user.role)
57-
```
58-
59-
### Keep libs business-agnostic
60-
- Category: maintainability
61-
- Description: Modules under `api/libs/` should remain reusable, business-agnostic building blocks. They must not encode product/domain-specific rules, workflow orchestration, or business decisions.
62-
- Suggested fix:
63-
- If business logic appears in `api/libs/`, extract it into the appropriate `services/` or `core/` module and keep `libs` focused on generic, cross-cutting helpers.
64-
- Keep `libs` dependencies clean: avoid importing service/controller/domain-specific modules into `api/libs/`.
65-
- Example:
66-
- Bad:
67-
```python
68-
# api/libs/conversation_filter.py
69-
from services.conversation_service import ConversationService
70-
71-
72-
def should_archive_conversation(conversation, tenant_id: str) -> bool:
73-
# Domain policy and service dependency are leaking into libs.
74-
service = ConversationService()
75-
if service.has_paid_plan(tenant_id):
76-
return conversation.idle_days > 90
77-
return conversation.idle_days > 30
78-
```
79-
- Good:
80-
```python
81-
# api/libs/datetime_utils.py (business-agnostic helper)
82-
def older_than_days(idle_days: int, threshold_days: int) -> bool:
83-
return idle_days > threshold_days
84-
85-
86-
# services/conversation_service.py (business logic stays in service/core)
87-
from libs.datetime_utils import older_than_days
88-
89-
90-
def should_archive_conversation(conversation, tenant_id: str) -> bool:
91-
threshold_days = 90 if has_paid_plan(tenant_id) else 30
92-
return older_than_days(conversation.idle_days, threshold_days)
93-
```
15+
`api/libs/` must not own product policy or orchestration. Flag service/controller dependencies and domain-specific decisions in library helpers. Move that behavior to its existing service or domain owner outside `core/`; retain only reusable infrastructure in `libs/`.

‎.agents/skills/backend-code-review/references/repositories-rule.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
- Description: If a table/model already has a repository abstraction, all reads/writes/queries for that table should use the existing repository. If no repository exists, introduce one only when complexity justifies it, such as large/high-volume tables, repeated complex query logic, or likely storage-strategy variation.
1212
- Suggested fix:
1313
- First check `api/repositories`, `api/core/repositories`, and `api/extensions/*/repositories/` to verify whether the table/model already has a repository abstraction. If it exists, route all operations through it and add missing repository methods instead of bypassing it with ad-hoc SQLAlchemy access.
14-
- If no repository exists, add one only when complexity warrants it (for example, repeated complex queries, large data domains, or multiple storage strategies), while preserving dependency direction (service/core depends on abstraction; infra provides implementation).
14+
- If no repository exists, add one only when complexity warrants it (for example, repeated complex queries, large data domains, or multiple storage strategies). Follow `api/AGENTS.md`: put new abstractions and implementations outside migration-only `api/core/`, with services/domain code depending on abstractions and infrastructure providing implementations.
1515
- Example:
1616
- Bad:
1717
```python

‎.devcontainer/README.md‎

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Development with devcontainer
22

33
This project includes a devcontainer configuration that allows you to open the project in a container with a fully configured development environment.
4-
Both frontend and backend environments are initialized when the container is started.
4+
The create hook installs workspace JavaScript dependencies and `uv`; the start hook synchronizes backend dependencies. Follow the [backend setup](../api/README.md) and [frontend guide](../web/README.md) to configure environment files and start services.
55

66
## GitHub Codespaces
77

@@ -19,20 +19,6 @@ if you have VS Code installed, you can click the button above to open this proje
1919

2020
You can learn more in the [Dev Containers documentation](https://code.visualstudio.com/docs/devcontainers/containers).
2121

22-
## Pros of Devcontainer
23-
24-
Unified Development Environment: By using devcontainers, you can ensure that all developers are developing in the same environment, reducing the occurrence of "it works on my machine" type of issues.
25-
26-
Quick Start: New developers can set up their development environment in a few simple steps, without spending a lot of time on environment configuration.
27-
28-
Isolation: Devcontainers isolate your project from your host operating system, reducing the chance of OS updates or other application installations impacting the development environment.
29-
30-
## Cons of Devcontainer
31-
32-
Learning Curve: For developers unfamiliar with Docker and VS Code, using devcontainers may be somewhat complex.
33-
34-
Performance Impact: While usually minimal, programs running inside a devcontainer may be slightly slower than those running directly on the host.
35-
3622
## Troubleshooting
3723

3824
if you see such error message when you open this project in codespaces:

‎.gitattributes‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,6 @@
99
# Codegen output must stay byte-identical across platforms so
1010
# `pnpm tree:check` in CI does not trip on CRLF rewrites.
1111
*.generated.ts text eol=lf
12+
13+
# Skill scripts run on every platform, so keep LF line endings.
14+
/skills/** text=auto eol=lf

‎.github/CODEOWNERS‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,10 @@
5757

5858
# Backend - RAG (Retrieval Augmented Generation)
5959
/api/core/rag/ @JohnJyong
60+
/api/services/knowledge/ @JohnJyong
6061
/api/services/rag_pipeline/ @JohnJyong
61-
/api/services/dataset_service.py @JohnJyong
6262
/api/services/knowledge_service.py @JohnJyong
63-
/api/services/external_knowledge_service.py @JohnJyong
6463
/api/services/hit_testing_service.py @JohnJyong
65-
/api/services/metadata_service.py @JohnJyong
66-
/api/services/vector_service.py @JohnJyong
6764
/api/services/entities/knowledge_entities/ @JohnJyong
6865
/api/services/entities/external_knowledge_entities/ @JohnJyong
6966
/api/controllers/console/datasets/ @JohnJyong

‎.github/actions/setup-core-e2e/action.yml‎

Lines changed: 0 additions & 29 deletions
This file was deleted.

‎.github/actions/setup-core-e2e/install.sh‎

Lines changed: 0 additions & 56 deletions
This file was deleted.
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
const { execFile } = require('node:child_process')
2+
const { appendFile, mkdtemp, rm } = require('node:fs/promises')
3+
const os = require('node:os')
4+
const path = require('node:path')
5+
const { promisify } = require('node:util')
6+
const waitForBuild = require('./wait.cjs')
7+
8+
const execFileAsync = promisify(execFile)
9+
10+
async function downloadWebBuild({
11+
env = process.env,
12+
execute = execFileAsync,
13+
webDir = path.resolve(__dirname, '../../../web'),
14+
} = {}) {
15+
const repository = env.GITHUB_REPOSITORY
16+
const runId = env.GITHUB_RUN_ID
17+
const runAttempt = Number(env.GITHUB_RUN_ATTEMPT)
18+
if (
19+
!repository ||
20+
!/^\d+$/.test(runId || '') ||
21+
!Number.isInteger(runAttempt) ||
22+
runAttempt < 1 ||
23+
!env.GH_TOKEN
24+
) {
25+
throw new Error(
26+
'Downloading the Web build requires GITHUB_REPOSITORY, GITHUB_RUN_ID, GITHUB_RUN_ATTEMPT, and GH_TOKEN.',
27+
)
28+
}
29+
30+
const run = (command, args, timeout = 60_000) =>
31+
execute(command, args, {
32+
env,
33+
timeout,
34+
maxBuffer: 32 * 1024 * 1024,
35+
})
36+
const api = async (endpoint, paginate = false) => {
37+
const { stdout } = await run('gh', [
38+
'api',
39+
endpoint,
40+
...(paginate ? ['--paginate', '--slurp'] : []),
41+
])
42+
return JSON.parse(stdout)
43+
}
44+
const prefix = `repos/${repository}/actions`
45+
let summary = ''
46+
const artifactId = await waitForBuild({
47+
github: {
48+
rest: {
49+
actions: { listJobsForWorkflowRunAttempt: 'jobs', listWorkflowRunArtifacts: 'artifacts' },
50+
},
51+
paginate: async (kind) => {
52+
const endpoint =
53+
kind === 'jobs'
54+
? `${prefix}/runs/${runId}/attempts/${runAttempt}/jobs?per_page=100`
55+
: `${prefix}/runs/${runId}/artifacts?per_page=100`
56+
const pages = await api(endpoint, true)
57+
return pages.flatMap((page) => page[kind])
58+
},
59+
},
60+
context: { repo: {}, runId },
61+
core: {
62+
info: console.log,
63+
summary: {
64+
addHeading(text) {
65+
summary += `### ${text}\n\n`
66+
return this
67+
},
68+
addRaw(text) {
69+
summary += `${text}\n`
70+
return this
71+
},
72+
async write() {
73+
if (env.GITHUB_STEP_SUMMARY) await appendFile(env.GITHUB_STEP_SUMMARY, summary)
74+
},
75+
},
76+
},
77+
runAttempt,
78+
})
79+
80+
const artifact = await api(`${prefix}/artifacts/${artifactId}`)
81+
const directory = await mkdtemp(path.join(os.tmpdir(), 'dify-e2e-web-build-'))
82+
try {
83+
// Artifact names are immutable within a run. Use the name selected by wait.cjs,
84+
// including its matching upload timestamp when only consumer jobs are rerun.
85+
await run(
86+
'gh',
87+
['run', 'download', runId, '--repo', repository, '--name', artifact.name, '--dir', directory],
88+
300_000,
89+
)
90+
await run('tar', ['-xf', path.join(directory, 'e2e-web-build.tar'), '-C', webDir])
91+
console.log(`Web build artifact ${artifactId} extracted and ready.`)
92+
} finally {
93+
await rm(directory, { force: true, recursive: true })
94+
}
95+
}
96+
97+
module.exports = downloadWebBuild
98+
99+
if (require.main === module) {
100+
downloadWebBuild().catch((error) => {
101+
console.error(error)
102+
process.exitCode = 1
103+
})
104+
}

0 commit comments

Comments
 (0)