@entrorove/skill 0.1.64 → 0.1.67

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/CAPABILITY-MAP.md +23 -0
  2. package/CHANGELOG.md +15 -0
  3. package/DELIVERY-BOUNDARIES.md +2 -0
  4. package/DESIGN-GATES.md +160 -0
  5. package/REUSE-DISCIPLINE.md +15 -1
  6. package/SKILL.md +4 -2
  7. package/blueprint-criterion-runner.mjs +1 -1
  8. package/cli.mjs +11 -4
  9. package/design-browser-checks.mjs +86 -0
  10. package/design-browser-identity.mjs +13 -0
  11. package/design-browser-state.mjs +41 -0
  12. package/design-browser.mjs +181 -0
  13. package/design-context.mjs +34 -0
  14. package/design-contracts.d.mts +4 -0
  15. package/design-contracts.mjs +77 -0
  16. package/design-declaration.mjs +17 -0
  17. package/design-frameworks.mjs +4 -0
  18. package/design-gate.mjs +19 -0
  19. package/design-lint.mjs +115 -0
  20. package/design-local-contracts.mjs +15 -0
  21. package/design-operations.mjs +81 -0
  22. package/design-policy.d.mts +5 -0
  23. package/design-policy.mjs +137 -0
  24. package/design-release.mjs +25 -0
  25. package/design-research.mjs +99 -0
  26. package/design-runner.mjs +31 -0
  27. package/design-store.mjs +116 -0
  28. package/design-ui.mjs +24 -0
  29. package/install-identity.mjs +16 -0
  30. package/local-cli.mjs +3 -1
  31. package/local-contracts.mjs +2 -0
  32. package/local-discipline-actions.mjs +5 -0
  33. package/local-discipline-batch.mjs +12 -3
  34. package/local-discipline-contracts.mjs +15 -3
  35. package/local-discipline-gate.mjs +9 -2
  36. package/local-error.mjs +18 -1
  37. package/local-execution.mjs +2 -1
  38. package/local-mutation-authority.mjs +2 -0
  39. package/local-runner-discipline.mjs +17 -3
  40. package/local-scope-zip.mjs +21 -0
  41. package/manifest.json +1 -1
  42. package/package-archive.mjs +54 -0
  43. package/package-replacement.mjs +48 -0
  44. package/package.json +34 -6
  45. package/release-state.json +50 -24
  46. package/release.mjs +3 -1
  47. package/releases.json +18 -0
  48. package/reuse-discipline-release.mjs +3 -1
  49. package/rollback-history.mjs +51 -0
  50. package/rollback-runner.mjs +77 -0
  51. package/version.mjs +20 -3
  52. package/workflow-local-runner.mjs +9 -0
package/CAPABILITY-MAP.md CHANGED
@@ -139,4 +139,27 @@ Entrorove 只治理通过技能入口执行的动作:路径、通行证、快
139
139
 
140
140
  ## 发布与校验边界
141
141
 
142
+ ### 界面设计门禁(0.1.65 新增)
143
+
144
+ 先 `design-flow` 全站流程,再 `design-research` 实际互联网布局与框架官方规范,
145
+ 以 `design-propose` 提交用户选择的效果图或原型图、组件/令牌/交互规则,
146
+ 通过原账号 `workflow-design-review` 真实确认后才准备 UI 实现。
147
+ 原 scope、签名 plan、M 和敏感变更审批继续生效;guarded-write/batch/delete/move
148
+ 及原批准 runner 检查基线与新增规则。原功能测试完成后,
149
+ `workflow-design-context` → 实际 Chromium `design-verify` → 原用户
150
+ `workflow-design-acceptance`,最终 verdict 再核对源码、测试与视觉确认。
151
+ 保存必须真实回读并刷新,提前成功、编辑提交、私有字段和布局/样式偏差不能通过。
152
+ 详见 [DESIGN-GATES.md](DESIGN-GATES.md)。对应反例和真实链测试为
153
+ `design-gates.test.mjs`、`design-browser.test.mjs`、`design-gates.integration.test.ts`;
154
+ 具体发布和部署状态以该版本真实交付证据为准,不改本表历史结论。
155
+
142
156
  本文件记录的是仓库对账,不代表 npm 发布件已验证。`@entrorove/skill@0.1.25` 必须在干净目录安装后重新执行对应 AC,未执行项单独列出,不能计入通过。生产服务、数据库和其他项目不属于本表的运行时依赖。
