@entrorove/skill 0.1.24 → 0.1.28

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 (50) hide show
  1. package/CAPABILITY-MAP.md +73 -15
  2. package/CHANGELOG.md +20 -0
  3. package/SKILL.md +11 -1
  4. package/calctool-runtime-entry.mjs +2 -2
  5. package/cli.mjs +18 -1
  6. package/local-archguard-checkpoint.mjs +5 -2
  7. package/local-archguard-snapshot.mjs +18 -13
  8. package/local-autocoord17-authority.mjs +36 -0
  9. package/local-autocoord17-controller.mjs +164 -0
  10. package/local-autocoord17-peers.mjs +111 -0
  11. package/local-autocoord17-store.mjs +110 -0
  12. package/local-autocoord17-tasks.mjs +68 -0
  13. package/local-autocoord17-waits.mjs +118 -0
  14. package/local-calctool-cli.mjs +3 -1
  15. package/local-calctool-composition17.mjs +50 -0
  16. package/local-calctool-runtime.mjs +1 -1
  17. package/local-calctool.mjs +1 -1
  18. package/local-chain-bindings.mjs +93 -0
  19. package/local-chain-broker.mjs +61 -0
  20. package/local-chain-contracts.mjs +21 -0
  21. package/local-chain-controller.mjs +321 -0
  22. package/local-chain-human.mjs +48 -0
  23. package/local-chain-lock.mjs +54 -0
  24. package/local-chain-operations.mjs +7 -0
  25. package/local-chain-plan.mjs +232 -0
  26. package/local-chain-release-labels.mjs +12 -0
  27. package/local-chain-results.mjs +75 -0
  28. package/local-cli.mjs +23 -15
  29. package/local-contract-types.mjs +1 -1
  30. package/local-contracts.mjs +2 -0
  31. package/local-error.mjs +23 -0
  32. package/local-execution.mjs +1 -1
  33. package/local-final-gate16-bundle.mjs +2 -2
  34. package/local-formula-ast.mjs +23 -14
  35. package/local-formula-engine.mjs +17 -3
  36. package/local-formula.mjs +1 -1
  37. package/local-invocations.mjs +35 -0
  38. package/local-ledger.mjs +13 -2
  39. package/local-mutation-authority.mjs +13 -6
  40. package/local-mutation-binding.mjs +5 -2
  41. package/local-mutation-files.mjs +29 -4
  42. package/local-task-handoff-observation.mjs +4 -1
  43. package/local-validator-isolation17.mjs +256 -0
  44. package/manifest.json +1 -1
  45. package/package.json +22 -2
  46. package/release-state.json +48 -29
  47. package/release.mjs +22 -1
  48. package/releases.json +24 -0
  49. package/runtime/calctool-runtime-v7.0.43.mjs +40 -17
  50. package/workflow-local-request.mjs +3 -1
package/CAPABILITY-MAP.md CHANGED
@@ -1,18 +1,76 @@
1
- # v2 capability map (AC-16)
1
+ # 能力对账图(WP-02 / AC-20)
2
2
 
3
- This is the operation-level inventory for the eight legacy domains. `equivalent` means the v2 operation preserves the contract; `narrower` means the operation is available behind a stricter v2 gate or with a smaller side-effect boundary; `uncovered` means the old operation remains intentionally unavailable and callers must receive an explicit unsupported result.
3
+ 本表把旧八域的远端公开操作与当前本地执行器分开统计。旧版 `SKILL.md` 只规定入口、状态和边界,没有逐域列出操作名;远端操作名采用随包锁定的 v2 runtime catalog 交叉核对,本地操作名直接来自 `local-contracts.mjs` 的 `LOCAL_OPERATION_SCHEMAS`。两个分母不能合并。
4
4
 
5
- | Legacy domain | Legacy operations | v2 mapping and semantics |
5
+ ## 边界
6
+
7
+ Entrorove 只治理通过技能入口执行的动作:路径、通行证、快照和证据。它不控制智能体或 IDE 直接执行的宿主 shell/操作系统副作用,例如 `docker rm`、`DROP DATABASE`、`cp -r` 或任意写文件。绕过技能的副作用可以被记录、拒绝交付或要求重做,但永远不会获得 `verified`,也不算已验证交付。对外承诺只能是:绕过治理的动作不会被系统认证为已交付。
8
+
9
+ ## 旧 `SKILL.md` 原文基线(AC-20 的来源)
10
+
11
+ 本节固定引用基线提交 `5da9c2d`(`@entrorove/skill@0.1.24`)的原文入口和保证,不把当前代码自报的操作名当作旧能力分母。原文明确写出的入口是:
12
+
13
+ | 原文位置 | 原文入口/保证 | 对账要求 |
14
+ | --- | --- | --- |
15
+ | `SKILL.md:12` | `npx @entrorove/skill install`;只替换受管理的 pristine 副本并保留手工文件 | 安装面必须保持单一受管理副本;发布件另行复测 |
16
+ | `SKILL.md:14-18` | `configure`、协议 `agreement`、密钥只从标准输入进入、协议接受后再重试 | 凭据与协议链路不能由本地自报替代 |
17
+ | `SKILL.md:22` | `invoke`、先发现 `capabilities`、按 schema 校验、`start`、`continue`、`direct`、`requestId` 内容派生 | 发现失败即停止;不能把未列入合同的操作静默映射 |
18
+ | `SKILL.md:28-30` | merge 必须带 `baselineRef`;能力图必须说明 equivalent/narrower/uncovered | 远端八域表只作为 runtime 交叉证据,不作为旧文原文的替代 |
19
+ | `SKILL.md:32-38` | `status` 只读恢复原 request、`flows` 列可恢复流;状态含 `reported` 与 `verified` | 恢复不得重发 POST,reported 不得升绿 |
20
+ | `SKILL.md:40` | `local <operation>`、`local capabilities`、快照/通行证/预算/证据/合并闸门;状态仅存 `~/.entrorove/` | 本地操作表必须逐项对应 schema 与测试,未跑项单列 |
21
+
22
+ 旧文没有逐域操作名;因此下面的八域远端表明确标注为“锁定 runtime catalog 的交叉核对”,本地表明确标注为当前 schema 分区。两者都不能反向改写旧 `SKILL.md` 的原文基线。
23
+
24
+ ## 远端基线(旧八域)
25
+
26
+ | 域 | 旧 runtime 公开操作 | 当前映射 | 语义边界 |
27
+ | --- | --- | --- | --- |
28
+ | aimlock | `capabilities`, `help`, `intake`, `classify`, `scope-contract`, `skill-route`, `propose-nodes`, `accept-nodes`, `snapshot-plan`, `mutate-gate`, `continuity-check`, `interrupt`, `keep-alive`, `delivery-doc`, `validate-json`, `snapshot-verify`, `run-status`, `chain-plan`, `chain-status`, `feedback` | discovery、任务路由、快照、门控、状态、取消和交付证据 | `brain-plan/check/validate/status` 没有 aimlock 公开入口,见 AC-24 |
29
+ | blueprint | `capabilities`, `help`, `intake`, `validate`, `compile-inline`, `acceptance-report` | `blueprint` runtime;验收证据由调用方提供,不能自升 `verified` | `acceptance-report` 不是自报绿灯 |
30
+ | calctool | `capabilities`, `help`, `intake`, `validate`, `compile-inline` | `calctool` runtime;公式执行和 final-gate 在本地 Decimal 层 | `research` 是 pipeline 内部阶段,不是独立公开操作 |
31
+ | swarm | `capabilities`, `help`, `intake`, `org-chart`, `blueprint-bridge`, `dispatch`, `claim`, `report`, `accept`, `swarm-status`, `traffic-light`, `security-check`, `validate-json`, `heartbeat`, `reclaim` | 服务端路由、租约和 `reported`/`verified` 状态 | 本地 peer/coordinate 是统一本地层,不能冒充旧 swarm 远端 |
32
+ | validator | `capabilities`, `help`, `intake`, `plan`, `validate-structure`, `security-scan`, `compliance-audit`, `functional-verify`, `verdict`;local-runner:`sandbox-run`, `fuzz-input`, `perf-benchmark`, `intrusive-test` | pure validator 与受控 runner 分开 | 生产验证需独立回执;`verdict` 不能把 reported 变绿 |
33
+ | mergeguard | `capabilities`, `help`, `intake`, `resolve-propose`, `ruleguard-scan`, `ruleguard-compile`;local-runner:`branch-create`, `branch-list`, `branch-switch`, `diff-report`, `preflight`, `merge-verified`, `rollback`, `ledger-query` | pure mergeguard 与本地合并闸门分开 | 虚拟分支不会创建 Git 分支/worktree |
34
+ | archguard | `capabilities`, `help`, `intake`, `contract-create`, `contract-get`, `contract-update`, `template-list`, `checkpoint`, `rules-scan`, `complexity-report`, `drift-status`, `ledger-query` | `archguard-*` 本地合同、快照、范围和账本 | 本地写入仍受路径和基线绑定 |
35
+ | confirm-protocol | `capabilities`, `help`, `interaction-request`, `interaction-answer`, `chat-render`, `memory-set`, `memory-list`, `memory-clear`, `batch-request`, `audit-query` | 纯协议操作 | IDE/native/mobile 通知不是本地层能力;高风险交互默认不批准、不记忆、不批量 |
36
+
37
+ ## 本地基线(当前 `LOCAL_OPERATION_SCHEMAS` 100 项)
38
+
39
+ | 分区 | 精确操作名 | 证据位置 |
6
40
  | --- | --- | --- |
