Skip to content

feat(app): 独立本地 OPC App 控制平面#27

Merged
coconilu merged 4 commits into
mainfrom
codex/issue-25-opc-app
Jul 25, 2026
Merged

feat(app): 独立本地 OPC App 控制平面#27
coconilu merged 4 commits into
mainfrom
codex/issue-25-opc-app

Conversation

@coconilu

@coconilu coconilu commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Closes #25

Scope

  • add an independently installable, explicitly started, loopback-only OPC App without introducing a second Agent harness
  • extract a shared snapshot service so the legacy Dashboard and App use the same aggregation, DTO, redaction, and state semantics
  • add explicit project registration/selection/removal with App-owned, rebuildable state outside the checkout and plugin
  • keep governance objects read-only; protect the only settings mutations with exact Host/Origin checks and a per-process CSRF token
  • add content-addressed install/update/rollback/uninstall lifecycle with dry-run defaults and preservation of .opc, File/Git knowledge, Git history, user configuration, App state, and optional Mem0 data
  • add responsive local-only UI, synthetic demo mode, ADR-0017, Chinese/English user docs, migration/rollback guidance, and compatibility notes
  • keep Codex Plugin, Skills, Hook, scripts, lifecycle gate, and opc_dashboard.py compatible; no feat(adapters): 管理 Codex、Claude 与 Kimi 的本地 OPC 集成生命周期 #26 Adapter work is included

Validation

  • python scripts/validate_repo.py ? PASS
  • python -m unittest discover -s tests -p "test_*.py" -v ? PASS, 393 tests, 6 expected platform/disposable-environment skips
  • python scripts/privacy_scan.py ? PASS
  • official Plugin Validator ? PASS
  • all 7 Skill quick validators ? PASS
  • clean-room App lifecycle tests ? PASS (install, launch, stop, update, rollback, uninstall, preservation, unowned-root rejection)
  • legacy Dashboard suite ? PASS, 25/25
  • Browser QA ? PASS at 1536?1024, 960?700, and 390?844; keyboard, queue drill-down, search/filter, context refresh, project drawer, mobile navigation, zero horizontal overflow, and zero console errors
  • generated concept-to-render fidelity ledger reviewed; synthetic fixture data intentionally determines project names and queue counts

The existing v0.2 public synthetic evidence was reproducibly regenerated because docs/installation-and-distribution.md is a hash-bound delivery artifact. The only evidence change is that document's SHA-256.

Risk and rollback

  • risk: only one writable App instance is supported per OPC_APP_HOME; relative overrides are rejected to prevent working-directory drift
  • risk: App settings corruption or unreadable explicit projects; behavior fails closed to an empty/degraded view and refuses to overwrite corrupt settings
  • risk: runtime update failure; releases are content-addressed and the active pointer is atomic
  • rollback: python scripts/opc_app_admin.py rollback previews, and rollback --apply switches back to the previous complete release
  • full removal: uninstall previews, and uninstall --apply removes only the owned runtime
  • repository rollback: revert this commit; the legacy Dashboard and existing plugin workflow remain separately usable

Data and configuration impact

  • new App state defaults to %LOCALAPPDATA%\OPC\App on Windows or ${XDG_STATE_HOME:-$HOME/.local/state}/opc-app on Linux
  • runtime defaults to a separate platform user-data directory
  • no global Agent role, feature flag, account, model, key, proxy, or Adapter configuration is modified
  • no project or knowledge directory is scanned automatically
  • uninstall does not delete App state, project .opc, File/Git knowledge, Git history, existing user configuration, or Mem0 data

Review boundary

This PR is ready for independent UI/install QA and review. It is intentionally not merged.

@coconilu

Copy link
Copy Markdown
Owner Author

Addressed all four review findings in f95910d:

Finding Resolution Regression evidence
App state root overlap / canonical links Rejects lexical and canonical two-way overlap with checkout, plugin/runtime, project, .opc, knowledge, and data roots before directory creation; linked/reparse ancestors fail closed. Added checkout, project, .opc, knowledge/data parent, linked-parent, and hostile-existing-registry tests.
Snapshot service was only a DI wrapper Moved stable reads, aggregation, redaction, validation, DTO, and HTTP read service into opc_snapshot_service.py; opc_dashboard.py is now only the CLI/presentation adapter. Added AST dependency-boundary test and exact direct-core/service snapshot equality test.
Filtered grid rows stayed visible Added an explicit .filterable[hidden] { display: none !important; } contract. Static regression assertions plus real-browser DOM checks for queue, project, run, knowledge, and health views.
Release files were not persisted/verified Added a versioned full-inventory SHA-256 manifest, exact tree/hash validation during install/status/launch/rollback, repair of corrupt same-ID releases, and failure-safe staged replacement. Tamper, extra file, missing manifest, linked content, copy/pointer failures, rollback corruption, and replacement/restore failure injection tests. If automatic restore itself fails, the .corrupt-* quarantine remains recoverable and current.json is unchanged.

Validation on the pushed tree:

  • python -m unittest discover -s tests -p "test_*.py" -v: 387 passed, 6 expected platform skips.
  • The one local HTTP test that transiently hit Windows WinError 10053 on the first full attempt passed 5/5 isolated repetitions, then passed in the complete rerun above; no test-code relaxation was made.
  • python scripts/validate_repo.py: pass.
  • python scripts/privacy_scan.py: pass.
  • Official Plugin Validator: pass.
  • Official Skill quick validator: 7/7 pass.
  • Public synthetic evidence regenerated and verified: pass; release remains explicitly blocked pending its pre-existing private acceptance gate.
  • Real-browser QA: all five filter surfaces hide non-matches, desktop/mobile layouts have no horizontal overflow, mobile menu supports Escape and focus restoration, page console is clean.

