yoi/.yoi/tickets/00001KX1JNJ2Y/thread.md

432 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- event: create author: "yoi ticket" at: 2026-07-08T19:18:25Z -->
## 作成
LocalTicketBackend によって作成されました。
---
<!-- event: intake_summary author: hare at: 2026-07-09T07:18:33Z -->
## Intake summary
Marked ready by `yoi ticket state`.
---
<!-- event: state_changed author: "yoi ticket" at: 2026-07-09T07:18:33Z from: planning to: ready reason: cli_state field: state -->
## State changed
Marked ready by `yoi ticket state`.
---
<!-- event: state_changed author: workspace-panel at: 2026-07-09T07:56:07Z from: ready to: queued reason: queued field: state -->
## State changed
Ticket を `workspace-panel` が queued にしました。
---
<!-- event: decision author: orchestrator at: 2026-07-09T07:57:36Z -->
## Decision
Routing decision: implementation_ready
Reason:
- Declared dependency `00001KWZ5KERY` is closed, so Decodal ProfileSourceArchive / archive verify / Runtime FSなし resolution の基礎は available。
- Related follow-up `00001KX0DSMPT` Workspace/Profile settings API/pages is also closed, so settings/profile editing surface exists and this Ticket can refine it to ProfileSourceTree virtual filesystem + Decodal editor。
- Ticket body gives concrete model/API/UI/editor/dependency/validation requirements and clear non-goals。
- typed relation blocker is resolved; OrchestrationPlan record は 0 件。inprogress Ticket も 0 件。
- queued notification is human authorized routing/start。
Evidence checked:
- Ticket body / thread / artifacts。
- `TicketRelationQuery(00001KX1JNJ2Y)`: outgoing `depends_on 00001KWZ5KERY`; target is closed。
- `TicketOrchestrationPlanQuery(00001KX1JNJ2Y)`: 0 件。
- `TicketList`: queued はこの Ticket 1件、inprogress は 0 件。
- Orchestrator worktree git status: clean on `orchestration`
- Relevant prior work: Decodal/ProfileSourceArchive closed, Workspace/Profile settings closed。
IntentPacket:
Intent:
- Add Backend-owned `ProfileSourceTree` virtual filesystem for Decodal profile sources, connect ProfileSourceArchive import closure building to that tree, strengthen Runtime archive import validation, and add Workspace settings Profile source tree editor using `decodal-codemirror@0.1.2` / CodeMirror 6。
Binding decisions / invariants:
- Browser/editor works with virtual relative paths and safe source tree/file ids, never host absolute paths or raw filesystem authority。
- Backend validation is authority; Browser editor diagnostics are only assistance。
- Runtime reads only ProfileSourceArchive manifest / virtual paths, not Workspace filesystem。
- `ProfileSourceArchive` is a snapshot of source tree revision + selected root + import closure。
- Browser-facing API must not expose host absolute path, symlink target path, Runtime endpoint/socket/session path, secret values, WorkingDirectory root path, raw resource handles, archive digest/content, or backend-private paths。
- `decodal-codemirror@0.1.2` / CodeMirror 6 dependency is in scope; formatter and Browser-side Decodal materialization as authority are out of scope。
Requirements / acceptance criteria:
- Implement typed `ProfileSourceTree` model with tree id, revision/generation, virtual root metadata, files, provenance, diagnostics, and safe summaries。
- Implement virtual import resolver for relative imports and adopted namespace imports, with normalization and typed diagnostics for root escape, absolute path, URL/unsupported scheme, extension/content-type, depth/count/bytes limits。
- Build `ProfileSourceArchive` from selected root virtual path and source tree import closure, including virtual path / source key / digest / size / content type / import map in manifest。
- Runtime `ArchiveSourceLoader` validates manifest-contained virtual paths only and rejects manifest-outside import, absolute path, `..`, unsupported namespace。
- Add scoped Profile settings file-tree API: list trees, list files, read/write/create/delete file, validate tree/file; rename/move optional。
- Add Settings UI profile source tree list and file tree editor routes/pages using Decodal CodeMirror extension and Backend save/create/delete/validate APIs。
- Focused backend/runtime/web tests cover path normalization, import closure, archive manifest validation, Runtime import resolution, Browser redaction, file tree API, and editor route smoke。
Implementation latitude:
- Exact endpoint names, source tree storage format, namespace enum details, UI route/component split, and editor model can follow existing workspace-server/Svelte style。
- v0 may keep source tree storage minimal and project/workspace-backed, provided Browser contract remains virtual-path based and Backend validation remains authority。
Escalate if:
- Browser-facing raw host path authority, Runtime workspace filesystem discovery, Browser-side Decodal materialization as authority, formatter integration, plugin/MCP/sandbox artifact sync, or Profile source RDB-normalized editor becomes required。
Validation:
- `cargo test -p yoi-workspace-server`
- `cargo test -p worker-runtime --features ws-server,fs-store`
- `cargo check -p yoi`
- `cd web/workspace && deno task check && deno task test`
- `git diff --check`
- `yoi ticket doctor`
- `nix build .#yoi --no-link`
Current code map:
- Backend profile settings/source model: `crates/workspace-server/src/profile_settings.rs`, `server.rs`, `hosts.rs`
- Decodal/Profile archive: `crates/worker-runtime/src/profile_archive.rs`, `config_bundle.rs`
- Frontend settings/profile editor: `web/workspace/src/routes/w/[workspaceId]/settings/**`, `web/workspace/src/lib/workspace-settings/**`
Critical risks / reviewer focus:
- virtual paths and tar paths must not allow absolute path / `..` / symlink/path escape。
- Browser must never receive host absolute path, secret, runtime endpoint, archive digest/content, or raw handle。
- Runtime SourceLoader must be archive-manifest/import-map only, no FS/backend fallback。
- source tree validation/write must be revision-aware and fail closed with typed diagnostics。
- new editor dependency and Nix/lock updates must be correct。
---
<!-- event: state_changed author: orchestrator at: 2026-07-09T07:57:59Z from: queued to: inprogress reason: routing_acceptance_implementation_ready field: state -->
## State changed
Queued acceptance recorded after Workspace Dashboard Queue notification authorized Orchestrator routing.
Checked context:
- Ticket body / thread / artifacts。
- `TicketRelationQuery(00001KX1JNJ2Y)`: depends_on `00001KWZ5KERY`; target is closed。
- `TicketOrchestrationPlanQuery(00001KX1JNJ2Y)`: prior record 0 件。今回 accepted_plan を記録済み。
- Orchestrator worktree git status: clean on `orchestration`
- queued Ticket はこの Ticket 1件のみ。inprogress は 0 件。
Acceptance basis:
- concrete missing decision / information は残っていない。
- Ticket scope is ProfileSourceTree virtual filesystem + Decodal editor follow-up on top of closed Decodal/ProfileSourceArchive and Workspace/Profile settings work。
- side effect はこの `queued -> inprogress` acceptance 後に、worktree-workflow に沿って implementation worktree を作成してから開始する。
---
<!-- event: implementation_report author: orchestrator at: 2026-07-09T07:58:54Z -->
## Implementation report
Implementation routing update:
- Worktree-workflow に従い implementation worktree を作成した。
- Worktree: `/home/hare/Projects/yoi/.worktree/00001KX1JNJ2Y-profile-source-tree`
- Branch: `work/00001KX1JNJ2Y-profile-source-tree`
- Worktree には tracked `.yoi` project records が見えている。
- `.yoi/memory` / `.yoi/runtime` / `.yoi/log` / `.yoi/logs` / `.yoi/local` / `.yoi/*.lock` / `.yoi/*secret*` は作成直後に存在しないことを確認した。
- Sibling Coder Pod `yoi-coder-00001KX1JNJ2Y-source-tree` を起動した。
- Scope policy: runtime workspace root は read-only、implementation worktree は write 可。Coder task では root/original workspace での read/write/validation/git operation を禁止し、この implementation worktree だけを作業対象に指定した。
- 初回プロンプト肥大化を避けるため、Coder には短い task と Ticket record 読み取り指示を渡した。
- 完了後は diff / validation evidence を Orchestrator が確認し、別 sibling Reviewer Pod に read-only review を依頼する。`StopPod` は既知の不具合があるため使用しない。
---
<!-- event: implementation_report author: orchestrator at: 2026-07-09T08:28:11Z -->
## Implementation report
Implementation progress report:
- Coder Pod completed implementation and committed `81abfa63 feat: add profile source tree editor` on branch `work/00001KX1JNJ2Y-profile-source-tree`.
- Worktree status was clean after commit。
- Changed files include:
- `crates/worker-runtime/src/profile_archive.rs`
- `crates/workspace-server/src/profile_settings.rs`
- `crates/workspace-server/src/server.rs`
- `web/workspace/deno.json`, `web/workspace/deno.lock`
- `web/workspace/src/lib/workspace-settings/DecodalSourceEditor.svelte`
- `web/workspace/src/lib/workspace-settings/{profile-api.ts,profile-types.ts}`
- `web/workspace/src/routes/w/[workspaceId]/settings/profiles/+page.svelte`
- child implementation also committed `.yoi/tickets/00001KX1JNJ2Y/{item.md,thread.md}` changes; Orchestrator will preserve authoritative Ticket records during merge if conflicts occur。
- Implementation summary:
- Added Runtime archive validation for virtual source metadata, duplicate/undeclared entries, scoped import maps, namespace/path safety, and relative virtual import resolution。
- Added Backend-owned project `ProfileSourceTree` support with virtual `profiles/...` paths, tree scanning, revision-aware file read/write/delete API, and tree-based archive/config-bundle generation。
- Added workspace server routes and Browser API/types for scoped source-tree access without host paths or backend-private/archive internals。
- Added Profiles settings Decodal CodeMirror editor using virtual paths and revision tokens。
- Added worker-runtime tests for scoped virtual import resolution and import-map mismatch rejection。
- Orchestrator reran validation in the implementation worktree:
- `git diff --check`: pass
- `cargo test -p yoi-workspace-server`: pass91 lib tests + 2 main tests
- `cargo test -p worker-runtime --features ws-server,fs-store`: pass47 lib tests + 5 main tests + doc tests
- `cargo check -p yoi`: pass
- `cd web/workspace && deno task check`: pass0 errors / 0 warnings
- `cd web/workspace && deno task test`: pass17 tests
- `yoi ticket doctor`: ok
- `nix build .#yoi --no-link`: pass
- External review will be requested via a read-only sibling Reviewer Pod. `StopPod` は使わない。
---
<!-- event: review author: reviewer at: 2026-07-09T08:32:35Z status: request_changes -->
## Review: request changes
External review result: request_changes
Blockers:
1. Decodal CodeMirror integration is missing。
- Ticket requires `decodal-codemirror@0.1.2` and the `decodal()` CodeMirror 6 extension。
- Implementation only adds `@codemirror/state` / `@codemirror/view` (`web/workspace/deno.json`) and `deno.lock` has no `decodal-codemirror` specifier。
- `DecodalSourceEditor.svelte` builds a plain CodeMirror editor with line numbers/selection/theme only, so there is no Decodal highlighting/indent/folding extension。
2. Settings UI does not implement the required tree route or create/delete editor operations。
- Ticket requires `/w/<workspace-id>/settings/profiles/trees/<source-tree-id>` plus save/create/delete via Backend APIs。
- Route tree only contains `web/workspace/src/routes/w/[workspaceId]/settings/profiles/+page.svelte`; no `trees/[sourceTreeId]` route。
- Page imports/uses only `writeProfileTreeFile` and renders only Save; no create/delete UI is wired despite `deleteProfileTreeFile` existing in API wrapper。
3. Archive construction is not a selected-root import-closure snapshot。
- `build_workspace_profile_archive(workspace_root, selector)` reads whole tree and calls `build_profile_archive_from_tree_sources`; selector is used only for post-build validation。
- Builder adds entrypoints for every project registry entry, scans every source for imports, and passes entire `sources` map into `ProfileSourceArchive::build`
- This violates requirement that archive is generated from `ProfileSourceTree revision + selected root + import closure`, with closure-only contents。
4. `ProfileSourceTree` typed model is incomplete against Ticket contract。
- Browser/backend structs expose tree id/root/revision/file count and file path/kind/revision/size, but no content digest, content type, or provenance field/typed provenance enum。
- These are explicit Ticket requirements for the typed model。
5. Symlink/path-escape write safety has a side-effect before containment validation。
- `write_profile_tree_file` calls `fs::create_dir_all(parent)` on unresolved workspace path before `checked_source_path` canonical root containment checks。
- With a symlinked intermediate directory under `.yoi/profiles`, this can create directories outside source root before later rejection。
- Path/symlink escape should fail closed without outside filesystem mutation。
Validation performed:
- Read Ticket item, routing IntentPacket, and implementation report。
- Inspected implementation commit metadata and relevant backend/runtime/frontend files in the implementation worktree only。
- Did not rerun reported cargo/deno/nix validations; Orchestrator-reported validation was passing, but blockers are functional/design acceptance gaps。
Non-blocking follow-ups:
- After blockers are fixed, add focused regression coverage for selected-root closure behavior, tree route smoke, Decodal editor extension wiring, UI create/delete flows, and symlinked-directory tree-write rejection-before-side-effect。
---
<!-- event: implementation_report author: orchestrator at: 2026-07-09T08:52:14Z -->
## Implementation report
Review-fix implementation report:
- Coder Pod completed requested changes and committed follow-up `941b9126 fix: complete profile source tree review fixes` on branch `work/00001KX1JNJ2Y-profile-source-tree`.
- Worktree status was clean after commit。
- Fix summary:
- Added `decodal-codemirror@0.1.2` and wired `decodal()` into the CodeMirror 6 editor。
- Added source tree settings route `/w/<workspace-id>/settings/profiles/trees/<source-tree-id>`
- Wired UI create/save/delete operations through Backend virtual-path source tree APIs。
- Changed archive construction to selected selector/root + import closure only, avoiding unrelated project tree sources。
- Added safe typed source/tree metadata including content type, content digest, and typed provenance。
- Hardened tree writes against symlinked intermediate directories before any outside filesystem mutation。
- Added focused regression coverage for selected-root closure-only archive contents, symlink write rejection before side effects, and web model smoke tests for tree routes / virtual create paths。
- Orchestrator reran validation in the implementation worktree:
- `git diff --check`: pass
- `cargo test -p yoi-workspace-server`: pass93 lib tests + 2 main tests
- `cargo test -p worker-runtime --features ws-server,fs-store`: pass47 lib tests + 5 main tests + doc tests
- `cargo check -p yoi`: pass
- `cd web/workspace && deno task check`: pass0 errors / 0 warnings
- `cd web/workspace && deno task test`: pass19 tests
- `yoi ticket doctor`: ok
- `nix build .#yoi --no-link`: pass
- Requesting follow-up external review against `81abfa63..941b9126` and full combined implementation。
---
<!-- event: review author: reviewer at: 2026-07-09T08:57:15Z status: request_changes -->
## Review: request changes
External follow-up review result: request_changes
Blockers:
1. Tree writes still allow a source-root symlink escape before/at filesystem mutation。
- Fix rejects symlinked intermediate directories under `.yoi/profiles`, but not `.yoi/profiles` itself being a symlink。
- `prepare_source_path_for_write()` creates/canonicalizes `source_root` without checking `symlink_metadata(source_root)` or verifying canonical source root remains under canonical workspace root。
- If `.yoi/profiles` is a symlink to an outside directory, `canonical_root` becomes outside target; `parent_relative` strips against textual symlink path and missing children can be created outside workspace。
- `checked_source_path()` repeats the escaped-root comparison and can accept files under escaped root。
- `list_profile_tree_files()` starts `read_dir` at `workspace_root/.yoi/profiles`, so symlinked source root can expose outside `.dcdl` files as virtual tree files。
- Required fix: reject symlinked source root and ensure canonical source root is confined under canonical workspace root before list/read/write/delete/archive operations。
2. Regression coverage for the remaining symlink escape is missing。
- Existing test covers symlinked child directory only。
- Add focused regression for `.yoi/profiles -> outside_dir` asserting tree write is rejected before side effects, outside target is not created/modified, and preferably list/read/archive construction also fail closed or do not traverse escaped root。
Evidence / resolved items:
- `decodal-codemirror@0.1.2` is present and `DecodalSourceEditor.svelte` imports/uses `decodal()` in CM6 extensions。
- Required tree route exists at `web/workspace/src/routes/w/[workspaceId]/settings/profiles/trees/[sourceTreeId]/...`; create/save/delete call Backend APIs。
- Selected-root archive construction now collects only import closure; tests assert unrelated sources are excluded。
- Browser-facing typed summaries include virtual path/root, content type/digest, and typed provenance without host paths。
- Runtime `ArchiveSourceLoader` remains manifest/import-map backed with no filesystem fallback。
Non-blocking follow-ups:
- Current web tests cover route helper/path normalization but not actual Svelte create/save/delete interaction behavior。
- Editor extension wiring is compile/static-source covered rather than directly unit-tested; acceptable for now, but smoke coverage would help future dependency regressions。
---
<!-- event: implementation_report author: orchestrator at: 2026-07-09T09:10:00Z -->
## Implementation report
Second review-fix implementation report:
- Coder Pod completed remaining requested changes and committed follow-up `13c6548b fix: reject profile source root symlinks` on branch `work/00001KX1JNJ2Y-profile-source-tree`
- Worktree status was clean after commit。
- Fix summary:
- Added shared `ProfileSourceRoot` guard。
- Rejects `.yoi/profiles` when it is a symlink。
- Verifies canonical `.yoi/profiles` remains under the canonical workspace root。
- Applied the guard consistently to source tree listing, file read/delete checks, write preparation, source summaries, and archive construction through tree scan/read paths。
- Hardened writes so `.yoi` and `.yoi/profiles` are created one component at a time only after symlink/canonical containment checks。
- Added regression coverage for `.yoi/profiles -> outside_dir`: write rejects before outside side effects, outside target is not created/modified, source tree read/list fails closed, and archive construction fails closed。
- Orchestrator reran validation in the implementation worktree:
- `git diff --check`: pass
- `cargo test -p yoi-workspace-server`: pass94 lib tests + 2 main tests
- `cargo test -p worker-runtime --features ws-server,fs-store`: pass47 lib tests + 5 main tests + doc tests
- `cargo check -p yoi`: pass
- `cd web/workspace && deno task check`: pass0 errors / 0 warnings
- `cd web/workspace && deno task test`: pass19 tests
- `yoi ticket doctor`: ok
- `nix build .#yoi --no-link`: pass
- Requesting second follow-up external review against `941b9126..13c6548b` and full combined implementation。
---
<!-- event: review author: reviewer at: 2026-07-09T09:16:20Z status: approve -->
## Review: approve
External second follow-up review result: approve
Blockers verified:
- Source-root symlink rejection is now in the shared source-root path validation:
- `existing_profile_source_root` rejects `.yoi/profiles` when the final component is a symlink。
- `prepare_profile_source_root_for_write` rejects both `.yoi` symlink parents and `.yoi/profiles` symlink roots before create/write operations。
- `checked_source_path`, tree listing, file read/write/delete, and archive construction all flow through these guards before touching tree contents。
- Regression coverage for `.yoi/profiles -> outside_dir` exists:
- `source_root_symlink_is_rejected_before_tree_side_effects` covers write rejection before outside side effects, `read_profile_source_tree` fail-closed behavior, and `build_workspace_profile_archive` fail-closed behavior。
- Existing symlink-parent / entry symlink tests remain in place for nested escape cases。
Still-good items checked and intact:
- Decodal editor wiring remains present via the Svelte editor component using `decodal()` / CodeMirror integration。
- Profile tree API routes remain wired for tree read and file create/save/delete:
- `/settings/profiles/trees/{source_tree_id}`
- `/settings/profiles/trees/{source_tree_id}/file`
- Virtual source tree summaries still expose virtual metadata/provenance/digest-type fields without host path disclosure。
- Selected-root archive construction still uses the selected profile entrypoint plus import closure rather than blindly archiving every source。
- `Runtime ArchiveSourceLoader` behavior remains bounded to archive manifest/import-map package files plus archive-backed imports; no host filesystem fallback was introduced。
Validation performed:
- Read Ticket records in `.yoi/tickets/00001KX1JNJ2Y/`, including latest implementation/review context。
- Inspected latest fix `13c6548b`, diff from `941b9126..13c6548b`, and relevant source files in the implementation worktree only。
- Reviewed the orchestrator-provided successful validation list; Reviewer did not rerun tests or modify files。
Non-blocking follow-up:
- A direct assertion for `read_profile_tree_file` against a root-symlinked `.yoi/profiles` would make the regression matrix even more explicit, but the implementation path already uses the same guarded `checked_source_path`, so this is not a merge blocker。
No merge-blocking issues found.
---
<!-- event: state_changed author: orchestrator at: 2026-07-09T09:18:29Z from: inprogress to: done reason: merged_validated_review_approved field: state -->
## State changed
Merge/validation acceptance:
- Second follow-up external review approved the implementation after symlink-root blocker fix。
- Implementation branch `work/00001KX1JNJ2Y-profile-source-tree` was merged into the orchestration branch with merge commit `9a2e57fb merge: profile source tree editor`
- Ticket-record conflicts during merge were resolved by preserving Orchestrator-side Ticket item/thread records; product/code changes were merged。
- Final validation in Orchestrator worktree passed:
- `git diff --check`: pass
- `cargo test -p yoi-workspace-server`: pass94 lib tests + 2 main tests
- `cargo test -p worker-runtime --features ws-server,fs-store`: pass47 lib tests + 5 main tests + doc tests
- `cargo check -p yoi`: pass
- `cd web/workspace && deno task check`: pass0 errors / 0 warnings
- `cd web/workspace && deno task test`: pass19 tests
- `yoi ticket doctor`: ok
- `nix build .#yoi --no-link`: pass
Implementation commits:
- `81abfa63 feat: add profile source tree editor`
- `941b9126 fix: complete profile source tree review fixes`
- `13c6548b fix: reject profile source root symlinks`
No merge-blocking issues remain.
---
<!-- event: state_changed author: hare at: 2026-07-09T09:18:48Z from: done to: closed reason: closed field: state -->
## State changed
Ticket を closed にしました。
---
<!-- event: close author: hare at: 2026-07-09T09:18:48Z status: closed -->
## 完了
完了。
実装内容:
- Backend-owned `ProfileSourceTree` virtual filesystem model を追加した。
- virtual relative paths / safe source tree ids / file ids / revision / content digest / content type / typed provenance / diagnostics を扱う safe summaries を追加した。
- Profile source tree list/read/write/create/delete/validate scoped APIs を追加した。
- virtual import resolver を追加し、relative imports / scoped namespace imports を扱い、absolute path、URL/unsupported scheme、root escape、unsupported namespace、limit excess を fail closed するようにした。
- `ProfileSourceArchive` construction を selected root/selector + source tree revision + import closure snapshot に変更し、unrelated sources を archive に含めないようにした。
- Runtime `ArchiveSourceLoader` / archive verification を virtual path / import-map only に強化し、Workspace filesystem/backend fallback を導入しないまま維持した。
- Settings UI に Profile source tree list と Decodal source editor route を追加した。
- `decodal-codemirror@0.1.2` と CodeMirror 6 の `decodal()` extension を editor に接続した。
- UI create/save/delete operations を Backend virtual-path APIs に接続した。
- `.yoi/profiles` source root symlink と symlink/path escape を list/read/write/delete/archive construction paths で reject し、outside filesystem mutation 前に fail closed するようにした。
- Focused backend/runtime/web tests を追加した。
主な commit / merge:
- implementation: `81abfa63 feat: add profile source tree editor`
- review fix: `941b9126 fix: complete profile source tree review fixes`
- symlink-root fix: `13c6548b fix: reject profile source root symlinks`
- merge into orchestration: `9a2e57fb merge: profile source tree editor`
Review:
- 初回 external review は request_changes。
- first follow-up review も request_changessource-root symlink blocker
- second follow-up review は approve。merge-blocking issue なし。
Final validation in Orchestrator worktree:
- `git diff --check`: pass
- `cargo test -p yoi-workspace-server`: pass94 lib tests + 2 main tests
- `cargo test -p worker-runtime --features ws-server,fs-store`: pass47 lib tests + 5 main tests + doc tests
- `cargo check -p yoi`: pass
- `cd web/workspace && deno task check`: pass0 errors / 0 warnings
- `cd web/workspace && deno task test`: pass19 tests
- `yoi ticket doctor`: ok
- `nix build .#yoi --no-link`: pass
補足:
- implementation branch merge 時に Ticket record conflict が発生したため、Orchestrator 側の Ticket item/thread を保持して解決した。product/code changes は merge 済み。
- Reviewer の non-blocking follow-up: `read_profile_tree_file` に対する root-symlink direct assertion を追加すると regression matrix がさらに明示的になるが、同じ guarded `checked_source_path` を通るため merge blocker ではない。
---