7
- | aimlock | capabilities, help, intake, classify, scope-contract, skill-route, propose-nodes, accept-nodes, snapshot-plan, mutate-gate, continuity-check, interrupt, keep-alive, delivery-doc, validate-json, snapshot-verify, run-status, chain-plan, chain-status, feedback | `capabilities/help/intake` → equivalent shared discovery; `classify/skill-route/scope-contract/propose-nodes/accept-nodes/continuity-check/validate-json` → equivalent through `task-describe/message-route`; `snapshot-plan/snapshot-verify/mutate-gate/interrupt/keep-alive/run-status/chain-plan/chain-status/feedback/delivery-doc` → narrower through persisted flow state, local guarded writes, cancellation, status, and evidence gates. |
8
- | blueprint | capabilities, help, intake, validate, compile-inline, acceptance-report | `capabilities/help/intake/validate/compile-inline/acceptance-report` → equivalent through `blueprint` runtime; acceptance evidence is caller supplied and cannot be self-promoted to verified. |
9
- | calctool | capabilities, help, intake, validate, compile-inline | `capabilities/help/intake/validate/compile-inline` → equivalent through `calctool`; `formula-evaluate/formula-graph/formula-final-gate` → local Decimal execution and final-gate evidence. |
10
- | swarm | capabilities, help, intake, org-chart, blueprint-bridge, dispatch, claim, report, accept, swarm-status, traffic-light, security-check, validate-json, heartbeat, reclaim | `capabilities/help/intake/org-chart/blueprint-bridge/dispatch/claim/report/accept/swarm-status/traffic-light/security-check/validate-json/heartbeat/reclaim` → narrower through server-routed coordination, bounded worker counts, leases, and persisted reported/verified states. |
11
- | validator | capabilities, help, intake, plan, validate-structure, security-scan, compliance-audit, functional-verify, verdict | All nine → equivalent through the `validator` runtime; `functional-verify` requires API-side receipt verification and `verdict` cannot turn reported evidence green. |
12
- | mergeguard | capabilities, help, intake, resolve-propose, ruleguard-scan, ruleguard-compile | All six → equivalent through `mergeguard`; `merge-verified` remains a local operation and fails closed without a valid baseline, evidence, and receipt. |
13
- | archguard | capabilities, help, intake, contract-create, contract-get, contract-update, template-list, checkpoint, rules-scan, complexity-report, drift-status, ledger-query | All twelve → equivalent through `archguard`; contract writes and ledger records are local and path/baseline bound. |
14
- | confirm-protocol | capabilities, help, interaction-request, interaction-answer, chat-render, memory-set, memory-list, memory-clear, batch-request, audit-query | All ten → equivalent for pure protocol operations; native/mobile notifier operations remain explicitly planned and are returned as unavailable. High-risk interaction keeps `default: null`, no memory, and no batch approval. |
15
-
16
- The local execution surface also covers `snapshot-plan`, `snapshot-verify`, `mutate-gate`, `gate-issue`, `gate-verify`, `guarded-write`, `budget-init`, `budget-context`, `budget-read`, `budget-status`, `budget-extend`, `budget-auto-renew-request`, `budget-auto-renew`, `budget-auto-renew-stop`, `peer-coordinate`, `spawn-bind`, `peer-complete`, `complete`, `lease-verify`, `evidence-sign`, `evidence-verify`, `merge-verified`, `rollback`, `final-gate`, `formula-evaluate`, `formula-graph`, `formula-final-gate`, `calculation-final-gate`, `run-approved-plan`, `branch-create`, `branch-list`, `branch-switch`, `diff-report`, `preflight`, `ledger-query`, `chain-init`, `chain-resume`, `chain-status`, `chain-answer`, `branch-create-virtual`, `chain-ledger-init`, `chain-ledger-status`, `chain-ledger-query`, `probe`, `reassess`, `task-describe`, `message-route`, `message-accept`, and `task-resume`. Local branch operations are virtual ledger records and never create Git branches or worktrees.
17
-
18
- The only intentionally uncovered legacy surface is an old notifier or an operation that would execute remote filesystem changes. These are represented as planned or narrowed behavior in capabilities; no adapter may silently substitute a different operation.
41
+ | 核心 discovery、快照、门控、预算、协调、证据、链、任务路由(54) | `capabilities`, `help`, `snapshot-plan`, `snapshot-verify`, `mutate-gate`, `gate-issue`, `mutation-snapshot-attest`, `gate-verify`, `guarded-write`, `budget-init`, `budget-handoff`, `budget-context`, `budget-read`, `budget-status`, `budget-extend`, `budget-transition`, `peer-coordinate`, `coordinate`, `spawn-bind`, `peer-complete`, `complete`, `lease-verify`, `evidence-sign`, `evidence-verify`, `rollback`, `final-gate`, `formula-evaluate`, `formula-graph`, `formula-final-gate`, `calculation-final-gate`, `run-approved-plan`, `branch-create`, `branch-create-virtual`, `branch-list`, `branch-switch`, `diff-report`, `preflight`, `ledger-query`, `chain-ledger-query`, `chain-init`, `chain-ledger-init`, `chain-resume`, `chain-status`, `chain-ledger-status`, `chain-answer`, `budget-auto-renew-request`, `budget-auto-renew`, `budget-auto-renew-stop`, `probe`, `reassess`, `task-describe`, `message-route`, `message-accept`, `task-resume` | `packages/entrorove-skill/local-contracts.mjs:14-114` |
42
+ | ArchGuard 本地适配(17) | `archguard-capabilities`, `archguard-help`, `archguard-intake`, `archguard-template-list`, `archguard-contract-create`, `archguard-contract-get`, `archguard-contract-update`, `archguard-snapshot`, `archguard-checkpoint`, `archguard-scope-request`, `archguard-rules-scan`, `archguard-complexity-report`, `archguard-drift-status`, `archguard-ledger-init`, `archguard-ledger-query`, `archguard-human-resume`, `archguard-human-direction-request` | `local-archguard-contracts.mjs:21`、`local-contracts.mjs:113` |
43
+ | Calctool 本地适配(24) | `calctool-run`, `calctool-compile`, `calctool-generate`, `calctool-compile-tool`, `calctool-verify`, `calctool-final-gate`, `calctool-estimate`, `calctool-impact`, `calctool-status`, `calctool-inventory`, `calctool-purge`, `calctool-probe-env`, `calctool-adapt-config`, `calctool-intake-round`, `calctool-brain-handshake`, `calctool-brain-invoke`, `calctool-swarm-orchestrate`, `calctool-auto-pipeline`, `calctool-brain-status`, `calctool-brain-resume`, `calctool-brain-complete`, `calctool-brain-events`, `calctool-brain-cancel`, `calctool-blueprint-orchestrate` | `local-calctool-contracts.mjs:4-42`、`local-contracts.mjs:113` |
44
+ | 合并账本本地适配(4) | `merge-verified`, `merge-freeze`, `merge-preflight`, `merge-journal-head` | `local-contracts.mjs:113`、`local-merge-contracts.mjs` |
45
+ | 工作流本地适配(1) | `workflow-run-step` | `workflow-local-contracts.mjs`、`local-contracts.mjs:64` |
46
+
47
+ 本地分区合计 `54 + 17 + 24 + 4 + 1 = 100`。`capabilities`/`help` 也计入 schema,因为它们是本地 CLI 可发现合同的一部分;不会被从分母中悄悄删除。
48
+
49
+ ## 十条核心保证对账(AC-21)
50
+
51
+ | 保证 | 仓库兑现者与状态 | 已跑绿测试 | 已发布版本 |
52
+ | --- | --- | --- | --- |
53
+ | 签名通行证 | **部分满足**:`local-mutation-authority.mjs` 拒绝过期/伪造签名;正向 signer 夹具仍缺 | `item14-mutation-authority.test.mjs`(拒绝路径) | 0.1.25 未验证 |
54
+ | 冻结基线 | **已满足**:snapshot receipt、handoff baseline、scope digest 绑定 | `item14-snapshot-integrity.test.mjs` | 0.1.25 未验证 |
55
+ | 回执验签 | **已满足**:`local-evidence-verification.mjs` 校验绑定、签名和有效期 | `item11-evidence-verification.test.mjs` | 0.1.25 未验证 |
56
+ | reported 不升 verified | **已满足**:final-gate 保持 reported/incomplete/verified 分离 | `item16-final-gate-cli.test.mjs` C21-CLI07/09 | 0.1.25 未验证 |
57
+ | 同规则三次红灯中断 | **未确认**:中断逻辑存在,合法 signer 夹具缺失 | `item17-archguard-generation.test.mjs` 仅覆盖生成器,不覆盖 signer 链 | 0.1.25 未验证 |
58
+ | 豁免强制审批与到期 | **已满足**:RuleGuard exemption 校验 actor、路径和 expiry | `item12-ruleguard-exemption-unit.test.ts` RGE01/RGE03 | 0.1.25 未验证 |
59
+ | 预算三轴 | **部分满足**:文件、token、时长字段各有实现,但缺三轴同一正向矩阵 | `item07-budget.test.mjs` B07-06/08 | 0.1.25 未验证 |
60
+ | 禁递归派生 | **部分满足**:`local-autocoord17-peers.mjs` 拒绝递归绑定;专门正反例未成套 | 读码证据,不计完整绿测 | 0.1.25 未验证 |
61
+ | 全面 fail-closed | **部分满足**:错误、过期、无回执路径阻断;“全面”矩阵未完成 | `item11-evidence-verification.test.mjs` I11-L06、`item16-final-gate-cli.test.mjs` C21-CLI09 | 0.1.25 未验证 |
62
+ | 仅 GET 恢复 | **已满足**:transport/status 只读恢复原 requestId,不重发 POST | `item18-transport.test.mjs` C04、`item17-local-chain-controller.test.mjs` LC17-EX02/03 | 0.1.25 未验证 |
63
+
64
+ ## 五个疑点(AC-22)
65
+
66
+ | 疑点 | 结论 | 证据与理由 |
67
+ | --- | --- | --- |
68
+ | `peer-status` | **缺失(开单)** | `statusLocalPeer17` 只在 `local-autocoord17-controller.mjs` 内部存在,未进入 `local-cli.mjs`/公开 schema;内部函数不算交付能力。 |
69
+ | `interrupt` | **已实现(仓库)** | `assets/aimlock-runtime-v7.0.44.mjs` operation catalog 与 policy 均有;发布件需 clean-install 后复测。 |
70
+ | `delivery-doc` | **已实现(仓库)** | 同一 aimlock runtime/source 有 operation、schema、entry;发布面保持未验证。 |
71
+ | calctool `research` | **有意不独立公开(理由)** | `calctool-runtime-v7.0.43.mjs` 将其作为 pipeline 内部 partition kind;它没有独立请求合同,拆成公开 operation 会重复 intake/验证边界。若需求改为独立调用,再开变更。 |
72
+ | aimlock 自有 `brain plan/check/validate/status` | **缺失(开单)** | 现有 `brain-*` 只在 calctool contract/runtime;aimlock 只有 chain/run-status,没有书面迁移或归属合同。 |
73
+
74
+ ## 发布与校验边界
75
+
76
+ 本文件记录的是仓库对账,不代表 npm 发布件已验证。`@entrorove/skill@0.1.25` 必须在干净目录安装后重新执行对应 AC,未执行项单独列出,不能计入通过。生产服务、数据库和其他项目不属于本表的运行时依赖。
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.28 — 2026-10-09
4
+
5
+ - 更新了协同执行与验证隔离能力、协同执行与验证隔离能力、协同执行与验证隔离能力、协同执行与验证隔离能力、协同执行与验证隔离能力、协同执行与验证隔离能力、协同执行与验证隔离能力、协同执行与验证隔离能力、安装包配置、自动版本与更新日志、统一本地错误信封。
6
+ - Updated coordination execution and validator isolation capabilities, coordination execution and validator isolation capabilities, coordination execution and validator isolation capabilities, coordination execution and validator isolation capabilities, coordination execution and validator isolation capabilities, coordination execution and validator isolation capabilities, coordination execution and validator isolation capabilities, coordination execution and validator isolation capabilities, package configuration, automatic versioning and changelog, unified local error envelopes.
7
+
8
+ ## 0.1.27 — 2026-10-08
9
+
10
+ - 更新了IDE 工作流程说明、能力覆盖映射、Archguard 架构合同与执行守卫、工作流原执行请求恢复。
11
+ - Updated IDE workflow instructions, capability coverage map, Archguard architecture contract and execution guard, workflow original execution request recovery.
12
+
13
+ ## 0.1.26 — 2026-10-07
14
+
15
+ - 更新了能力覆盖映射、安装包配置。
16
+ - Updated capability coverage map, package configuration.
17
+
18
+ ## 0.1.25 — 2026-10-07
19
+
20
+ - 原链本地执行与固定Git计费收口,补齐macOS路径和发布夹具
21
+ - Close original-chain local execution and fixed Git accounting, with macOS path and release fixture coverage
22
+
3
23
  ## 0.1.24 — 2026-10-07