No merge or self-approval performed.

@coconilu

Copy link
Copy Markdown
Owner Author

Addressed the remaining P2 launcher-transaction finding in adb7094.

Launcher artifact contract

  • bin is now one complete owned artifact set: opc-app.py, opc-app.cmd, opc-app, and launcher-manifest.json.
  • The manifest binds the exact inventory, byte sizes, and SHA-256 digests. status, rollback, post-activation validation, and the Python launcher itself verify the set.
  • Lexical and canonical runtime-boundary checks reject linked, junction/reparse, hard-linked, non-regular, incomplete, and unexpected bin content before any launcher write.
  • All files are written exclusively into a fresh sibling staging directory, flushed and file-fsynced, directory-synced where the platform supports it, then read back and verified.

Recoverable activation semantics

Activation is a recoverable directory transaction:

write + verify fresh stage
  -> bin to unique backup
  -> stage to bin
  -> verify active set
  -> best-effort old backup cleanup
  -> atomically update release pointer

This does not claim that Windows directory replacement is atomic. If activation fails, the old complete launcher set is restored. If restoration itself fails, .launcher-backup-* is retained, verified, and directly usable for manual recovery. The launcher is release-agnostic and verifies current.json plus the selected release on every run, so a pointer-write failure leaves the old pointer safely usable with the newly verified launcher set.

Failure-injection evidence

Injection Observed invariant
linked bin to outside directory fail closed; outside sentinel unchanged; old pointer unchanged
linked launcher to outside file fail closed; outside sentinel unchanged; old pointer unchanged
first staged write failure old launcher set byte-identical and usable; old pointer unchanged; no stage/backup residue
second staged write failure same invariants as first write
stage-to-bin activation failure old backup restored; old set byte-identical and usable; old pointer unchanged
activation plus restore failure bin absent rather than partially valid; one verified .launcher-backup-* retained and its launcher is usable; old pointer unchanged
pointer activation failure verified release-agnostic launcher remains usable with unchanged old pointer
launcher digest tamper status and launch fail closed; trusted update repairs the set

Validation on the pushed tree:

  • lifecycle integration: 10/10 passed;
  • full suite: 392 passed, 6 expected platform skips, 356.686s;
  • python scripts/validate_repo.py: passed;
  • python scripts/privacy_scan.py: passed;
  • official Plugin Validator: passed;
  • official Skill quick validator: 7/7 passed;
  • regenerated public evidence: passed; release remains explicitly blocked only by the pre-existing private acceptance gate.

The final full suite passed on its first run for this revision. No test relaxation, self-approval, or merge was performed.

@coconilu coconilu left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review(Kimi Code CLI 代为审查)

总体评价:质量不错,可以合并。issue #25 的核心不变量在代码层面均有落实且有测试佐证:

  • 单一语义来源:opc_dashboard.py 缩为薄 shim,聚合/脱敏/DTO 全部移入 opc_snapshot_service.pyopc_app.py 不 import dashboard(AST 断言强制),不存在两份业务规则实现;
  • 只读脱敏边界:写路由仅 projects/selection 三条,只写 App 自有 settings.json_assert_redacted 三重断言;测试断言响应字节流不含接入路径;
  • 网络安全:仅 loopback bind、Host 精确匹配、Origin 白名单、每进程 CSRF token、CSP default-src 'self'、资产路由白名单 + 防链接替换;
  • 状态隔离与生命周期:state root 双向重叠检查、release 哈希 ID + 原子 pointer、uninstall 仅删 owned runtime。

本地实测(Windows):validate_repo.py PASS;python -m unittest discover -s tests392 tests, OK (skipped=6);手动冒烟 install → launcher 启动 → rollback 拒绝(无 previous)→ uninstall 保留 state 链路正常。

发现的问题(均为 minor/nit,不阻塞合并)

  1. minor — opc_app.py:60-63scripts/opc_app_admin.py:51-61OPC_APP_HOME 为相对路径时用 os.path.abspath() 解析,状态根会随启动 cwd 漂移(不同目录启动同一 App 得到不同接入清单)。与 add_project 要求绝对路径(ABSOLUTE_PROJECT_ROOT_REQUIRED)的严格度不一致。建议非绝对的 OPC_APP_HOME 直接报错,或在 docs/opc-app.md 明确说明按 cwd 解析。

  2. nit — opc_app.py:398-440AppSettingsStore 的 read-modify-write 只有进程内 threading.RLock 保护;两个 App 实例共享同一 state root 时后写静默覆盖先写(文件本身原子写不会损坏,只丢一次操作)。单机单用户场景风险低,但 ADR-0017 未提及多实例取舍,建议明确说明。

  3. nit — PR 描述计数:PR 声称 "378 tests, 6 expected skips",本分支实测为 392 tests, skipped=6(全部通过)。仅计数口径不一致,建议核对。

无法在本环境复核、依赖 PR 声明的部分

  • 浏览器 QA(多分辨率、键盘导航、零横向溢出、零 console error)与官方 Plugin Validator、独立安装 QA;
  • Linux/macOS launcher 与 XDG 状态目录未实测(仅 Windows 验证,POSIX 路径有 6 个预期 skip 的测试兜底);
  • Host 严格校验下浏览器手动输入 http://localhost:port 会得到 400 —— 是有意的反 DNS-rebinding 设计且文档已写明默认 URL 为 127.0.0.1,可考虑返回更可读的错误页。

@coconilu
coconilu merged commit e88118e into main Jul 25, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(app): 将现有 Dashboard 演进为独立本地 OPC App

1 participant