157
+
158
+ ## 包替换与凭证回滚门禁
159
+
160
+ 受管安装先比较版本和真实运行包摘要;同版本异内容、旧版本覆新版本拒绝。
161
+ 受管写入和批量写入读取普通 ZIP/tar/tgz 内部身份及 SemVer,未知、损坏和冲突不能靠文件名或 force 放行。
162
+ 实际历史快照字节回退及可识别的直接 Git 回滚被阻断;有意回滚继续使用原独立签名、原事务和精确前后镜像的 rollback 入口。
163
+ 普通进程中的包管理器安装需要原冻结清单和锁文件,禁止传入旧包和目的地覆盖参数。
164
+ 测试为 `package-replacement.test.mjs`、`rollback-guard.test.mjs`、`install-version-guard.test.mjs` 及真实签名合并/回滚链。
165
+ 检查覆盖 SDK 受管入口;任意外部脚本不是 OS 沙箱,发现违规副作用后保留失败及未知影响,不自动反向覆盖。
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.67 — 2026-10-19
4
+
5
+ - 更新了包降级、版本冲突与凭证回滚门禁。
6
+ - Updated package downgrade, version conflict and evidence-bound rollback guards.
7
+
8
+ ## 0.1.66 — 2026-10-19
9
+
10
+ - 更新了字体与 ZIP 完整结构核验、复用与变更一致性门禁、IDE 执行与复用纪律文档、包降级、版本冲突与凭证回滚门禁、复用纪律版本追踪、技能运行和受管安装、版本安全检查、IDE 工作流程说明、能力覆盖映射、安装包配置、统一本地错误信封、原任务短时写入授权与文件快照隔离。
11
+ - Updated font and ZIP structural verification, reuse and change consistency checks, IDE execution and reuse discipline documentation, package downgrade, version conflict and evidence-bound rollback guards, reuse discipline release tracking, skill runtime and managed installation, version safety checks, IDE workflow instructions, capability coverage map, package configuration, unified local error envelopes, Original task short-lived mutation authority and file snapshot isolation.
12
+
13
+ ## 0.1.65 — 2026-10-19
14
+
15
+ - 新增强制界面设计门禁:先全站流程图、实际布局与框架官方规范研究,再以用户选择的效果图或原型图确认设计;原写入权限继续约束实现,功能测试、真实浏览器回读刷新及视觉确认完成后才允许交付。保留原失败和未知记录。
16
+ - Add enforced UI design gates: whole-site flow, actual layout and official framework research, then original user approval of selected mockups or prototypes before implementation. Original write authority remains mandatory; delivery requires functional tests, actual browser state read-back and reload checks, and user visual acceptance. Preserve failures and unknown outcomes.
17
+
3
18
  ## 0.1.64 — 2026-10-19
4
19
 
5
20
  - 统一交付原合同连续执行与失败恢复修复:受限完整响应、原请求结算恢复、过期挑战与未用授权处理、已批准依赖与二进制核验、系统元数据观察;新增图片输入并适配 DeepSeek V4.1 Flash,完善中英文明确退出及无 IDE 记录的离线退出。保留范围、签名、预算及失败和未知记录;缺少进程终局证据的观察仍拒绝重跑。
@@ -1,5 +1,7 @@
1
1
  # Delivery boundaries and cost visibility
2
2
 
3
+ UI delivery requires the [design gates](DESIGN-GATES.md) in addition to the original delivery boundary: actual source-bound functional tests, browser state read-back and reload checks, real light/dark screenshots, and the original user's visual acceptance. Generated mockups, a successful HTTP response, a success toast or `passed: true` cannot replace that evidence.
4
+
3
5
  Contract for claiming stage delivery and displaying measured consumption. Each section is marked **live** (implemented in this package) or **planned** (documented, not yet wired).
4
6
 
5
7
  ## Stop only at a boundary (live)