4
24
 
5
25
  - 更新已验收能力并完成发布前校验。
package/SKILL.md CHANGED
@@ -5,10 +5,14 @@ description: An engineering skill that understands a task, chooses a fitting wor
5
5
 
6
6
  # entrorove · 熵巡
7
7
 
8
- Version: 0.1.24.
8
+ Version: 0.1.28.
9
9
 
10
10
  Read the user's goal and repository instructions first. Preserve the original task through planning, execution and verification. Use the workflow only where it helps; a small, low-risk edit can proceed directly. Never claim a reported step is a verified result.
11
11
 
12
+ ## Enforcement boundary (§2.5)
13
+
14
+ Entrorove governs only actions that pass through its skill entry points. It can bind paths, passes, snapshots, and evidence for those actions, but it does not control direct shell or operating-system side effects run by an agent or its host IDE, such as `docker rm`, `DROP DATABASE`, `cp -r`, or arbitrary file writes. Bypassing the skill may be recorded, rejected at delivery, or require the work to be redone; a bypass side effect never receives `verified` status and never counts as verified delivery. In the contract's terms: entrorove 管得住“走它的动作”,管不住“不走它的动作”。
15
+
12
16
  Install or update the managed copy with `npx @entrorove/skill install`. The installer keeps one global managed entrorove skill, replaces its older pristine version, removes only pristine duplicate installations, and preserves hand-edited files. An older package cannot overwrite a newer managed version.
13
17
 
14
18
  Register or sign in by email at https://entrorove.com. Enter the account key once with `npx @entrorove/skill configure` and `{"token":"..."}` on standard input. The credential persists in the current OS user's restricted local store across skill upgrades. Before asking again, use the stored credential; a missing or invalid credential must be reported explicitly. Never place the key in arguments, URLs, project files or logs. entrorove accounts and keys are independent of other services.
@@ -21,6 +25,10 @@ On first use or after a material agreement revision, the CLI returns a Confirm P
21
25
 
22
26
  Send JSON on standard input to `npx @entrorove/skill invoke`. Before every execution call, discover `operation: "capabilities"` with `{}` and validate input against the returned operation schema. The CLI performs remote discovery automatically; local adapters discover the installed contract before execution. Discovery failure stops execution. Adapters that expose the four-step task route can call `task-describe`, `message-route`, `message-accept` and `task-resume` in order; `start` remains the one-call workflow equivalent. Then start with `operation: "start"` and an `input.task` containing the goal, work type, risk, difficulty and observed scope facts. Work types are `implementation`, `documentation`, `calculation`, `merge`, `review`, `decision` and `diagnosis`. Assess facts from the real task and repository; do not invent certainty. entrorove chooses the workflow. A small task may return `decision: "direct"`, meaning the IDE should do the work without another service call. The public envelope is `entrorove.skill.request/2.0`; `requestId` is the SHA-256 digest of `operation + canonical(input)`. Version 1.0 remains accepted for older callers.
23
27
 
28
+ ### Per-task cloud sandbox choice
29
+
30
+ When the host exposes an enabled cloud-sandbox preference and a new task is about to dispatch a cloud model, ask once whether this task should use the cloud sandbox. This is a low-risk routing preference, separate from agreement acceptance and the medium/high-risk confirmations below. Offer `use for this task` and `continue locally`; if the user gives no answer, use the cloud sandbox by the host's stated timeout default. An explicit opt-out applies only to the current task and must not change the account-wide sandbox preference. Do not ask again while continuing or resuming the same task. If the account-wide preference is disabled or unavailable, do not dispatch a cloud task and do not show a misleading per-task prompt; preserve the normal local workflow and report the prerequisite clearly. This choice never bypasses the normal task schema, mutation authority, evidence checks, or risk confirmation.
31
+
24
32
  ```json
25
33
  {"operation":"start","input":{"task":{"goal":"Update the checkout flow and verify it","workType":"implementation","risk":"medium","difficulty":"medium","crossModule":true,"parallel":false,"unclear":false,"architectureContract":false}}}
26
34
  ```
@@ -39,6 +47,8 @@ Use `npx @entrorove/skill status <requestId>` after a connection loss. It perfor
39
47
 
40
48
  The local execution layer is available through `npx @entrorove/skill local <operation> [repositoryRoot]` with a JSON object on standard input. `local capabilities` lists the complete contract. Operations include `snapshot-plan`, `snapshot-verify`, `mutate-gate`, `gate-issue`, `gate-verify`, `guarded-write`, `budget-init`, `budget-context`, `budget-read`, `budget-status`, `budget-extend`, budget renewal confirmation, `peer-coordinate`, `spawn-bind`, `peer-complete`, `lease-verify`, `probe`, `reassess`, execution ledger and virtual branch records, `chain-init`, `chain-resume`, `chain-status`, `chain-answer`, `evidence-sign`, `evidence-verify`, `merge-verified`, `rollback`, `formula-evaluate`, `formula-graph`, `formula-final-gate`, `run-approved-plan` and `final-gate`. State is kept only under `~/.entrorove/`; snapshots never create Git branches or worktrees. Guarded writes require a short-lived Ed25519 pass bound to the flow, step, snapshot digest and path set. Local evidence uses `entrorove.test-evidence/2.0`; unsigned local evidence remains incomplete and verified delivery requires a trusted receipt. Approved plan execution requires a frozen plan, matching approval and unchanged files; without a configured runner signer it reports local evidence as incomplete.
41
49
 