@@ -0,0 +1,160 @@
1
+ # UI design and acceptance gates
2
+
3
+ Use this sequence for every governed UI implementation or change. Keep the name
4
+ Entrorove unchanged in every language. Read the actual operation schemas through
5
+ `local capabilities` and server discovery; never invent operations or signatures.
6
+
7
+ ## 1. Whole-site flow before UI code
8
+
9
+ Invoke `local design-flow` with the original repository, home, flow, design ID,
10
+ revision and `site`. Declare original requirement IDs, roles, pages, routes,
11
+ entry points, public and internal fields, states, and page-to-page actions.
12
+ Actions distinguish open, edit, input, validation, navigation, save, cancel and
13
+ delete. Every page must be reachable; mutation pages need failure and cancel
14
+ paths. The SDK freezes a Mermaid flow diagram in private design storage.
15
+
16
+ Present that whole-site diagram before designing screens. Do not use a few
17
+ isolated page screenshots as the site's information architecture. Preserve
18
+ the existing implementation when extending a system, and preserve the original
19
+ task scope and checklist order.
20
+
21
+ ## 2. Research layouts and official framework rules
22
+
23
+ Invoke `local design-research` after recording the flow. Supply two to eight
24
+ public HTTPS layout references from independent sites and a concrete reason
25
+ each pattern fits the actual product. Look up current, widely used structures
26
+ for the relevant page type: navigation, list/filter/detail, editing, confirmation,
27
+ empty/loading/error states and responsive behavior. Fetch the actual references;
28
+ an unsupported claim that a layout is popular does not count as research.
29
+
30
+ When the user specifies a framework or the nearest UI package declares one,
31
+ use its official documentation. Bind `packageName`, exact locked `version`,
32
+ `packagePath`, `lockPath` and `officialUrls`. The SDK checks recognized official
33
+ hosts, dependency declarations and package/lock hashes. Use the framework's
34
+ standard controls, navigation conventions and theme integration. Do not replace
35
+ a declared component library with arbitrary native controls or another library.
36
+ Unsupported frameworks or lock formats require explicit implementation support;
37
+ never silently relabel them as a supported framework.
38
+
39
+ References are bounded, credential-free public reads. They grant no business
40
+ source, account, database, runtime, deployment or write access.
41
+
42
+ ## 3. Create mockups or prototypes and get actual user confirmation
43
+
44
+ Respect the user's choice: an effect mockup (`kind: mockup`) or a prototype /
45
+ wireframe (`kind: prototype`) is a valid visual baseline. Use the host's actual
46
+ image-generation or design/prototyping tool after research. Present several
47
+ directions when undecided, then export real PNG views of the chosen pages and
48
+ required states at the target viewport sizes. A structural prototype can be
49
+ simple; bind theme colors, typography, spacing and components separately in its
50
+ rules so implementation cannot invent them. The same prototype may represent
51
+ both theme layouts when the approved tokens specify the theme differences.
52
+ Fix layout and interactive states before implementation. If no suitable tool
53
+ is available, report the missing prerequisite and wait; never fabricate tool
54
+ results or a user's approval. High-fidelity effect images are not mandatory.
55
+
56
+ Invoke `local design-browser-info` for the actual Chromium executable identity.
57
+ Choose an isolated `http://127.0.0.1:<port>/` preview before approval. Invoke
58
+ `local design-propose` with the real PNGs (each `mockups` entry includes its
59
+ `kind`), actual tool generation/export records and executable
60
+ `rules`. Each generation record is a plain JSON object:
61
+
62
+ ```json
63
+ {"schemaVersion":"entrorove.image-generation-record/1.0","tool":"actual-host-tool","requestId":"actual-tool-call-id","promptSha256":"<64 hex>","imageSha256":"<64 hex>","generatedAt":"<actual ISO timestamp>"}
64
+ ```
65
+
66
+ These references bind bytes and chronology; they are not an independent signature
67
+ from the external image/design tool. Show the actual host tool result and
68
+ mockup/prototype views. Never present an owned test bitmap as a real user design.
69
+
70
+ Rules freeze UI paths, verification paths, browser identity, preview origin,
71
+ approved component imports, native replacements, central style/token sources,
72
+ public field projections and executable interaction/layout/style assertions.
73
+ Every page/state/theme mockup needs its own scenario. Each scenario has an
74
+ `entryPageId`, entry `route`, ordered original flow edges, a final mockup index,
75
+ layout bounds, computed styles and a success-message selector.
76
+
77
+ Show the flow, research links, images and concrete rules to the user. Submit
78
+ `continue` → `workflow-design-review` with the exact `design`. Present the
79
+ returned interaction using the host question UI and submit only the user's
80
+ actual answer to its original callback. No default, timeout, remembered answer,
81
+ model-generated `approved:true` or another account can approve it. Do not
82
+ prepare UI implementation code before this confirmation.
83
+
84
+ The response's `approvedDesign` contains `design` and `designApproval`. Carry
85
+ both into the original `policy.discipline`. This approval grants no execution,
86
+ scope or write authority. Original scope, candidate source hashes, signed plan,
87
+ budget, mutation pass and any separate sensitive-change approval still apply.
88
+ Changing the design needs a new revision and genuine confirmation. Keep every
89
+ old proposal, rejection and approval. An existing planned task may instead use
90
+ the combined `workflow-discipline-review`, with the same original proof checks.
91
+
92
+ ## 4. Implement through the original guarded entry
93
+
94
+ UI paths and UI content are detected without an opt-out flag. Guarded writes,
95
+ batches, deletes/moves and approved runners check the approved baseline. Source
96
+ checks reject introduced inline styles, undeclared components/style imports,
97
+ unknown tokens, private fields, whole-object dumping and dynamic UI bypasses.
98
+ Framework component libraries require their approved standard controls.
99
+ Put new appearance rules in the approved central style/token files.
100
+
101
+ The frozen source baseline preserves unchanged legacy violations; it cannot
102
+ justify adding more of them. Do not bulk-rewrite old styles to satisfy a new
103
+ rule. Executable lint counterexamples ship with this capability.
104
+
105
+ Open/edit/fill/validation/navigation/cancel must not commit HTTP or browser
106
+ storage writes. Save/delete must be explicit. Render only approved public
107
+ fields; an allowed field need not appear everywhere. Internal IDs, costs,
108
+ protocol metadata and whole API objects must not spill into the UI.
109
+
110
+ ## 5. Functional and visual acceptance
111
+
112
+ Run all original approved functional tests first. Obtain
113
+ `continue` → `workflow-design-context` with `baselineDigest` and a fresh
114
+ `attemptNonce`. Its short-lived original W signature binds the original owner,
115
+ task, plan, physical repository, all actual tested input hashes and tests digest.
116
+ No fresh context is issued from a caller's success flag or incomplete tests.
117
+
118
+ Invoke `local design-verify` with the exact original `acceptedPlan`,
119
+ `validationContext`, `baselineDigest`, `repositoryRoot`, `home`, `flowId`,
120
+ approved `baseUrl` and `browserExecutablePath`. The runner launches actual
121
+ Chromium with clean temporary contexts, approved viewport/theme, no external
122
+ network or user profile, and verifies source bytes before and after testing.
123
+ The context and original plan must stay valid; the entire run is bounded to
124
+ five minutes and the original budget. Oversized/incomplete runs do not pass.
125
+
126
+ Each save/delete step requires `readBack`: same-preview HTTP GET or local
127
+ storage JSON, public `before` and `after` projections, and `afterReload`
128
+ assertions for every changed public field (or record absence after deletion).
129
+ Successful commits must change state; failed commits must preserve it. Real
130
+ HTTP status/count, premature success, browser errors, field leakage, layout
131
+ bounds, computed styles, overflow, persistence and refresh are checked.
132
+ For example, HTTP 200 followed by a toast but unchanged read-back is a failure.
133
+ Keep preview requests restricted to isolated authorized fixture data.
134
+
135
+ The result is `needs-visual-confirmation`, never an aesthetic pass. Show real
136
+ screenshots alongside approved mockups, then submit the exact report through
137
+ `continue` → `workflow-design-acceptance` and obtain the original user's actual
138
+ visual answer. Final verdict rechecks original approval, tests and source
139
+ bindings. Later source/test changes require fresh acceptance.
140
+
141
+ ## Failure and enforcement boundaries
142
+
143
+ Keep original request IDs, reports, screenshots and failures. An unfinished
144
+ browser attempt remains unknown and blocks repeating preview actions; inspect
145
+ `local design-status`. Do not erase its journal or infer that no effects occurred.
146
+ An expired authority never renews itself. These gates cannot repair or invent
147
+ historical missing process results.
148
+
149
+ Static rules cover the implemented HTML/JS/TS/JSX/Vue/Svelte and CSS subset.
150
+ Unsupported preprocessors/native UI syntax fail explicitly; official research
151
+ does not prove every framework API or language scenario was verified. Browser
152
+ checks and a user's visual judgment remain necessary.
153
+
154
+ SDK checks govern SDK operations. Optional host hooks can add interception, but
155
+ neither a hook nor a generic process runner is an OS sandbox. A generic runner's
156
+ unexpected UI writes are retained as failed/unknown effects, never successful
157
+ delivery. Do not claim that disabled hooks prevent arbitrary IDE or OS edits,
158
+ or that screenshots alone guarantee aesthetics, accessibility or full business
159
+ correctness. Exiting governance preserves the unfinished business and all
160
+ original evidence; it does not count as acceptance.
@@ -2,6 +2,8 @@
2
2
 
3
3
  Apply these rules when adding to or changing an existing system. Reuse is assessed inside the current accepted repository; external projects, databases, accounts, sessions and keys are outside scope.
4
4
 
5
+ UI changes also require [DESIGN-GATES.md](DESIGN-GATES.md): approved flow, researched layout and official framework rules, generated mockups and original user confirmation before UI implementation. New style rules check only introduced violations against the frozen original source; do not rewrite existing styles in bulk to satisfy them. Final UI delivery additionally requires real functional and visual acceptance.
6
+
5
7
  Snapshot scans stream whole-repository integrity hashes and bound semantic work to 1 MiB per source, 8 MiB of retained source, 512 files and 200,000 syntax nodes. Original target paths and proposed files receive capacity first. A resource-limited non-target source stays in the integrity comparison and is recorded as unresolved; `scanComplete` is false and absent references cannot prove a symbol unused. A target that exceeds a bound returns `ENTROROVE_DISCIPLINE_ANALYSIS_LIMIT` with its path and the exceeded limit. These are execution memory limits, never size-based review exemptions or approval thresholds. Preserve the original flow, signer, scope and queue during recovery. Upgrade with the standard managed installer, then retry the original `scope-connect` input; do not create a replacement task or bypass the host guard with an arbitrary Node environment wrapper.
6
8
 
7
9
  - D-1: Before another business mapping or fact definition, search actual definitions and reuse them. Snapshot planning records the whole repository comparison and AST usage review in `discipline-review.json`; its digest is covered by the signed original snapshot. Typed source-entity field mappings use the existing import/alias resolver to identify the same entity, then compare field/value sets. A conflicting second mapping is refused with the existing file and line. An unresolved semantic relation remains unknown. No overlap threshold is invented.
@@ -17,4 +19,16 @@ For an independent new file with no same-semantic definition, documentation/comm
17
19
 
18
20
  The write entry accepts complete content and also has separate structural action entries; complete-file replacement still passes actual old/new AST and diff checks. Error disposition `needs-human` exists for original proposal/usage/snapshot approval failures. Usage review is a separate hash-bound artifact reused by write declarations. Equivalence baselines reuse the existing frozen GoldenBaseline; original source hashes and actual baseline bytes are independently checked. Moves use explicit guarded action events, independent of Git rename heuristics. Semantic matching is limited to proven same source-entity mapping contexts; generic field-name overlap is never enough.
19
21
 