50
+ Local CLI failures use the machine-readable `entrorove.local-error/1.0` envelope `{schemaVersion,status,code,message}` on stderr. `status` is `failed` for invalid input or computation errors and `blocked` for missing mutation, lease, snapshot or handoff authority. Formula `add` and `mul` require at least one argument; `div`, `safeDivide` and `percentOf` reject a zero denominator with `DIV_ZERO`. The formula engine declares `round` as half-away-from-zero with two decimal places by default; an engine may declare another `decimalPlaces` value from 0 through 28 in its `roundingPolicy`.
51
+
42
52
  When the user gives an actual evaluation after using the skill, offer to publish that feedback. Only after they explicitly agree to public display under the current notice, send their unchanged words, IDE name and 1–5 rating as JSON on standard input to `npx @entrorove/skill review`, including `invocationRequestId` from their actual successful call, `publicConsent: true` and `noticeVersion: "2026-10-07.1"`. The same account must own that successful call within the last 30 days; a recorded usage event, failed call or unknown result does not qualify. Three public reviews per account per day share one server quota across API replicas. Public output exposes only IDE, rating, original text and time, never account or invocation identity. Never invent, rewrite or infer a review from task completion. The website reads accepted reviews directly from entrorove's independent database.
43
53
 
44
54
  Feedback command example (replace the request ID with the original successful invocation ID, and use only the user’s actual words after explicit public consent):
@@ -1,6 +1,6 @@
1
1
  import { buildFormulaEngine, compileFormulaEngine, validateFormulaEngine, formulaDigest } from './local-formula-engine.mjs';
2
2
  import { formulaError } from './local-decimal.mjs';
3
- import { FORMULA_OPERATORS } from './local-formula-ast.mjs';
3
+ import { FORMULA_OPERATORS, FORMULA_ROUNDING_POLICY } from './local-formula-ast.mjs';
4
4
 
5
5
  const CLOUD_OPERATIONS = ['capabilities', 'help', 'intake', 'validate', 'compile-inline'];
6
6
  const LOCAL_OPERATIONS = ['run', 'compile', 'generate', 'verify', 'estimate', 'impact', 'status', 'inventory', 'purge',
@@ -30,7 +30,7 @@ export function createCalctoolEntrypoint(core) {
30
30
  const output = await core.run(request, options);
31
31
  return { ...output, capabilities: { ...output.capabilities, operations: CLOUD_OPERATIONS, localRunnerOperations: LOCAL_OPERATIONS,
32
32
  formulaOperators: FORMULA_OPERATORS, engineSchema: 'engine.spec/1', formulaRuntime: 'decimal28-ast/1.0',
33
- decimalPolicy: { precision: 28, rounding: 'half-away-from-zero', representation: 'decimal-string' } },
33
+ decimalPolicy: { precision: 28, rounding: 'half-away-from-zero', representation: 'decimal-string' }, roundingPolicy: FORMULA_ROUNDING_POLICY },
34
34
  localRunner: { command: 'entrorove local calctool-<operation>', requiredFor: LOCAL_OPERATIONS } };
35
35
  }
36
36
  if (request.operation === 'validate') {
package/cli.mjs CHANGED
@@ -288,12 +288,13 @@ async function cleanProjectDuplicates(target, cwd) {
288
288
  export async function install({ home = os.homedir(), cwd = process.cwd() } = {}) {
289
289
  const bundle = await sourceBundle();
290
290
  const target = globalInstallPath(home);
291
+ const stale = await agreementStale(home);
291
292
  const parent = path.dirname(target);
292
293
  await fs.mkdir(parent, { recursive: true, mode: 0o700 });
293
294
  return withInstallLock(parent, async () => {
294
295
  const updated = await updateGlobalInstall(target, bundle);
295
296
  const removed = await cleanProjectDuplicates(target, cwd);
296
- return { version: bundle.manifest.version, updated, removed };
297
+ return { version: bundle.manifest.version, updated, removed, agreementStale: stale };
297
298
  });
298
299
  }
299
300
 
@@ -330,6 +331,22 @@ async function agreementAccepted(home) {
330
331
  && Number.isFinite(Date.parse(data.acceptedAt));
331
332
  }
332
333
 
334
+ async function agreementStale(home) {
335
+ const file = agreementPath(home);
336
+ const stat = await statOrNull(file);
337
+ if (!stat) return false;
338
+ if (!stat.isFile() || stat.isSymbolicLink()) return true;
339
+ if (process.platform !== 'win32' && (stat.mode & 0o077) !== 0) return true;
340
+ try {
341
+ const data = await readJson(file);
342
+ return data.schemaVersion !== agreementSchema || data.version !== agreementVersion
343
+ || data.answer !== 'accept' || typeof data.acceptedAt !== 'string'
344
+ || !Number.isFinite(Date.parse(data.acceptedAt));
345
+ } catch {
346
+ return true;
347
+ }
348
+ }
349
+
333
350
  async function writeAgreementAcceptance(data, home) {
334
351
  if (Object.keys(data).sort().join(',') !== 'answer,version'
335
352
  || data.version !== agreementVersion || !['accept', 'decline'].includes(data.answer)) {
@@ -11,11 +11,14 @@ import { signRunnerReceipt } from './local-archguard-signing.mjs';
11
11
 
12
12
  async function verifyWriteAuthorization(ctx, input, snapshot) {
13
13
  const savedPass = await readJson(input.gatePassPath);
14
- const verified = await verifyMutationPass({ ...input, repositoryRoot: ctx.root, targetPath: input.path });
14
+ const verified = await verifyMutationPass({ ...input, repositoryRoot: ctx.root, targetPath: input.path }, { allowTargetDrift: true });
15
15
  const pass = verified.pass;
16
16
  if (canonical(pass) !== canonical(savedPass) || canonical(await readJson(input.gatePassPath)) !== canonical(savedPass)) fail('ARCH_PASS_RACE', 'gate pass changed during verification');
17
17
  if (pass.snapshotId !== snapshot.snapshotId || pass.snapshotReceipt.snapshotDigest !== snapshot.snapshotDigest
18
- || pass.snapshotReceipt.contractDigest !== snapshot.contractDigest || pass.stepId !== snapshot.stepId
18
+ // The cloud pass binds the original Aimlock path contract, while the
19
+ // ArchGuard snapshot binds its independent architecture contract. The
20
+ // signed scope authorization below binds both digests before a write.
21
+ || pass.stepId !== snapshot.stepId
19
22
  || canonical(pass.paths) !== canonical([...snapshot.paths, 'arch.contract.yaml'].sort())) fail('ARCH_PASS_BINDING', 'gate pass is not bound to the exact snapshot and path set');
20
23
  const verifiedScope = await verifyAuthority(ctx, input.scopeAuthorization, 'entrorove.archguard-scope-authorization/1.0');
21
24
  const scope = verifiedScope.value;
@@ -6,7 +6,7 @@ import { snapshotPlan, snapshotVerify } from './local-execution.mjs';
6
6
  import { context, safeId, safePath, noLinks, canonical, sha256, readBytes, readJson,
7
7
  atomicJson, seal, unseal, withExclusiveLock, appendLedger, readLedger, fail } from './local-archguard-state.mjs';
8
8
  import { freezeContract, loadContractLock } from './local-archguard-contract.mjs';
9
- import { readBudgetedProjectFile } from './local-budget-reads.mjs';
9
+ import { readSnapshotCopy14 } from './local-mutation-snapshot.mjs';
10
10
 
11
11
  export const MISSING_SHA256 = sha256('entrorove.missing-file/1.0');
12
12
  export function sourcePaths(paths) {
@@ -27,16 +27,15 @@ export function drift(ledger) {
27
27
  interrupted: shared.length > 0, ruleIds: shared, consecutiveBlocks: shared.length ? 3 : 0 };
28
28
  }
29
29
  function snapshotFile(ctx, snapshotId) { return path.join(ctx.directory, 'snapshots', `${safeId(snapshotId, 'snapshotId')}.json`); }
30
- async function cachedSource(ctx, snapshotId, file) {
31
- const target = path.join(ctx.home, '.entrorove', 'snapshots', ctx.repositoryDigest, ctx.flowId, safeId(snapshotId, 'snapshotId'), safePath(file.path));
30
+ async function cachedSource(ctx, snapshotId, file, copyLayout) {
31
+ const snapshotRoot = path.join(ctx.home, '.entrorove', 'snapshots', ctx.repositoryDigest, ctx.flowId, safeId(snapshotId, 'snapshotId'));
32
+ const copyRoot = copyLayout === 'files/1.0' ? path.join(snapshotRoot, 'files') : snapshotRoot;
33
+ const target = path.join(copyRoot, safePath(file.path));
32
34
  await noLinks(target, !file.exists);
33
- if (!file.exists) {
34
- try { await fs.lstat(target); fail('ARCH_SNAPSHOT_TAMPERED', 'a missing-file snapshot acquired source bytes'); }
35
- catch (error) { if (error.code !== 'ENOENT') throw error; }
36
- return null;
37
- }
38
- const bytes = await readBudgetedProjectFile({ home: ctx.home, repositoryRoot: ctx.root, flowId: ctx.flowId }, target, { sourcePath: file.path });
39
- await noLinks(target);
35
+ const manifest = { paths: [safePath(file.path)], files: [file], ...(copyLayout === undefined ? {} : { copyLayout }) };
36
+ const read = await readSnapshotCopy14({ home: ctx.home, repositoryRoot: ctx.root, flowId: ctx.flowId }, snapshotRoot, manifest, file.path);
37
+ if (!read.exists) return null;
38
+ const bytes = read.content;
40
39
  if (sha256(bytes) !== file.sha256) fail('ARCH_SNAPSHOT_TAMPERED', 'native snapshot source digest changed');
41
40
  return bytes;
42
41
  }
@@ -46,7 +45,8 @@ export async function loadSnapshot(ctx, snapshotId) {
46
45
  if (snapshot.repositoryDigest !== ctx.repositoryDigest || snapshot.flowId !== ctx.flowId || snapshot.snapshotId !== snapshotId
47
46
  || snapshot.contentDigest !== sha256(canonical(snapshot.files))) fail('ARCH_SNAPSHOT_TAMPERED', 'snapshot is not bound to the repository or content');
48
47
  const files = [];
49
- for (const file of snapshot.files) { const bytes = await cachedSource(ctx, snapshotId, file); files.push({ ...file, content: bytes?.toString('base64') ?? null }); }
48
+ const copyLayout = snapshot.receipt?.copyLayout;
49
+ for (const file of snapshot.files) { const bytes = await cachedSource(ctx, snapshotId, file, copyLayout); files.push({ ...file, content: bytes?.toString('base64') ?? null }); }
50
50
  return { ...snapshot, files };
51
51
  }
52
52
  export async function archguardSnapshot(input) {
@@ -62,8 +62,13 @@ export async function archguardSnapshot(input) {
62
62
  files.push({ path: relative, exists: value.exists, content: value.exists ? value.content.toString('base64') : null,
63
63
  sha256: value.exists ? sha256(value.content) : MISSING_SHA256, mode: value.mode });
64
64
  }
65
+ // The native ArchGuard record is bound to the architecture contract, while
66
+ // its mutation receipt must remain bound to the original Aimlock scope.
67
+ // Keep those digests separate when a cloud mutation challenge supplies the
68
+ // already verified path contract; legacy local calls retain the native
69
+ // architecture digest.
65
70
  const native = await snapshotPlan({ ...input, repositoryRoot: ctx.root, paths: files.map(file => file.path), snapshotId,
66
- contractDigest: current.contractDigest });
71
+ contractDigest: input.scopeContractDigest ?? current.contractDigest });
67
72
  const verified = await snapshotVerify({ ...input, repositoryRoot: ctx.root, snapshotId });
68
73
  for (const file of files) {
69
74
  const actual = await readBytes(path.join(ctx.root, file.path), true, ctx);
@@ -99,7 +104,7 @@ export async function restoreSnapshot(ctx, snapshot) {
99
104
  }
100
105
  const temporary = `${target}.${randomUUID()}.rollback`;
101
106
  if (file.exists) {
102
- const bytes = await cachedSource(ctx, snapshot.snapshotId, file);
107
+ const bytes = await cachedSource(ctx, snapshot.snapshotId, file, snapshot.receipt?.copyLayout);
103
108
  if (file.content !== bytes.toString('base64')) fail('ARCH_SNAPSHOT_TAMPERED', 'snapshot content does not match its budgeted native copy');
104
109
  await fs.writeFile(temporary, bytes, { flag: 'wx', mode: file.mode });
105
110
  }
@@ -0,0 +1,36 @@
1
+ import { canonical, sha256 } from './local-integrity.mjs';
2
+ import { readSwarmCloudAuthority, readSignedSwarmMaterial } from './local-swarm-authority.mjs';
3
+ import { readTaskHandoffFence } from './local-task-handoff-fence.mjs';
4
+ import { coordFail17 } from './local-autocoord17-store.mjs';
5
+ import { createTaskMessageClient } from './remote-client.mjs';
6
+
7
+ export async function coordNative17(material, live = true) {
8
+ if (live) return readSwarmCloudAuthority(material, 'issue');
9
+ const verified = readSignedSwarmMaterial(material, 'report'), fence = await readTaskHandoffFence(material);
10
+ const attempt = fence.executionAttempts.find(value => value.authority.attemptNonce === material.executionAttemptNonce);
11
+ if (!attempt || canonical(attempt.authority) !== canonical(material.executionAuthority) || attempt.executionHome !== material.home
12
+ || ['flowId', 'taskId', 'ownerId', 'taskDigest', 'chainDigest', 'runnerKeyId'].some(key => attempt.authority[key] !== verified.grant[key])) {
13
+ coordFail17('ENTROROVE_COORD_AUTHORITY', 'coordination requires its exact original native registered authority');
14
+ }
15
+ return { ...verified, fence, attempt, root: material.repositoryRoot, home: material.home,
16
+ repositoryDigest: sha256(material.repositoryRoot).slice(0, 32), paths: verified.grant.paths,
17
+ active: fence.status === 'active' && attempt.status !== 'revoked' && !fence.revokedLeaseIds.includes(verified.lease.leaseId) };
18
+ }
19
+ export function coordTask17(state, input) {
20
+ const task = state.tasks.find(value => value.taskId === input.taskId);
21
+ if (!task || input.agentId !== task.agentId || input.chainId !== task.chainId) {
22
+ coordFail17('ENTROROVE_COORD_TASK_BINDING', 'operation must retain its original task, agent and chain');
23
+ }
24
+ return task;
25
+ }
26
+ export async function coordCloud17(material, dependencies) {
27
+ const configured = dependencies ?? (await import('./cli.mjs')).remoteDependencies(material.home);
28
+ const client = createTaskMessageClient(configured), output = await client.invoke('task-resume', { flowId: material.flowId });
29
+ const task = output.task, grant = material.delegationGrant;
30
+ if (output.flowId !== material.flowId || !task || task.flowId !== material.flowId || task.taskId !== grant.taskId
31
+ || task.ownerId !== grant.ownerId || task.taskDigest !== grant.taskDigest || !task.task || typeof task.task.goal !== 'string'
32
+ || !['accepted', 'active'].includes(task.status) || output.continuity?.handoffPaused || output.continuity?.activeTakeover) {
33
+ coordFail17('ENTROROVE_COORD_CLOUD_BINDING', 'original authenticated cloud task is unavailable or changed');
34
+ }
35
+ return { originalTask: structuredClone(task.task), apiOrigin: new URL(configured.apiOrigin()).origin };
36
+ }
@@ -0,0 +1,164 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import { canonical } from './local-integrity.mjs';
3
+ import { peerCoordinate, completePeer } from './local-swarm.mjs';
4
+ import { readSwarmCloudAuthority } from './local-swarm-authority.mjs';
5
+ import { canonicalSwarmPaths } from './local-swarm-state.mjs';
6
+ import { coordNative17, coordCloud17, coordTask17 } from './local-autocoord17-authority.mjs';
7
+ import { coordDuration17, coordExact17, coordFail17, coordEvent17, readCoordination17, withCoordination17 } from './local-autocoord17-store.mjs';
8
+ import { registerTask17, taskConflicts17, enqueueResource17 } from './local-autocoord17-tasks.mjs';
9
+ import { bindSpawn17, physicalPeers17, registerIntent17, scanIntent17 } from './local-autocoord17-peers.mjs';
10
+ import { cancelWait17, coordinationBlocked17, registerWait17, scanQueues17, scanWaits17 } from './local-autocoord17-waits.mjs';
11
+
12
+ const IDENTITY = ['repositoryRoot', 'coordinationHome', 'taskId', 'agentId', 'chainId'];
13
+ function input17(input, fields = [], optional = []) {
14
+ coordExact17(input, [...IDENTITY, ...fields], optional);
15
+ try { return structuredClone(input); } catch { coordFail17('ENTROROVE_COORD_INPUT', 'coordination input must be finite original JSON'); }
16
+ }
17
+ async function storageBinding(input) {
18
+ if (typeof process.env.ENTROROVE_SWARM_COORDINATION_HOME !== 'string'
19
+ || await fs.realpath(process.env.ENTROROVE_SWARM_COORDINATION_HOME) !== await fs.realpath(input.coordinationHome)) {
20
+ coordFail17('ENTROROVE_COORD_PHYSICAL_HOME', 'the original physical Swarm coordination home must be explicitly retained');
21
+ }
22
+ }
23
+ async function scan17(loaded) {
24
+ const state = loaded.state, newDecisions = scanQueues17(state);
25
+ for (const task of state.tasks) {
26
+ const native = await coordNative17(task.material, false);
27
+ if (!native.active || Date.parse(native.grant.expiresAt) <= Date.now()) {
28
+ coordEvent17(state, 'lease-expired', task.taskId, native.lease.leaseId, { leaseId: native.lease.leaseId }, native.lease.leaseId);
29
+ }
30
+ const physical = await physicalPeers17(task);
31
+ for (const intent of state.intents.filter(value => value.taskId === task.taskId)) scanIntent17(state, task, intent, physical);
32
+ }
33
+ scanWaits17(state); return newDecisions;
34
+ }
35
+ function taskView(loaded, task) {
36
+ return { schemaVersion: 'entrorove.local-autocoord/1.0', taskId: task.taskId,
37
+ originalTask: structuredClone(task.originalTask), card: structuredClone(task.card),
38
+ conflicts: taskConflicts17(loaded.state, task), ...coordinationBlocked17(loaded.state, task),
39
+ messages: loaded.state.messages.filter(value => value.taskId === task.taskId),
40
+ waits: loaded.state.waits.filter(value => value.taskId === task.taskId),
41
+ decisions: loaded.state.decisions.filter(value => value.taskId === task.taskId),
42
+ queues: loaded.state.lockQueues.filter(value => value.taskId === task.taskId) };
43
+ }
44
+ export async function registerLocalTaskCard17(rawInput, dependencies) {
45
+ const input = input17(rawInput, ['material', 'card']); await storageBinding(input);
46
+ const native = await coordNative17(input.material), cloud = await coordCloud17(input.material, dependencies);
47
+ return withCoordination17(input, 'task-register', loaded => {
48
+ const task = registerTask17(loaded.state, input, native, cloud); return taskView(loaded, task);
49
+ }, true);
50
+ }
51
+ export async function statusLocalTaskCard17(rawInput) {
52
+ const input = input17(rawInput); await storageBinding(input);
53
+ const loaded = await readCoordination17(input), task = coordTask17(loaded.state, input);
54
+ await coordNative17(task.material, false);
55
+ return { ...taskView(loaded, task), sequence: loaded.sequence, head: loaded.head };
56
+ }
57
+ export async function coordinateLocalPeer17(rawInput) {
58
+ const input = input17(rawInput, ['intentId', 'paths', 'baselineHash', 'ttlMs', 'coordinationTimeoutMs', 'fileLockTtlMs', 'priority']);
59
+ await storageBinding(input);
60
+ const loaded = await readCoordination17(input), task = coordTask17(loaded.state, input);
61
+ await coordNative17(task.material);
62
+ // Only the existing signed native entry can persist an engineering write
63
+ // intent. Our additional partial result never reserves or grants a path.
64
+ await peerCoordinate(task.material);
65
+ return withCoordination17(input, 'peer-coordinate', async fresh => {
66
+ const current = coordTask17(fresh.state, input), intent = registerIntent17(fresh.state, current, input);
67
+ return scanIntent17(fresh.state, current, intent, await physicalPeers17(current));
68
+ });
69
+ }
70
+ export async function statusLocalPeer17(rawInput) {
71
+ const input = input17(rawInput, ['intentId']); await storageBinding(input);
72
+ return withCoordination17(input, 'peer-status', async loaded => {
73
+ const task = coordTask17(loaded.state, input), intent = loaded.state.intents.find(value => value.intentId === input.intentId && value.taskId === task.taskId);
74
+ if (!intent) coordFail17('ENTROROVE_COORD_INTENT_BINDING', 'peer status must name its original task intent');
75
+ await coordNative17(task.material, false);
76
+ const view = scanIntent17(loaded.state, task, intent, await physicalPeers17(task));
77
+ return { ...view, spawnRequest: loaded.state.spawnRequests.find(value => value.spawnRequestId === view.spawnRequestId) ?? null };
78
+ });
79
+ }
80
+ export async function completeLocalPeer17(rawInput) {
81
+ const input = input17(rawInput, ['intentId']); await storageBinding(input);
82
+ const loaded = await readCoordination17(input), task = coordTask17(loaded.state, input);
83
+ const intent = loaded.state.intents.find(value => value.intentId === input.intentId && value.taskId === task.taskId);
84
+ if (!intent) coordFail17('ENTROROVE_COORD_INTENT_BINDING', 'completion must name its original task intent');
85
+ const actual = await completePeer(task.material);
86
+ return withCoordination17(input, 'peer-complete', async fresh => {
87
+ const current = fresh.state.intents.find(value => value.intentId === input.intentId && value.taskId === task.taskId);
88
+ current.status = 'completed';
89
+ coordEvent17(fresh.state, actual.status === 'reported' ? 'task-reported' : 'task-failed', task.taskId,
90
+ task.material.lease.leaseId, { status: actual.status, leaseId: task.material.lease.leaseId }, input.intentId);
91
+ await scan17(fresh); return { ...taskView(fresh, coordTask17(fresh.state, input)), actual,
92
+ engineeringVerification: 'original-native-receipt', taskCompletionVerified: false };
93
+ });
94
+ }
95
+ export async function bindLocalPeerSpawn17(rawInput) {
96
+ const input = input17(rawInput, ['spawnRequestId', 'targetTaskId']); await storageBinding(input);
97
+ return withCoordination17(input, 'peer-spawn-bind', async loaded => {
98
+ const source = coordTask17(loaded.state, input), target = loaded.state.tasks.find(value => value.taskId === input.targetTaskId);
99
+ if (target) await coordNative17(target.material);
100
+ return bindSpawn17(loaded.state, source, { spawnRequestId: input.spawnRequestId, targetTaskId: input.targetTaskId });
101
+ });
102
+ }
103
+ export async function waitLocalDependency17(rawInput) {
104
+ const input = input17(rawInput, ['waitId', 'waitFor', 'event', 'expectedWithinMs', 'onArrival', 'onTimeout', 'refetchPaths']);
105
+ await storageBinding(input);
106
+ return withCoordination17(input, 'dependency-wait', async loaded => {
107
+ const task = coordTask17(loaded.state, input); await coordNative17(task.material, false);
108
+ const wait = registerWait17(loaded.state, task, input); scanWaits17(loaded.state);
109
+ return { wait, ...coordinationBlocked17(loaded.state, task) };
110
+ });
111
+ }
112
+ export async function cancelLocalWait17(rawInput) {
113
+ const input = input17(rawInput, ['waitId']); await storageBinding(input);
114
+ return withCoordination17(input, 'wait-cancel', loaded => {
115
+ const task = coordTask17(loaded.state, input), wait = cancelWait17(loaded.state, task, input.waitId);
116
+ return { wait, ...coordinationBlocked17(loaded.state, task) };
117
+ });
118
+ }
119
+ export async function emitLocalCoordSignal17(rawInput) {
120
+ const input = input17(rawInput, ['eventId', 'resource', 'content']); await storageBinding(input);
121
+ if (typeof input.content !== 'string' || !input.content || input.content.length > 4096) coordFail17('ENTROROVE_COORD_EVENT', 'signal content must be explicit bounded text');
122
+ return withCoordination17(input, 'event-signal', async loaded => {
123
+ const task = coordTask17(loaded.state, input); await coordNative17(task.material, false);
124
+ const event = coordEvent17(loaded.state, 'signal', task.taskId, input.resource, { content: input.content }, input.eventId);
125
+ scanWaits17(loaded.state); return { event, executionEvidence: false };
126
+ });
127
+ }
128
+ export async function queueLocalResource17(rawInput) {
129
+ const input = input17(rawInput, ['queueId', 'kind', 'resource', 'paths', 'baselineHash', 'queueTimeoutMs']); await storageBinding(input);
130
+ return withCoordination17(input, 'resource-queue', async loaded => {
131
+ const task = coordTask17(loaded.state, input); await coordNative17(task.material);
132
+ const queue = enqueueResource17(loaded.state, task, input);
133
+ return { queue, allowed: false, writeAuthorized: false, consumerAvailable: false };
134
+ });
135
+ }
136
+ export async function tickLocalCoordination17(rawInput) {
137
+ const input = input17(rawInput, [], ['now']); await storageBinding(input);
138
+ if (input.now !== undefined && !Number.isFinite(Date.parse(input.now))) coordFail17('ENTROROVE_COORD_TIME', 'observed time must be an explicit timestamp');
139
+ return withCoordination17(input, 'tick', async loaded => {
140
+ const task = coordTask17(loaded.state, input); await coordNative17(task.material, false);
141
+ const decisions = await scan17(loaded);
142
+ return { ...taskView(loaded, task), decisions, observedAt: input.now ?? null, scannedAt: new Date().toISOString() };
143
+ });
144
+ }
145
+ export async function waitLocalEvent17(rawInput) {
146
+ const input = input17(rawInput, ['waitId', 'pollTimeoutMs']); coordDuration17(input.pollTimeoutMs, 60_000);
147
+ const deadline = Date.now() + input.pollTimeoutMs;
148
+ while (true) {
149
+ const view = await tickLocalCoordination17(Object.fromEntries(IDENTITY.map(key => [key, input[key]])));
150
+ const wait = view.waits.find(value => value.waitId === input.waitId);
151
+ if (!wait) coordFail17('ENTROROVE_COORD_WAIT_BINDING', 'event host must retain its exact original wait');
152
+ if (wait.status !== 'waiting' || Date.now() >= deadline) return { wait, hostActive: true, ...view };
153
+ await new Promise(resolve => setTimeout(resolve, Math.min(25, Math.max(1, deadline - Date.now()))));
154
+ }
155
+ }
156
+ export async function assertLocalCoordinationAllowed17(rawInput) {
157
+ const input = input17(rawInput, ['paths']); await storageBinding(input);
158
+ const paths = input.paths.length ? canonicalSwarmPaths(input.paths) : [], loaded = await readCoordination17(input);
159
+ const task = coordTask17(loaded.state, input); await readSwarmCloudAuthority(task.material, 'verify');
160
+ const blocked = coordinationBlocked17(loaded.state, task, paths);
161
+ if (paths.some(target => !task.card.taskScope.includes(target))) coordFail17('ENTROROVE_COORD_SCOPE', 'work exceeds its original taskcard');
162
+ if (blocked.blocked) coordFail17('ENTROROVE_COORD_WORK_BLOCKED', 'unresolved coordination blocks this original controlled chain');
163
+ return { ...blocked, restrictiveCheckPassed: true, writeAuthorized: false };
164
+ }