20
- Claims are limited to observed managed actions: undeclared changes can be detected and retained snapshots can support rollback. Neither static usage nor equivalence proves the business requirement is correct. Report deployment/publication only with actual evidence. Keep first-party files ≤500 lines and functions ≤100 lines; regenerate pinned assets with their generator. Do not create branches/worktrees. Validate the changed units and reverse dependencies; UI changes require light and dark checks.
22
+ ## Package replacement and rollback
23
+
24
+ The managed installer compares actual package versions before changing installed files or hooks. It also records a digest of every packaged runtime/document file. Same-version conflicting documentation or runtime identity is refused. Identical replay and restoration of a missing managed document whose remaining hashes match are allowed; an edited or unowned copy is preserved. Old markers without a runtime digest do not prove their historical runtime identity.
25
+
26
+ Guarded writes and forward batches inspect embedded `name`/SemVer in ordinary ZIP, tar and tgz package roots, with bounded structure/CRC/checksum/expanded-length validation in memory. They never extract or execute archives. A filename, timestamp, download label or `force` flag cannot establish freshness. Older versions, different identities, unknown/corrupt/ambiguous metadata, and same-version archives with different bytes refuse before M consumption or physical writes. Same-version development `package.json` changes remain ordinary source edits; unversioned source configuration is not falsely treated as a release archive. Unsupported installer formats refuse replacement rather than claiming a version comparison. Limits: 16 MiB per entry, 64 MiB expanded/input archive, 10,000 entries, 256 KiB metadata; links, ZIP64 and unsupported tar extensions stay unknown.
27
+
28
+ Guarded entries retain immutable, repository/path-bound observations of actual earlier snapshot digests. A candidate equal to a known earlier image cannot use a fresh forward pass to silently restore it. These metadata records preserve original authority/snapshot digests, claim neither completed writes nor rollback authority, and survive snapshot cleanup. They do not reconstruct unobserved historical versions or infer the outcome of an interrupted process. Direct package-manager installation requires the original frozen manifest and lock file in the actual tested source set, no package operand or destination override, and `npm ci` / frozen-lock / immutable mode. Shell-wrapped package replacement is refused. Recognizable `git reset/restore/revert/checkout/switch` commands are refused in approved process entry points before launch; use read-only `git show/log/diff` for diagnosis.
29
+
30
+ Intentional rollback uses the existing `local rollback` protocol: original successful transaction and journal head, independently signed fresh rollback authorization, exact current and restore images, original owner/task/flow/repository/scope, fresh independent lease and M pass, and real signed result reporting. A generic mutation pass, old snapshot, caller approval flag or expired contract cannot substitute. Missing, stale or foreign evidence refuses; retain failures and unknown effects. Atomic compensation of the same failed in-progress batch may restore only its already frozen exact inverse image; it is not discretionary rollback authority, never erases consumption and never converts the failed operation into success.
31
+
32
+ Approved arbitrary scripts are not an OS sandbox. A script that hides a package replacement/rollback can physically change files; post-execution checks then refuse successful evidence, preserve the actual failed comparison and report unknown effects. Do not automatically reverse those unknown effects. These checks do not claim to prevent edits outside SDK or optional host guards.
33
+
34
+ Claims are limited to observed managed actions: undeclared changes can be detected and retained snapshots can support evidenced rollback. Neither static usage nor equivalence proves the business requirement is correct. Report deployment/publication only with actual evidence. Keep first-party files ≤500 lines and functions ≤100 lines; regenerate pinned assets with their generator. Do not create branches/worktrees. Validate the changed units and reverse dependencies; UI changes require light and dark checks.
package/SKILL.md CHANGED
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: entrorove
3
- description: Execute a task selected for Entrorove through explicit SDK operations, preserving its scope, approvals and verifiable results. Hooks are optional; unrelated IDE work remains outside its governance.
3
+ description: Execute a task selected for Entrorove through explicit SDK operations, preserving scope, approvals and verifiable results. UI work requires a whole-site flow, current layout and official framework research, mockups or prototypes, original user design approval, and functional plus visual acceptance. Hooks are optional.
4
4
  ---
5
5
 
6
6
  # entrorove · 熵巡
7
7
 
8
- Version: 0.1.64.
8
+ Version: 0.1.67.
9
9
 
10
10
  Entrorove applies only to the task for which it is selected; reading these instructions, discovering capabilities or installing the package never selects or governs another task. Hooks are optional. Reading this document or calling an entry once does not execute the task. On every later turn, including after compaction, continue the original task and its required next operation before dependent work. Keep the original goal, scope, account, flow, cloud task and failure records. Never describe planning, a reported step or model output as verified delivery.
11
11
 
@@ -24,6 +24,8 @@ Entrorove applies only to the task for which it is selected; reading these instr
24
24
  6. All governed mutations use the original authority: `local guarded-write`, `local guarded-delete`, `local guarded-move`, or the original frozen approved runner plan. Native patches, scripts, terminal writes, database mutations and unknown MCP operations must not substitute for the controlled entry. User-authorized task messages use the host's message tool and remain subject to host approval; sending a report does not change the flow or grant execution authority. **A failed gate or unknown outcome must never be replaced with another executor or reported as local success.** Query the original request read-only, repair the cause, and obtain the original flow's valid authority before proceeding. If the original scope challenge preparation is absent from authenticated status, `scope-amend` may use the discovered `scope-challenge-retire` lifecycle operation. The server validates the same approved proposal and receiver, atomically fences only an unadmitted challenge, and records a signed retirement without executing that challenge. Recover and acknowledge the original retirement result with GET before a fresh challenge attempt; retain both requests and all failures. An admitted request, issued nonce, scope application or mutation has no missing-challenge retirement path. An uncertain retirement itself permits only its original GET.
25
25
  For a member scope, follow a returned `localAction` before submitting execution evidence. `mutation-connect` obtains only the original task's limited editing pass; it keeps unresolved loader files and project configuration read only. After actual writes, refresh the original scope and run `mutation-settle`. Use the returned `previousPassId` for further edits. In a signed member-local plan, `workflow-observe-step` runs the original approved command and tests without a validator private key. A server-signed `localAcceptance` advances local dependencies; a failed test does not. Preserve the raw observation and its absent independent receipt. See [IDE-EXECUTION.md](IDE-EXECUTION.md#local-ide-acceptance).
26
26
  7. Read [REUSE-DISCIPLINE.md](REUSE-DISCIPLINE.md) when modifying an existing system. The comparison baseline is the existing implementation in that same repository. It never means another project, another account, or copying external systems.
27
+ Never overwrite a newer package with older bytes, relabel an old archive as new, or roll back because the current state is uncertain. Package identity/version checks and immutable historical byte observations run in the guarded entries. A refusal requires preserved inputs and diagnosis; a `force`, `rollback`, timestamp or filename is not evidence. Intentional rollback uses the original independently signed merge rollback authorization, transaction journal, exact current/restore hashes and fresh original mutation pass. Preserve old failures, receipts and unknown effects; a successful rollback never makes the failed operation successful.
28
+ **Before preparing or writing any UI implementation, read [DESIGN-GATES.md](DESIGN-GATES.md).** Record the whole-site flow first, fetch current mainstream layout references and the specified framework's official rules, create actual mockups or prototypes using the host image/design tool, and show them to the user. Respect their choice of visual format; a prototype is a valid approval baseline. Obtain the original persisted design confirmation before UI code preparation. Carry that exact design approval through the original scope, plan and mutation gates. Neither a caller flag nor a text-only description substitutes for this sequence. Finish real functional tests, actual browser interaction/layout/style checks and original user visual acceptance before delivery. Keep Entrorove unchanged in every language.
27
29
  8. Read [DELIVERY-BOUNDARIES.md](DELIVERY-BOUNDARIES.md) before claiming a delivered stage or showing consumption. A delivered stage requires a clean boundary and uses `delivered-stage-N`. Blocked or unknown work can wait with recovery data intact; it is not a delivered stage. Display consumption only with a measured baseline.
28
30
  9. For independent verified completion, submit actual independent execution evidence and run server verification and verdict. For a signed member-local plan, finish all original local steps and requirement checkpoints, then call the returned `workflow-verdict`; only its signed final `localAcceptance` means local acceptance is complete. Describe it as local execution and tests accepted, with independent validation not performed. Never invoke formal V, merge, Swarm or handoff to relabel it. Display the confirmed evaluation acknowledgement and preserve `receiptId`, `requestId`, execution generation and evaluation digest. “已回传” requires `acknowledgement.stored: true`. Missing or failed feedback remains visible. Active, waiting and unknown tasks retain recovery data.
29
31
 
@@ -95,7 +95,7 @@ export async function runBlueprintCriterionAttempt(input) {
95
95
  attemptNonce: attempt.lease.attemptNonce, executionId: `criterion-run-${attempt.attemptId}` });
96
96
  if (canonical(activity.authority) !== canonical(attempt.executionAuthority)) fail('criterion running authority differs from its formal server attempt');
97
97
  const signer = await loadTaskHandoffSigner(ctx, input.signerConfigPath, attempt.runnerKeyId);
98
- const execution = await runApprovedPlanObject({ repositoryRoot: ctx.root, executionPlan: attempt.executionPlan,
98
+ const execution = await runApprovedPlanObject({ repositoryRoot: ctx.root, home: ctx.home, flowId: ctx.flowId, executionPlan: attempt.executionPlan,
99
99
  approvalPath: input.approvalPath, signerConfigPath: input.signerConfigPath,
100
100
  executionDeadlineAt: attempt.expiresAt });
101
101
  // The approved runner checks this signed deadline immediately before its actual spawn, after all control/manifest IO.
package/cli.mjs CHANGED
@@ -5,6 +5,7 @@ import os from 'node:os';
5
5
  import path from 'node:path';
6
6
  import { fileURLToPath } from 'node:url';
7
7
  import { compareVersions } from './version.mjs';
8
+ import { installPackageIdentity } from './install-identity.mjs';
8
9
  import { credentialPath, readCredential, writeCredential } from './local-credential.mjs';
9
10
  import { runLocal } from './local-cli.mjs';
10
11
  import { runNative } from './native-client.mjs';
@@ -25,7 +26,7 @@ import { exitIdeSession } from './ide-exit.mjs';
25
26
  import { installOptions } from './ide-command.mjs';
26
27
  import { readCliInput } from './cli-input.mjs';
27
28
  const markerName = '.entrorove-managed.json';
28
- const installFiles = ['SKILL.md', 'CAPABILITY-MAP.md', 'AGREEMENT.md', 'manifest.json', 'IDE-EXECUTION.md', 'REUSE-DISCIPLINE.md', 'DELIVERY-BOUNDARIES.md'];
29
+ const installFiles = ['SKILL.md', 'CAPABILITY-MAP.md', 'AGREEMENT.md', 'manifest.json', 'IDE-EXECUTION.md', 'REUSE-DISCIPLINE.md', 'DELIVERY-BOUNDARIES.md', 'DESIGN-GATES.md'];
29
30
  const markerSchema = 'entrorove.managed-install/1.0';
30
31
  const agreementSchema = 'entrorove.agreement-acceptance/1.0';
31
32
  const installLockWaitMs = 10_000;
@@ -63,7 +64,7 @@ async function sourceBundle() {
63
64
  throw new Error('entrorove skill instructions have a mismatched version');
64
65
  }
65
66
  const hashes = Object.fromEntries(installFiles.map((name) => [name, sha256(content.get(name))]));
66
- return { manifest, content, hashes };
67
+ return { manifest, content, hashes, packageDigest: await installPackageIdentity(packageDir, packageMetadata) };
67
68
  }
68
69
 
69
70
  function globalInstallPath(home = os.homedir()) { return path.join(home, '.codex', 'skills', 'entrorove'); }
@@ -135,6 +136,7 @@ async function managedState(directory) {
135
136
  ? Object.keys(marker.files) : [];
136
137
  if (marker.schemaVersion !== markerSchema || marker.name !== 'entrorove'
137
138
  || typeof marker.version !== 'string' || !/^\d+\.\d+\.\d+$/.test(marker.version)
139
+ || marker.packageDigest !== undefined && !/^[a-f0-9]{64}$/.test(marker.packageDigest)
138
140
  || !markerFiles.includes('SKILL.md') || !markerFiles.includes('manifest.json')
139
141
  || markerFiles.some((name) => !installFiles.includes(name) && !(marker.version === '0.1.39' && name === 'DELIVERY-BOUNDARIES.md'))
140
142
  || entries.slice().sort().join(',') !== [...markerFiles, markerName].sort().join(',')) {
@@ -155,7 +157,7 @@ async function writeStagedInstall(directory, bundle) {
155
157
  for (const name of installFiles) {
156
158
  await fs.writeFile(path.join(directory, name), bundle.content.get(name), { flag: 'wx', mode: 0o600 });
157
159
  }
158
- const marker = { schemaVersion: markerSchema, name: 'entrorove', version: bundle.manifest.version, files: bundle.hashes };
160
+ const marker = { schemaVersion: markerSchema, name: 'entrorove', version: bundle.manifest.version, files: bundle.hashes, packageDigest: bundle.packageDigest };
159
161
  await fs.writeFile(path.join(directory, markerName), JSON.stringify(marker) + '\n', { flag: 'wx', mode: 0o600 });
160
162
  }
161
163
 
@@ -225,7 +227,12 @@ async function updateGlobalInstall(target, bundle) {
225
227
  const previous = await managedState(target);
226
228
  if (previous === false) throw new Error('Global entrorove skill is not pristine and managed; refusing overwrite');
227
229
  if (previous && compareVersions(previous.version, bundle.manifest.version) > 0) {
228
- throw new Error('A newer managed entrorove skill is installed; use the latest package');
230
+ throw Object.assign(new Error('A newer managed entrorove skill is installed; use the latest package'), { code: 'ENTROROVE_PACKAGE_DOWNGRADE' });
231
+ }
232
+ if (previous?.version === bundle.manifest.version
233
+ && (Object.keys(previous.files).some(name => previous.files[name] !== bundle.hashes[name])
234
+ || previous.packageDigest && previous.packageDigest !== bundle.packageDigest)) {
235
+ throw Object.assign(new Error('Same-version managed package has different bytes; refusing overwrite'), { code: 'ENTROROVE_PACKAGE_VERSION_CONFLICT' });
229
236
  }
230
237
  if (previous?.version === bundle.manifest.version
231
238
  && installFiles.every((name) => previous.files[name] === bundle.hashes[name])) return false;
@@ -0,0 +1,86 @@
1
+ import { designFail } from './design-policy.mjs';
2
+
3
+ export async function browserAssertion(page, check) {
4
+ const locator = page.locator(check.selector);
5
+ if (check.kind === 'count') return await locator.count() === check.count;
6
+ if (check.kind === 'hidden') return !await locator.isVisible();
7
+ if (await locator.count() !== 1 || !await locator.isVisible()) return false;
8
+ if (check.kind === 'visible') return true;
9
+ if (check.kind === 'text') return (await locator.innerText()).trim() === check.value;
10
+ if (check.kind === 'value') return await locator.inputValue() === check.value;
11
+ if (check.kind === 'style') return await locator.evaluate((node, property) => getComputedStyle(node).getPropertyValue(property), check.property) === check.value;
12
+ if (check.kind === 'bounds') {
13
+ const box = await locator.boundingBox();
14
+ return Boolean(box && ['x', 'y', 'width', 'height'].every(key => Math.abs(box[key] - check.bounds[key]) <= check.bounds.tolerance));
15
+ }
16
+ return false;
17
+ }
18
+ export async function browserChecks(page, checks, failures) {
19
+ for (const check of checks) {
20
+ try { if (!await browserAssertion(page, check)) failures.push(`assert-${check.kind}`); }
21
+ catch { failures.push(`assert-${check.kind}`); }
22
+ }
23
+ }
24
+ export async function publicFieldCheck(page, pageContract, forbidden, failures) {
25
+ const fields = await page.locator('[data-entrorove-field]').evaluateAll(nodes => nodes.filter(node => {
26
+ const style = getComputedStyle(node); return style.display !== 'none' && style.visibility !== 'hidden' && node.getClientRects().length;
27
+ }).map(node => node.getAttribute('data-entrorove-field')));
28
+ if (fields.some(name => !pageContract.publicFields.includes(name))) failures.push('public-field-projection');
29
+ const text = await page.locator('body').innerText();
30
+ if ([...pageContract.internalFields, ...forbidden].some(value => value && text.includes(value))) failures.push('private-field-visible');
31
+ }
32
+ export function networkCheck(actual, expected, failures) {
33
+ if (actual.length !== expected.reduce((sum, item) => sum + item.count, 0)
34
+ || expected.some(item => actual.filter(event => event.method === item.method && event.path === item.path && event.status === item.status).length !== item.count)) failures.push('unexpected-http-mutation');
35
+ }
36
+ export async function installSuccessMonitor(page, successSelector) {
37
+ await page.addInitScript(selector => {
38
+ const events = [], acknowledgements = [], storage = [], original = window.fetch.bind(window);
39
+ Object.defineProperty(window, '__entroroveDesignMonitor', { value: { events, acknowledgements, storage } });
40
+ for (const method of ['setItem', 'removeItem', 'clear']) {
41
+ const apply = Storage.prototype[method];
42
+ Storage.prototype[method] = function(...args) {
43
+ const result = apply.apply(this, args); storage.push({ method, at: performance.now() });
44
+ acknowledgements.push({ at: performance.now(), ok: true }); return result;
45
+ };
46
+ }
47
+ if (typeof IDBObjectStore !== 'undefined') for (const method of ['add', 'put', 'delete', 'clear']) {
48
+ const apply = IDBObjectStore.prototype[method];
49
+ IDBObjectStore.prototype[method] = function(...args) { storage.push({ method: 'indexed-db-write', at: performance.now() }); return apply.apply(this, args); };
50
+ }
51
+ window.fetch = async (...args) => {
52
+ const response = await original(...args);
53
+ const method = String(args[1]?.method ?? (args[0] instanceof Request ? args[0].method : 'GET')).toUpperCase();
54
+ if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(method)) acknowledgements.push({ at: performance.now(), ok: response.ok });
55
+ return response;
56
+ };
57
+ const open = XMLHttpRequest.prototype.open;
58
+ XMLHttpRequest.prototype.open = function(method, ...args) {
59
+ const mutation = ['POST', 'PUT', 'PATCH', 'DELETE'].includes(String(method).toUpperCase());
60
+ this.addEventListener('readystatechange', () => {
61
+ if (mutation && this.readyState === 4) acknowledgements.push({ at: performance.now(), ok: this.status >= 200 && this.status < 300 });
62
+ });
63
+ return open.call(this, method, ...args);
64
+ };
65
+ let visible = false;
66
+ const observe = () => {
67
+ const node = document.querySelector(selector), style = node && getComputedStyle(node);
68
+ const next = Boolean(node && style.display !== 'none' && style.visibility !== 'hidden' && node.getClientRects().length);
69
+ if (next && !visible) events.push({ at: performance.now(), successfulAcknowledgement: acknowledgements.some(item => item.ok) });
70
+ visible = next;
71
+ };
72
+ document.addEventListener('DOMContentLoaded', () => {
73
+ new MutationObserver(observe).observe(document.documentElement, { attributes: true, childList: true, subtree: true, characterData: true }); observe();
74
+ });
75
+ }, successSelector);
76
+ }
77
+ export async function monitorSuccess(page, failures, canSucceed) {
78
+ const observed = await page.evaluate(() => window.__entroroveDesignMonitor?.events ?? []);
79
+ if (!canSucceed && observed.length || observed.some(event => !event.successfulAcknowledgement)) failures.push('premature-success-message');
80
+ }
81
+ export function previewUrl(value) {
82
+ let url; try { url = new URL(value); } catch { designFail('PREVIEW', 'Start the approved local preview before browser acceptance'); }
83
+ if (url.protocol !== 'http:' || url.hostname !== '127.0.0.1' || !url.port || url.username || url.password
84
+ || url.pathname !== '/' || url.search || url.hash) designFail('PREVIEW', 'Use an isolated HTTP preview on 127.0.0.1 with a specific port, without credentials');
85
+ return url;
86
+ }
@@ -0,0 +1,13 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { repositoryIntegrityFile } from './local-discipline-repository.mjs';
4
+ import { designFail } from './design-policy.mjs';
5
+
6
+ export async function readDesignBrowserIdentity(input) {
7
+ if (typeof input.browserExecutablePath !== 'string' || !path.isAbsolute(input.browserExecutablePath)) designFail('BROWSER', 'Configure a local Chromium executable before design confirmation');
8
+ const executablePath = await fs.realpath(input.browserExecutablePath), stat = await fs.stat(executablePath);
9
+ if (!stat.isFile() || !(stat.mode & 0o111) || stat.size > 536_870_912
10
+ || !/^(?:Google Chrome|Chromium|chrome|chromium|chromium-browser|google-chrome|chrome-headless-shell)$/.test(path.basename(executablePath))) designFail('BROWSER', 'Browser checks require an actual configured Chromium executable, without arbitrary command arguments');
11
+ const { sha256 } = await repositoryIntegrityFile(path.dirname(executablePath), path.basename(executablePath));
12
+ return { executablePath, sha256 };
13
+ }
@@ -0,0 +1,41 @@
1
+ import { canonical } from './local-integrity.mjs';
2
+
3
+ function projected(value, pointer = '') {
4
+ for (const part of pointer.split('/').slice(1).map(part => part.replace(/~1/g, '/').replace(/~0/g, '~'))) {
5
+ if (!value || !Object.hasOwn(value, part)) return undefined;
6
+ value = value[part];
7
+ }
8
+ return value;
9
+ }
10
+ export async function checkBrowserState(page, proof, phase, fields, baseUrl, failures) {
11
+ const expected = proof[phase]; let actual;
12
+ try {
13
+ if (proof.kind === 'http') {
14
+ const response = await page.request.get(new URL(proof.path, baseUrl).href, { maxRedirects: 0, timeout: 5000 });
15
+ if (response.status() !== proof[phase === 'before' ? 'statusBefore' : 'statusAfter']) failures.push('read-back-http-status');
16
+ if (expected !== null || response.status() >= 200 && response.status() < 300) {
17
+ const bytes = await response.body();
18
+ if (bytes.length > 1_048_576) throw new Error();
19
+ actual = projected(JSON.parse(bytes.toString('utf8')), proof.jsonPointer);
20
+ } else actual = null;
21
+ await response.dispose();
22
+ } else {
23
+ const value = await page.evaluate(key => localStorage.getItem(key), proof.key);
24
+ actual = value === null ? null : projected(JSON.parse(value), proof.jsonPointer);
25
+ }
26
+ if (expected === null) { if (actual !== null) failures.push('read-back-state'); }
27
+ else if (!actual || typeof actual !== 'object' || Array.isArray(actual)
28
+ || Object.keys(actual).some(key => !fields.includes(key))
29
+ || Object.keys(expected).some(key => canonical(actual[key] ?? null) !== canonical(expected[key]))) failures.push('read-back-state');
30
+ } catch { failures.push('read-back-unavailable'); }
31
+ }
32
+ export async function beginBrowserAction(page) {
33
+ await page.evaluate(() => {
34
+ const monitor = window.__entroroveDesignMonitor;
35
+ monitor.storage.length = 0; monitor.acknowledgements.length = 0; monitor.events.length = 0;
36
+ });
37
+ }
38
+ export async function checkNonCommitStorage(page, commits, failures) {
39
+ const count = await page.evaluate(() => window.__entroroveDesignMonitor.storage.length);
40
+ if (!commits && count) failures.push('unexpected-browser-storage-write');
41
+ }