@entrorove/skill 0.1.54 → 0.1.56
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.
- package/CAPABILITY-MAP.md +1 -1
- package/CHANGELOG.md +10 -0
- package/IDE-EXECUTION.md +7 -1
- package/SKILL.md +3 -3
- package/ide-code-mode.mjs +24 -4
- package/ide-command.mjs +30 -1
- package/ide-hook.mjs +30 -18
- package/ide-session.mjs +1 -1
- package/local-approved-plan.mjs +3 -2
- package/local-discipline-contracts.mjs +5 -1
- package/local-runner-discipline.mjs +7 -2
- package/manifest.json +1 -1
- package/package.json +6 -1
- package/release-state.json +17 -17
- package/releases.json +12 -0
- package/scope-runner-client.mjs +8 -2
- package/workflow-local-runner.mjs +2 -2
- package/workflow-observation-connection.mjs +2 -1
- package/workflow-observation-wire.mjs +15 -8
package/CAPABILITY-MAP.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
| --- | --- | --- |
|
|
7
7
|
| 成员受限编辑 | `mutation-connect <repositoryRoot>`、`local mutation-delegate-prepare` | 原任务、冻结修改声明及租约绑定;观察、编辑、独立验证三把密钥分离。运行时依赖保持未知且只读,只允许现存文件的受限修改;不授予通用执行、合并或交付权限 |
|
|
8
8
|
| 连续编辑结算 | `mutation-settle <repositoryRoot>`、`local mutation-consumption-attest`、服务端 `mutation-pass-settle` | 实际写后 M 回执与 fresh S 后验共同确认原 pass;下一轮使用 `previousPassId`,不伪造架构验证事件 |
|
|
9
|
-
| 沙箱关闭后的本地测试 | `workflow-observe-step <repositoryRoot>`、`member-observation-admit/report/status` |
|
|
9
|
+
| 沙箱关闭后的本地测试 | `workflow-observe-step <repositoryRoot>`、`member-observation-admit/report/status` | 仅运行原冻结命令;写入步骤对照已结算源文件,只读步骤对照原 W 计划的签名源文件且无需 M 结算。结果独立保存为 local / pending-independent,不升格为 V 验证或交付 |
|
|
10
10
|
| 续跑入口 | 原 `execution-claim`、原 flow 恢复 | 成员受限范围返回原字段的 `localAction`,客户端串联真实快照、签名、云端通行证及本地安装;网络结果不明时恢复原请求 |
|
|
11
11
|
| 同一阻断停止 | IDE `Stop` 回调 | 真实重复阻断可等待;状态查询及检查点不能重置阻断,宿主重复 Stop 不再生成无限续跑。原 flow、需求队列与恢复数据保留 |
|
|
12
12
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.56 — 2026-10-17
|
|
4
|
+
|
|
5
|
+
- 补齐原工作流显式只读步骤及无写入结算的本地观察,复核真实受检仓库字节与权限;修复流程列表空输入兼容,返回精确规划前置条件与范围错误,并配置 GitHub OIDC 发布。
|
|
6
|
+
- Added explicit read-only workflow steps and local observations without fabricated mutation settlements, checking actual repository bytes and modes; fixed empty-input flow listing, precise planning prerequisite and scope errors, and GitHub OIDC publishing metadata.
|
|
7
|
+
|
|
8
|
+
## 0.1.55 — 2026-10-17
|
|
9
|
+
|
|
10
|
+
- 修复任务间消息被技能误拦截,将已授权通信交回主机审批;规范只读和 SDK 命令执行参数;新增原流程计划阶段的显式范围重评与恢复提示,保留原门禁、合同、请求身份及失败历史。
|
|
11
|
+
- Restored host-approved task messaging, constrained read and SDK execution inputs, and added explicit planning-scope reassessment with phase-correct recovery while retaining original gates, contracts, request identities, and failure history.
|
|
12
|
+
|
|
3
13
|
## 0.1.54 — 2026-10-17
|
|
4
14
|
|
|
5
15
|
- 修复受管 Node 只读 SHA-256 命令在 Codex 工具门禁中的静态识别与安全改写;按已证明的 createRequire 原目录解析实际安装依赖并记录只读元数据,保留未知上下文和越界拒绝。
|
package/IDE-EXECUTION.md
CHANGED
|
@@ -6,6 +6,12 @@ If the IDE guard itself is the blocked step, run `entrorove unlock` directly fro
|
|
|
6
6
|
|
|
7
7
|
Capability discovery and offline unlock remain administrative calls during `entry`, before a flow exists. The guard recognizes its own rendered commands with quoted identity assignments, their order changed, or a literal `env` prefix containing only supported host/session identities; it rewrites approved calls to the installed runtime and current session. This does not authorize native scripts or unbound project writes.
|
|
8
8
|
|
|
9
|
+
`flows` is a read-only account query with no stdin. For host compatibility, the guard accepts a literal empty `{}` here-doc on `flows` and removes it from the bound command; any nonempty input remains rejected.
|
|
10
|
+
|
|
11
|
+
Approved Codex `exec_command` reads and SDK calls run with `/bin/sh` and `login:false`; static code mode uses the same execution input check. Its noninteractive workdir and output controls pass through. Other hosts keep their adapter's command schema, including timeout where supported, with custom executors and permission expansion rejected.
|
|
12
|
+
|
|
13
|
+
For a Codex task-to-task report explicitly authorized by the human user, the guard passes the exact `mcp__codex_app__send_message_to_thread` tool through unchanged, including a static `tools.mcp__codex_app__send_message_to_thread({...})` call inside `functions.exec`. Codex still decides whether the user authorized the message. Its text and response grant no Entrorove flow, file, shell or task-creation authority.
|
|
14
|
+
|
|
9
15
|
To stop using Entrorove on this computer, run `entrorove uninstall` directly. It works offline, removes only the recorded Entrorove host hooks and pristine global skill, and leaves other host settings, credentials and task records in place. A hand-edited file is reported as preserved rather than deleted. This command does not inspect or change another project.
|
|
10
16
|
|
|
11
17
|
Every invocation rereads that same credential file, including clients already running. After an authenticated account page supplies the current key and `configure` synchronizes it once, all tasks under the same OS user automatically use the new value. A revoked or expired key cannot obtain new permissions for itself: return to the authenticated account page and synchronize once through JSON stdin. Status recovery only queries the original request; it never rotates credentials or repeats the operation.
|
|
@@ -86,7 +92,7 @@ After the pass's actual writes finish, rerun the original `scope-connect` with i
|
|
|
86
92
|
|
|
87
93
|
If settlement reports `ENTROROVE_SCOPE_AFTER_WRITE_REQUIRED`, refresh only the original scope and retry the same consumption request and receipt. A lost network response is different: rerunning the identical connector restores its original GET result without a replacement POST or signature.
|
|
88
94
|
|
|
89
|
-
With cloud sandbox disabled, the returned `observationAction` runs `npx @entrorove/skill workflow-observe-step <original repositoryRoot
|
|
95
|
+
With cloud sandbox disabled, the returned `observationAction` runs `npx @entrorove/skill workflow-observe-step <original repositoryRoot>`. A mutable step uses `{taskId,flowId,stepId,leaseId,generation,settlementId}` and the service updates source hashes only through accepted M settlements. An explicitly frozen read-only step uses `{taskId,flowId,stepId,leaseId,generation}`; its signed admission declares `sourceKind: original-read-only-plan` and contains no settlement ID or digest. Both paths require the original frozen command, executable hash, environment, limits and current signed source manifest. The local runner checks the actual source and protected dependencies before and after execution, including repository bytes and modes. No validator private key is needed. A real successful process remains `status: incomplete`, `runner: local`, `receipt: null`, `independentValidation: pending`; its result is stored separately and read with `member-observation-status`. It cannot advance formal verified dependencies or task delivery. Reentry preserves a completed process result; an unknown process outcome never authorizes an automatic second launch.
|
|
90
96
|
|
|
91
97
|
This command runs with the host OS user's permissions, not inside an OS sandbox. Its repository checks do not prove that external files, networks or databases were unchanged: `executionIsolation` remains `host-permissions`, and `externalEffects` remains `unverified`. The independent supervisor enforces the admitted start deadline and command timeout and reclaims its own process group if the CLI disappears. Only the original approved command can be launched; these observations grant no general shell or project-write permission.
|
|
92
98
|
|
package/SKILL.md
CHANGED
|
@@ -5,7 +5,7 @@ description: Execute an engineering task through Entrorove, preserving its origi
|
|
|
5
5
|
|
|
6
6
|
# entrorove · 熵巡
|
|
7
7
|
|
|
8
|
-
Version: 0.1.
|
|
8
|
+
Version: 0.1.56.
|
|
9
9
|
|
|
10
10
|
Entrorove applies to the whole task once selected. 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
|
|
|
@@ -19,8 +19,8 @@ Entrorove applies to the whole task once selected. Reading this document or call
|
|
|
19
19
|
With cloud sandbox disabled, the IDE performs its own planning and continues the original API flow. Do not require a cloud model call or create a sandbox task to perform local work.
|
|
20
20
|
When the model produces a task contract, preserve its `workType`, `risk`, `difficulty`, `crossModule`, `parallel`, `unclear`, `architectureContract`, optional `baselineRef`, and optional `interaction` as returned. Valid work types are `implementation`, `documentation`, `calculation`, `merge`, `review`, `decision`, and `diagnosis`. A `merge` requires a concrete `baselineRef` identifying the branch, tag, commit, or review baseline; keep that baseline unchanged through independent verification. A `decision` requires `interaction`, including a risk description for medium or high risk. Do not fill missing required fields by guessing.
|
|
21
21
|
4. After real cloud acceptance, immediately display **云端任务 <taskNumber> · 已进入**. Preserve `taskNumber` verbatim: it is the homepage sandbox tab number. Keep its UUID `taskId` separately. Run the returned `cloud-watch`, show each real public event and its cursor, and continue the original accepted flow from status. A command log alone is insufficient. No accepted cloud task means no cloud ID or model-execution claim.
|
|
22
|
-
5. For an accepted `flowId`, execute the returned `nextStep` with its required payload. Discover the schema before constructing input. An empty payload is valid only when that operation's schema allows it. Reconnect and refresh only the original flow. A changed snapshot is a binding failure to investigate; do not invent a fresh successful baseline, rebuild a flow to evade it, or erase the old failure. If the terminal flow truly owns a stale task slot, use the explicit `flow-abandon` lifecycle operation described below before routing the original task again.
|
|
23
|
-
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. **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.
|
|
22
|
+
5. For an accepted `flowId`, execute the returned `nextStep` with its required payload. Discover the schema before constructing input. An empty payload is valid only when that operation's schema allows it. Reconnect and refresh only the original flow. A changed snapshot is a binding failure to investigate; do not invent a fresh successful baseline, rebuild a flow to evade it, or erase the old failure. If a fresh scope proof returns `ENTROROVE_SCOPE_PLANNING_REASSESSMENT_REQUIRED` before plan freeze, follow its explicit `workflow-scope-reassess` recovery and retry the original complete request; a local reassessment or snapshot refresh alone does not renew the accepted gate. If the terminal flow truly owns a stale task slot, use the explicit `flow-abandon` lifecycle operation described below before routing the original task again.
|
|
23
|
+
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.
|
|
24
24
|
For a restricted 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. With sandbox disabled, `workflow-observe-step` runs the original approved command and preserves its real local result without a validator private key; independent verification remains pending. See [IDE-EXECUTION.md](IDE-EXECUTION.md#restricted-member-editing).
|
|
25
25
|
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.
|
|
26
26
|
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.
|
package/ide-code-mode.mjs
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
|
-
import { literalCommand, sdkCommand, readCommand, runtimeHashRead } from './ide-command.mjs';
|
|
2
|
+
import { literalCommand, sdkCommand, readCommand, runtimeHashRead, prepareIdeShellInput } from './ide-command.mjs';
|
|
3
|
+
|
|
4
|
+
export const hostTaskMessageTool = 'mcp__codex_app__send_message_to_thread';
|
|
5
|
+
export function hostTaskMessageInput(value) {
|
|
6
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
7
|
+
&& typeof value.threadId === 'string' && value.threadId.trim().length > 0
|
|
8
|
+
&& typeof value.prompt === 'string' && value.prompt.trim().length > 0
|
|
9
|
+
&& Object.entries(value).every(([key, item]) =>
|
|
10
|
+
['threadId', 'prompt', 'hostId', 'model', 'thinking'].includes(key) && typeof item === 'string');
|
|
11
|
+
}
|
|
3
12
|
|
|
4
13
|
export function prepareIdeCodeMode(code, runtime) {
|
|
5
14
|
if (typeof code !== 'string' || code.length > 1_000_000) return { allowed: false };
|
|
6
15
|
const ts = createRequire(runtime.cli)('typescript');
|
|
7
16
|
const source = ts.createSourceFile('host-call.js', code, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
|
|
8
17
|
if (source.parseDiagnostics.length) return { allowed: false };
|
|
9
|
-
let allowed = true; const replacements = [];
|
|
18
|
+
let allowed = true, unsafeShellInput = false; const replacements = [];
|
|
10
19
|
const literal = node => {
|
|
11
20
|
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) return node.text;
|
|
12
21
|
if (ts.isNumericLiteral(node)) return Number(node.text);
|
|
@@ -21,7 +30,8 @@ export function prepareIdeCodeMode(code, runtime) {
|
|
|
21
30
|
throw new Error('Dynamic tool arguments');
|
|
22
31
|
};
|
|
23
32
|
const visit = node => {
|
|
24
|
-
if (ts.isImportDeclaration(node) || ts.isNewExpression(node) || ts.isElementAccessExpression(node)
|
|
33
|
+
if (ts.isImportDeclaration(node) || ts.isNewExpression(node) || ts.isElementAccessExpression(node)
|
|
34
|
+
|| ts.isFunctionExpression(node) || ts.isTaggedTemplateExpression(node)) allowed = false;
|
|
25
35
|
if (ts.isCallExpression(node)) {
|
|
26
36
|
const name = node.expression.getText(source);
|
|
27
37
|
if (name === 'tools.exec_command') {
|
|
@@ -42,6 +52,16 @@ export function prepareIdeCodeMode(code, runtime) {
|
|
|
42
52
|
replacements.push({ start: argument.getStart(source), end: argument.getEnd(), text: JSON.stringify(fixed) });
|
|
43
53
|
}
|
|
44
54
|
} else if (!(readCommand(command) || sdkCommand(command, runtime))) allowed = false;
|
|
55
|
+
else {
|
|
56
|
+
const fixed = node.arguments.length === 1 && !node.questionDotToken
|
|
57
|
+
&& prepareIdeShellInput('exec_command', args, args.cmd);
|
|
58
|
+
if (!fixed) { allowed = false; unsafeShellInput = true; }
|
|
59
|
+
else replacements.push({ start: argument.getStart(source), end: argument.getEnd(), text: JSON.stringify(fixed) });
|
|
60
|
+
}
|
|
61
|
+
} catch { allowed = false; }
|
|
62
|
+
} else if (name === `tools.${hostTaskMessageTool}`) {
|
|
63
|
+
try {
|
|
64
|
+
if (node.questionDotToken || node.arguments.length !== 1 || !hostTaskMessageInput(literal(node.arguments[0]))) allowed = false;
|
|
45
65
|
} catch { allowed = false; }
|
|
46
66
|
} else if (name === 'tools.update_goal' || name === 'tools.get_goal') {
|
|
47
67
|
try {
|
|
@@ -55,7 +75,7 @@ export function prepareIdeCodeMode(code, runtime) {
|
|
|
55
75
|
ts.forEachChild(node, visit);
|
|
56
76
|
};
|
|
57
77
|
visit(source);
|
|
58
|
-
if (!allowed) return { allowed: false };
|
|
78
|
+
if (!allowed) return { allowed: false, ...(unsafeShellInput ? { reason: 'shell-input' } : {}) };
|
|
59
79
|
let prepared = code;
|
|
60
80
|
for (const item of replacements.sort((a, b) => b.start - a.start)) {
|
|
61
81
|
prepared = `${prepared.slice(0, item.start)}${item.text}${prepared.slice(item.end)}`;
|
package/ide-command.mjs
CHANGED
|
@@ -79,7 +79,9 @@ export function sdkCommand(parsed, runtime) {
|
|
|
79
79
|
if (['unlock', 'uninstall'].includes(args[0]) && args.length === 1 && !parsed.input) return { args };
|
|
80
80
|
if (simple.has(args[0]) && args.length === 1 && parsed.input) return { args, input: parsed.input };
|
|
81
81
|
if (['status', 'feedback'].includes(args[0]) && /^[a-f0-9]{64}$/u.test(args[1] ?? '') && [2,3].includes(args.length)) return { args };
|
|
82
|
-
if ([
|
|
82
|
+
if (args[0] === 'flows' && args.length === 1 && (!parsed.input || typeof parsed.input === 'object'
|
|
83
|
+
&& !Array.isArray(parsed.input) && Object.keys(parsed.input).length === 0)) return { args };
|
|
84
|
+
if (['install', 'billing'].includes(args[0]) && args.length === 1 && !parsed.input) return { args };
|
|
83
85
|
if (args[0] === 'install' && args.length === 3 && args[1] === '--host' && args[2] === 'codex' && !parsed.input) return { args };
|
|
84
86
|
if (args[0] === 'recurring' && ['list', 'claim'].includes(args[1] ?? '') && args.length === 2 && !parsed.input) return { args };
|
|
85
87
|
if (args[0] === 'recurring' && ['finish', 'runs'].includes(args[1] ?? '') && args.length === 3 && (!parsed.input || args[1] === 'finish')) return { args, ...(parsed.input ? { input: parsed.input } : {}) };
|
|
@@ -113,6 +115,33 @@ export function readCommand(parsed) {
|
|
|
113
115
|
return false;
|
|
114
116
|
}
|
|
115
117
|
|
|
118
|
+
const safeShellKeys = {
|
|
119
|
+
exec_command: new Set(['cmd', 'command', 'workdir', 'max_output_tokens', 'yield_time_ms', 'tty', 'shell', 'login']),
|
|
120
|
+
Bash: new Set(['command', 'cmd', 'workdir', 'timeout', 'description', 'max_output_chars', 'max_output_tokens', 'yield_time_ms', 'tty']),
|
|
121
|
+
shell_command: new Set(['command', 'cmd', 'workdir', 'timeout', 'description', 'max_output_chars', 'max_output_tokens', 'yield_time_ms', 'tty']),
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export function prepareIdeShellInput(toolName, input, normalizedCommand, mirrorCommand = false) {
|
|
125
|
+
const keys = safeShellKeys[toolName];
|
|
126
|
+
if (!keys || !input || typeof input !== 'object' || Array.isArray(input)
|
|
127
|
+
|| typeof normalizedCommand !== 'string' || !Reflect.ownKeys(input).every(key => keys.has(key))) return null;
|
|
128
|
+
const primary = toolName === 'exec_command' ? 'cmd' : 'command';
|
|
129
|
+
if (typeof input[primary] !== 'string' || input.cmd !== undefined && input.command !== undefined
|
|
130
|
+
&& input.cmd !== input.command || input.tty !== undefined && input.tty !== false
|
|
131
|
+
|| input.workdir !== undefined && typeof input.workdir !== 'string'
|
|
132
|
+
|| input.description !== undefined && typeof input.description !== 'string'
|
|
133
|
+
|| input.timeout !== undefined && (!Number.isSafeInteger(input.timeout) || input.timeout <= 0)
|
|
134
|
+
|| ['max_output_chars', 'max_output_tokens'].some(key => input[key] !== undefined
|
|
135
|
+
&& (!Number.isSafeInteger(input[key]) || input[key] <= 0))
|
|
136
|
+
|| input.yield_time_ms !== undefined && (!Number.isSafeInteger(input.yield_time_ms) || input.yield_time_ms < 0)
|
|
137
|
+
|| input.shell !== undefined && input.shell !== '/bin/sh'
|
|
138
|
+
|| input.login !== undefined && input.login !== false) return null;
|
|
139
|
+
const { cmd: _cmd, command: _command, ...options } = input;
|
|
140
|
+
return { ...options, [primary]: normalizedCommand,
|
|
141
|
+
...(toolName === 'exec_command' && mirrorCommand ? { command: normalizedCommand } : {}),
|
|
142
|
+
...(toolName === 'exec_command' ? { shell: '/bin/sh', login: false } : {}) };
|
|
143
|
+
}
|
|
144
|
+
|
|
116
145
|
const runtimeHashTool = process.platform === 'darwin'
|
|
117
146
|
? { name: 'shasum', executable: '/usr/bin/shasum', flags: ['-a', '256'] }
|
|
118
147
|
: process.platform === 'linux'
|
package/ide-hook.mjs
CHANGED
|
@@ -5,8 +5,8 @@ import { ideSession, ideRuntime, bindIdeAccount, ideContext, ideDigest, readBrea
|
|
|
5
5
|
ideBlockIdentity, repeatedIdeBlock, observeIdeToolOutcome } from './ide-session.mjs';
|
|
6
6
|
import { isStageTerminal, stageOf } from './local-stage-boundary.mjs';
|
|
7
7
|
import { literalCommand, sdkCommand, readCommand, renderSdkCommand, installerCommand,
|
|
8
|
-
runtimeHashCommand, runtimeHashRead } from './ide-command.mjs';
|
|
9
|
-
import { prepareIdeCodeMode } from './ide-code-mode.mjs';
|
|
8
|
+
runtimeHashCommand, runtimeHashRead, prepareIdeShellInput } from './ide-command.mjs';
|
|
9
|
+
import { hostTaskMessageInput, hostTaskMessageTool, prepareIdeCodeMode } from './ide-code-mode.mjs';
|
|
10
10
|
import { pathToFileURL } from 'node:url';
|
|
11
11
|
import path from 'node:path';
|
|
12
12
|
import { parseIdeResults, consumeIdeResults } from './ide-results.mjs';
|
|
@@ -42,7 +42,7 @@ function blockedReason(state, event, reason, detail = {}) {
|
|
|
42
42
|
state.blockStreak = repeated ? (state.blockStreak ?? 0) + 1 : 1;
|
|
43
43
|
state.lastBlockKey = key;
|
|
44
44
|
state.lastBlockIdentity = ideBlockIdentity(state);
|
|
45
|
-
const mechanism = detail.mechanism ?? `机制:${state.stateFile ?? '会话状态文件'} 当前 phase=${state.phase}${state.pendingCall ? `,pendingCall=${state.pendingCall.toolUseId}` : ''};该阶段只放行 Entrorove
|
|
45
|
+
const mechanism = detail.mechanism ?? `机制:${state.stateFile ?? '会话状态文件'} 当前 phase=${state.phase}${state.pendingCall ? `,pendingCall=${state.pendingCall.toolUseId}` : ''};该阶段只放行 Entrorove 管理调用及主机明确授权的任务间报告,业务与写操作需先建立原 flow 并取得 nextStep。`;
|
|
46
46
|
const repeat = repeated && state.blockStreak >= 2 ? `同一阻断已出现 ${state.blockStreak} 次,禁止再次检查:说明机制、给出绕过方案并执行绕过。` : '';
|
|
47
47
|
const recovery = detail.recovery ?? `绕过方案(离线、无网络、无协议、不经 hook,终端直接运行):\n${unlockCommand}\n执行后重试原管理调用;项目写入仍需原任务授权。`;
|
|
48
48
|
return `${reason} ${mechanism} ${repeat}\n${recovery}`;
|
|
@@ -68,6 +68,12 @@ function denyRuntimeHash(state, event, runtime) {
|
|
|
68
68
|
recovery: `恢复:用固定只读命令重新取得该 Node 文件的哈希:\n${safe ?? '当前平台没有受支持的固定哈希命令;请使用受管安装所绑定的 Node 路径。'}${safe ? `\n代码模式请传静态 tools.exec_command({cmd:${JSON.stringify(safe)},shell:'/bin/sh',login:false})。` : ''}\n本次在 hook 本地预检中拒绝,未发起协议请求,因此没有 requestId。`,
|
|
69
69
|
});
|
|
70
70
|
}
|
|
71
|
+
function denyUnsafeShellInput(state, event) {
|
|
72
|
+
return denyBlocked(state, event, 'ENTROROVE_IDE_EXEC_INPUT_DENIED:只读或 SDK 命令含不安全的执行参数。', {
|
|
73
|
+
mechanism: '机制:仅接受静态命令及只读的 workdir、输出上限和超时参数;自定义 shell、登录模式、环境变量、交互终端或权限扩张均不属于受管调用。',
|
|
74
|
+
recovery: '恢复:移除执行器和权限扩张参数。exec_command 将固定为 /bin/sh 且 login=false;本次在 hook 本地预检中拒绝,未发起协议请求。',
|
|
75
|
+
});
|
|
76
|
+
}
|
|
71
77
|
function lifecycleResult(value, depth = 0) {
|
|
72
78
|
if (!value || typeof value !== 'object' || depth > 4) return null;
|
|
73
79
|
if (value.operation === 'flow-cancel' || value.operation === 'flow-abandon') return value;
|
|
@@ -130,12 +136,11 @@ async function beforeTool(event, state, home) {
|
|
|
130
136
|
const isShell = ['Bash', 'exec_command', 'shell_command'].includes(event.tool_name);
|
|
131
137
|
const installer = isShell && installerCommand(parsed);
|
|
132
138
|
if (installer) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
return output('PreToolUse', { permissionDecision: 'allow', updatedInput:
|
|
138
|
-
...(Object.hasOwn(event.tool_input ?? {}, 'cmd') ? { cmd: installer } : {}) } });
|
|
139
|
+
const safeInput = prepareIdeShellInput(event.tool_name, event.tool_input, installer, true);
|
|
140
|
+
if (!safeInput) return denyUnsafeShellInput(state, event);
|
|
141
|
+
if (state.active) { state.readCalls ??= {};
|
|
142
|
+
state.readCalls[event.tool_use_id] = { toolUseId: event.tool_use_id, command: 'install' }; }
|
|
143
|
+
return output('PreToolUse', { permissionDecision: 'allow', updatedInput: safeInput });
|
|
139
144
|
}
|
|
140
145
|
const namedSdk = parsed?.words.some(word => word === 'entrorove' || /^@entrorove\/skill(?:@[^ ]+)?$/u.test(word)
|
|
141
146
|
|| /(?:@entrorove\/skill|entrorove-skill)\/cli\.mjs$/u.test(word));
|
|
@@ -143,20 +148,20 @@ async function beforeTool(event, state, home) {
|
|
|
143
148
|
const runtime = isShell && parsed ? await ideRuntime(home) : null;
|
|
144
149
|
const call = runtime && sdkCommand(parsed, runtime);
|
|
145
150
|
if (call) {
|
|
151
|
+
if (!prepareIdeShellInput(event.tool_name, event.tool_input, command, true)) return denyUnsafeShellInput(state, event);
|
|
146
152
|
if (call.args[0] === 'uninstall') {
|
|
147
153
|
state.active = false; state.phase = 'user-exited';
|
|
148
|
-
return output('PreToolUse', { permissionDecision: 'allow', updatedInput:
|
|
149
|
-
|
|
150
|
-
...(Object.hasOwn(event.tool_input ?? {}, 'cmd') ? { cmd: renderSdkCommand(call, runtime, state.sessionId) } : {}) } });
|
|
154
|
+
return output('PreToolUse', { permissionDecision: 'allow', updatedInput: prepareIdeShellInput(event.tool_name,
|
|
155
|
+
event.tool_input, renderSdkCommand(call, runtime, state.sessionId), true) });
|
|
151
156
|
}
|
|
152
157
|
state.active = true;
|
|
153
158
|
if (call.args[0] !== 'configure' && call.args[0] !== 'unlock') await bindIdeAccount(state, home);
|
|
154
|
-
if (state.phase === 'setup' && !unlocked && !isManagementCall(call)) return denyBlocked(state, event, 'Entrorove:自举阶段只放行 Entrorove
|
|
159
|
+
if (state.phase === 'setup' && !unlocked && !isManagementCall(call)) return denyBlocked(state, event, 'Entrorove:自举阶段只放行 Entrorove 管理调用及主机明确授权的任务间报告;业务与写操作需先建立原 flow。');
|
|
155
160
|
if (call.args[0] === 'unlock') {
|
|
156
161
|
resetBlockStreak(state);
|
|
157
162
|
const rewritten = renderSdkCommand(call, runtime, state.sessionId);
|
|
158
|
-
return output('PreToolUse', { permissionDecision: 'allow', updatedInput:
|
|
159
|
-
|
|
163
|
+
return output('PreToolUse', { permissionDecision: 'allow', updatedInput: prepareIdeShellInput(event.tool_name,
|
|
164
|
+
event.tool_input, rewritten, true), additionalContext: `${ideContext(state)}离线管理解锁已放行。` });
|
|
160
165
|
}
|
|
161
166
|
const newStart = call.args[0] === 'cloud-task' || call.args[0] === 'invoke' && ['start','cloud-task'].includes(call.input?.operation);
|
|
162
167
|
if (newStart && (state.flowId || state.taskId) && state.phase !== 'ended') return denyBlocked(state, event, 'Entrorove:必须继续原任务,禁止用第二次 start 或 cloud-task 绕过原门禁。');
|
|
@@ -187,16 +192,19 @@ async function beforeTool(event, state, home) {
|
|
|
187
192
|
} else state.pendingCall = pending;
|
|
188
193
|
const installation = call.args[0] === 'install' && parsed.words.some(word => ['npx','npx.cmd'].includes(word));
|
|
189
194
|
const rewritten = renderSdkCommand(call, runtime, state.sessionId);
|
|
190
|
-
return output('PreToolUse', { permissionDecision: 'allow',
|
|
191
|
-
|
|
195
|
+
return output('PreToolUse', { permissionDecision: 'allow', updatedInput: prepareIdeShellInput(event.tool_name,
|
|
196
|
+
event.tool_input, installation ? command : rewritten, true), additionalContext: ideContext(state) });
|
|
192
197
|
}
|
|
193
198
|
if (!state.active) return {};
|
|
199
|
+
// The host owns user authorization for peer messages; this hook does not rewrite or grant it.
|
|
200
|
+
if (event.tool_name === hostTaskMessageTool && hostTaskMessageInput(event.tool_input)) return output('PreToolUse', { additionalContext: ideContext(state) });
|
|
194
201
|
if (['exec','functions.exec'].includes(event.tool_name)) {
|
|
195
202
|
const current = await ideRuntime(home), code = typeof event.tool_input === 'string' ? event.tool_input : event.tool_input?.code;
|
|
196
203
|
const prepared = prepareIdeCodeMode(code, current);
|
|
197
204
|
if (prepared.allowed) return output('PreToolUse', { additionalContext: ideContext(state),
|
|
198
205
|
...(prepared.code === code ? {} : { updatedInput: typeof event.tool_input === 'string' ? prepared.code
|
|
199
206
|
: { ...event.tool_input, code: prepared.code } }) });
|
|
207
|
+
if (prepared.reason === 'shell-input') return denyUnsafeShellInput(state, event);
|
|
200
208
|
if (typeof code === 'string' && /\b(?:shasum|sha256sum)\b/u.test(code)) return denyRuntimeHash(state, event, current);
|
|
201
209
|
return denyBlocked(state, event, 'Entrorove:代码模式含未批准的原生执行或动态工具输入;请使用原任务的受控入口。');
|
|
202
210
|
}
|
|
@@ -213,7 +221,11 @@ async function beforeTool(event, state, home) {
|
|
|
213
221
|
}
|
|
214
222
|
if (runtimeHashAttempt(command, parsed)) return denyRuntimeHash(state, event, runtime ?? await ideRuntime(home));
|
|
215
223
|
}
|
|
216
|
-
if (isShell && readCommand(parsed)
|
|
224
|
+
if (isShell && readCommand(parsed)) {
|
|
225
|
+
const safeInput = prepareIdeShellInput(event.tool_name, event.tool_input, command);
|
|
226
|
+
return safeInput ? output('PreToolUse', { updatedInput: safeInput, additionalContext: ideContext(state) }) : denyUnsafeShellInput(state, event);
|
|
227
|
+
}
|
|
228
|
+
if (readTools.has(event.tool_name)) return output('PreToolUse', { additionalContext: ideContext(state) });
|
|
217
229
|
return denyBlocked(state, event, `Entrorove:已阻止原生 ${event.tool_name ?? 'unknown'} 调用。当前阶段 ${state.phase}${state.taskNumber ? `,云端任务 ${state.taskNumber}` : ''}。写入、删除、移动及不确定的终端操作必须使用原任务的 guarded-write/guarded-delete/guarded-move 或已批准的执行计划。`);
|
|
218
230
|
}
|
|
219
231
|
export async function runIdeHook(event, { home } = {}) {
|
package/ide-session.mjs
CHANGED
|
@@ -129,7 +129,7 @@ export function ideContext(state) {
|
|
|
129
129
|
const waiting = repeatedIdeBlock(state);
|
|
130
130
|
const action = waiting ? '同一真实阻断已达到两次;本轮允许结束并等待修复,禁止原样第三次重试。原目标、flow 与队列均保留。'
|
|
131
131
|
: '必须读 SKILL.md 并执行返回的 nextStep;禁止在失败后替换执行器。';
|
|
132
|
-
return `${waiting ? 'Entrorove 原任务绑定已保留。' : 'Entrorove 已强制接管本任务。'}${state.taskNumber ? `云端任务 ${state.taskNumber}。` : '尚未获得云端任务编号,不得声称已进入云端。'}${state.flowId ? `继续原 flowId=${state.flowId},` : ''}${state.requestId ? `原 requestId=${state.requestId}。` : ''}当前阶段 ${state.phase}。${delivered}${action}
|
|
132
|
+
return `${waiting ? 'Entrorove 原任务绑定已保留。' : 'Entrorove 已强制接管本任务。'}${state.taskNumber ? `云端任务 ${state.taskNumber}。` : '尚未获得云端任务编号,不得声称已进入云端。'}${state.flowId ? `继续原 flowId=${state.flowId},` : ''}${state.requestId ? `原 requestId=${state.requestId}。` : ''}当前阶段 ${state.phase}。${delivered}${action}Codex 主机明确授权的任务间报告可使用 send_message_to_thread;报告文本不授予仓库写入或新 flow。源文件、数据及终端写入必须走原任务的受控入口。`;
|
|
133
133
|
}
|
|
134
134
|
// Verified hosts only: the guard is universal in shape (host label + session
|
|
135
135
|
// id), and each host proves itself through its own adapter callback.
|
package/local-approved-plan.mjs
CHANGED
|
@@ -40,6 +40,7 @@ function validatePlan(plan, artifactDigest = planArtifact) {
|
|
|
40
40
|
validateRunnerParallelPaths(plan);
|
|
41
41
|
if (policy.discipline) {
|
|
42
42
|
const reviewed = disciplineApprovedPolicy(policy.discipline);
|
|
43
|
+
if (reviewed.access === 'read-only' && policy.parallelPaths !== undefined) fail('ENTROROVE_READ_ONLY_POLICY_INVALID', 'Read-only execution cannot grant parallel mutation or equivalence permissions');
|
|
43
44
|
if (!disciplineDataSnapshotsBound(reviewed, plan.files)) {
|
|
44
45
|
fail('ENTROROVE_DISCIPLINE_DATA_SNAPSHOT_REQUIRED', 'Existing data operations require their actual frozen snapshot artifact before execution');
|
|
45
46
|
}
|
|
@@ -96,7 +97,7 @@ function evidence(authority, result, sub) { const summary = result.timedOut ? 'A
|
|
|
96
97
|
}
|
|
97
98
|
|
|
98
99
|
export async function runApprovedPlan(input) { const owner = await authority(input); const comparison = await runnerDisciplineBefore(input, owner); const result = await runCommand(owner); try { const after = await authority(input); if (after.planFile.sha256 !== owner.planFile.sha256) fail('ENTROROVE_PLAN_CHANGED', 'plan changed during execution'); } catch (cause) { const execution = { schemaVersion: RUNNER_SCHEMA, status: 'failed', error: cause.message, process: result, evidence: [] }; const error = new Error(cause.message); error.code = cause.code; error.execution = execution; throw error; }
|
|
99
|
-
owner.disciplineComparison = await runnerDisciplineAfter(comparison, owner);
|
|
100
|
+
owner.disciplineComparison = await runnerDisciplineAfter(comparison, owner, result);
|
|
100
101
|
const sub = await subject(owner, result); const item = evidence(owner, result, sub); const status = !owner.signing && result.status === 'succeeded' ? 'incomplete' : result.status; return { schemaVersion: RUNNER_SCHEMA, status, planSha256: owner.planFile.sha256, approvalSha256: owner.approval.sha256, approvedBy: owner.approval.approval.approvedBy, subject: sub, process: result, evidence: [item], receipt: item.receipt ?? null, ...(owner.signing ? { receiptPublicKey: createPublicKey(owner.signing.privateKey).export({ format: 'der', type: 'spki' }).toString('base64') } : {}), signingBoundary: owner.signing ? 'configured-signature-only' : 'local-evidence-only' };
|
|
101
102
|
}
|
|
102
103
|
|
|
@@ -125,7 +126,7 @@ export async function runApprovedPlanObject(input) {
|
|
|
125
126
|
const error = new Error(cause.message); error.code = cause.code;
|
|
126
127
|
error.execution = { schemaVersion: RUNNER_SCHEMA, status: 'failed', error: cause.message, process: result, evidence: [], effects: 'unknown' }; throw error;
|
|
127
128
|
}
|
|
128
|
-
owner.disciplineComparison = await runnerDisciplineAfter(comparison, owner);
|
|
129
|
+
owner.disciplineComparison = await runnerDisciplineAfter(comparison, owner, result);
|
|
129
130
|
const sub = await subject(owner, result); const item = evidence(owner, result, sub);
|
|
130
131
|
return { schemaVersion: RUNNER_SCHEMA, status: !owner.signing && result.status === 'succeeded' ? 'incomplete' : result.status,
|
|
131
132
|
planSha256: owner.planFile.sha256, approvalSha256: owner.approval.sha256, approvedBy: owner.approval.approval.approvedBy,
|
|
@@ -12,11 +12,15 @@ export const DISCIPLINE_WRITE_SCHEMA = object({ targetPath: relativePath,
|
|
|
12
12
|
}, ['targetPath', 'kind', 'reason', 'requirementRefs']);
|
|
13
13
|
const approval = object({ interactionId: identifier, interactionDigest: shaSchema, auditId: identifier,
|
|
14
14
|
proposalDigest: shaSchema }, ['interactionId', 'interactionDigest', 'auditId', 'proposalDigest']);
|
|
15
|
-
|
|
15
|
+
const mutablePolicy = object({ schemaVersion: { const: 'entrorove.discipline-policy/1.0' },
|
|
16
16
|
requirementIds: refs, writes: array(DISCIPLINE_WRITE_SCHEMA, 1), approval,
|
|
17
17
|
dataOperations: array(object({ operation: { enum: ['delete', 'migrate', 'rebuild'] }, target: text,
|
|
18
18
|
reason: text, requirementRefs: refs, snapshotArtifactDigest: shaSchema }, ['operation', 'target', 'reason', 'requirementRefs', 'snapshotArtifactDigest'])),
|
|
19
19
|
}, ['schemaVersion', 'requirementIds', 'writes']);
|
|
20
|
+
const readOnlyPolicy = object({ schemaVersion: { const: 'entrorove.discipline-policy/1.0' },
|
|
21
|
+
access: { const: 'read-only' }, requirementIds: refs, writes: { type: 'array', maxItems: 0 },
|
|
22
|
+
}, ['schemaVersion', 'access', 'requirementIds', 'writes']);
|
|
23
|
+
export const DISCIPLINE_POLICY_SCHEMA = { oneOf: [mutablePolicy, readOnlyPolicy] };
|
|
20
24
|
export const DISCIPLINE_DECLARATION_SCHEMA = object({ ...DISCIPLINE_WRITE_SCHEMA.properties,
|
|
21
25
|
usageReviewDigest: shaSchema }, DISCIPLINE_WRITE_SCHEMA.required);
|
|
22
26
|
const validate = new Ajv({ allErrors: true, strict: true }).compile(DISCIPLINE_POLICY_SCHEMA);
|
|
@@ -6,9 +6,9 @@ import path from 'node:path';
|
|
|
6
6
|
export async function runnerDisciplineBefore(input, authority) {
|
|
7
7
|
if (!authority.plan.policy.discipline) return null;
|
|
8
8
|
const original = await disciplineRepositoryObservation(input, authority.root);
|
|
9
|
-
return { input, original: original.comparison };
|
|
9
|
+
return { input, original: original.comparison, readOnly: authority.plan.policy.discipline.access === 'read-only' };
|
|
10
10
|
}
|
|
11
|
-
export async function runnerDisciplineAfter(before, authority) {
|
|
11
|
+
export async function runnerDisciplineAfter(before, authority, processResult) {
|
|
12
12
|
if (!before) return null;
|
|
13
13
|
const after = (await disciplineRepositoryObservation(before.input, authority.root)).comparison;
|
|
14
14
|
const changed = disciplineRepositoryDifferences(before.original, after);
|
|
@@ -23,5 +23,10 @@ export async function runnerDisciplineAfter(before, authority) {
|
|
|
23
23
|
catch (error) {
|
|
24
24
|
if (error.code !== 'ENTROROVE_STATE_EXISTS' || canonical(await readMutationJson14(file)) !== canonical(record)) throw error;
|
|
25
25
|
}
|
|
26
|
+
if (before.readOnly && changed.length) {
|
|
27
|
+
throw Object.assign(new Error('Explicit read-only execution changed repository bytes or permissions; no successful receipt is issued'),
|
|
28
|
+
{ code: 'ENTROROVE_READ_ONLY_EXECUTION_DRIFT', execution: { schemaVersion: 'entrorove.validator-local-runner/1.0',
|
|
29
|
+
status: 'failed', process: processResult, evidence: [], effects: 'unknown', comparison: report } });
|
|
30
|
+
}
|
|
26
31
|
return { ...report, reportDigest };
|
|
27
32
|
}
|
package/manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@entrorove/skill",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.56",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"entrorove": "cli.mjs"
|
|
@@ -275,5 +275,10 @@
|
|
|
275
275
|
"tree-sitter-wasms": "0.1.13",
|
|
276
276
|
"@vue/compiler-sfc": "3.5.43",
|
|
277
277
|
"svelte": "5.57.1"
|
|
278
|
+
},
|
|
279
|
+
"repository": {
|
|
280
|
+
"type": "git",
|
|
281
|
+
"url": "git+https://github.com/88208555/entrorove.git",
|
|
282
|
+
"directory": "packages/entrorove-skill"
|
|
278
283
|
}
|
|
279
284
|
}
|
package/release-state.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "entrorove.release-state/1.0",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"digest": "
|
|
3
|
+
"version": "0.1.56",
|
|
4
|
+
"digest": "217c46f009d2c8513e5697b663e07e8fabe3b647728ce7c07bf20c49037c991e",
|
|
5
5
|
"files": {
|
|
6
6
|
"connected-cli.mjs": "260c0fc99586c24bd4d0351a14e0744849a612ebe6f1bd7951e4fce200daf920",
|
|
7
7
|
"local-approved-process.mjs": "442856df788e63f38757439ba0657d1c689aaf797631a955fae3dd639e844e6d",
|
|
8
8
|
"local-approved-supervisor.mjs": "87142a4c13d27c8265cc1fd7fc9088988da9bbb02134164e1a7c5417da926ac7",
|
|
9
9
|
"local-mutation-consumption.mjs": "cd8650b9be894639ceb35af88d23c8002b4acff27afad2e64174eb21a9424cfb",
|
|
10
10
|
"workflow-observation-client.mjs": "5e67bc32a8625de9523498f694655c378ae34af59c4aa2f0e07efcd72347dbc0",
|
|
11
|
-
"workflow-observation-connection.mjs": "
|
|
11
|
+
"workflow-observation-connection.mjs": "daf5f707dd93397e3581dc761131ef380026110b07f58fa9a2c9e738e68fa492",
|
|
12
12
|
"workflow-observation-remote.mjs": "1e5a8b9c2b9b095242ad28a1d5ebce5944efdae74383fb20d6676b93b240c5a7",
|
|
13
13
|
"workflow-observation-runner.mjs": "3eb8280ec7abffcb30398e3f4e5f7d0ec3df4385fa720f188ed83757b87cd977",
|
|
14
14
|
"workflow-observation-state.mjs": "a525046df9b3de75b10d2bf35c2b6d7719f12a647e4aee1f1e85dbdbfb167321",
|
|
15
|
-
"workflow-observation-wire.mjs": "
|
|
15
|
+
"workflow-observation-wire.mjs": "9bff48377d240c2fd33ee82de6adcd97376b239d32d2552cfa1ce4f7e5e3562e",
|
|
16
16
|
"local-preserved-mutation-protection.mjs": "95041d4f75253a153c7fd85573ba0457513ffe47a6a6bd00c4f59379b81ce580",
|
|
17
17
|
"local-preserved-mutation-signer.mjs": "2b0771cbdd18ba00267aa441b7892cd04feab9f8543ae5e2703430b1e580747f",
|
|
18
18
|
"local-preserved-mutation-state.mjs": "1ff7939d668e39ec641a7cf46a4ce3014c63f3898207cc874f21170376479671",
|
|
@@ -20,11 +20,11 @@
|
|
|
20
20
|
"local-delegated-modify-analysis.mjs": "f87148ddb023b79e241ca97db582991da8d0e1a51b26b1d9bc2799a76efd7297",
|
|
21
21
|
"mutation-client.mjs": "8d27b267dc660510aa1406c44d4005e44b972a1260f90657de4fa0e75b7b6c3e",
|
|
22
22
|
"cloud-mutation-authority.mjs": "c7226e882a4f3be4d2f55728f515d5a815c0aa1f21aa5e4689314e6e6a8c66a2",
|
|
23
|
-
"ide-code-mode.mjs": "
|
|
24
|
-
"ide-command.mjs": "
|
|
25
|
-
"ide-hook.mjs": "
|
|
23
|
+
"ide-code-mode.mjs": "f8ba4de33632ab4c6df52dab52f392414b4632c7e94b218b8065677970c075c7",
|
|
24
|
+
"ide-command.mjs": "3db45dd1d12db670e594b6ced35f2d7778cb01091695b1e240179b1ea34f25ee",
|
|
25
|
+
"ide-hook.mjs": "68ca1f5805a49b78182f2508bdd5fac390080e9b2c2ba3645a9aaea69f9d4d0f",
|
|
26
26
|
"ide-install.mjs": "10247efadcfeef5da001022d12080bd0f789e67fdf7862bcc65db73dda3bbe57",
|
|
27
|
-
"ide-session.mjs": "
|
|
27
|
+
"ide-session.mjs": "6df04bca3231954e8b83f6b1b535befff16b6c62c51ca65ae3aa2559180ad7c2",
|
|
28
28
|
"break-glass.mjs": "6ec9f97c5d68bb33121b2374c06b9d4c8eb3419b52d00849fec7124bc63e4582",
|
|
29
29
|
"ide-results.mjs": "7ed5caf8e77cf42ae6d62cc1ed42b1639bca6069d809b90a399bec5192fb903e",
|
|
30
30
|
"ide-queue.mjs": "806e716c775fd0079417027b135977c8516f47dddb0bc5ca19e32a355dab0e9e",
|
|
@@ -33,14 +33,14 @@
|
|
|
33
33
|
"local-discipline-batch.mjs": "2cc90dbae55efdee0c445da399c0a9acafa92e915344c25208ebca554fc4c9e9",
|
|
34
34
|
"local-typescript-function-reference.mjs": "af682b479fe0d69a5ecd62232406bb8078408ec8710733cfed9ae6b059762ba5",
|
|
35
35
|
"local-scope-import-expressions.mjs": "20f1e065582c68d2334e0dfaa130a559ff19886a57f9038dc569f65993bb06c8",
|
|
36
|
-
"local-discipline-contracts.mjs": "
|
|
36
|
+
"local-discipline-contracts.mjs": "ab0c11dcc4a6ffd3cb79b53862c19308eaf0d159deee7f9958266852d977363e",
|
|
37
37
|
"local-discipline-contracts.d.mts": "defc684c78dc88afa1c54340ab4a01ae36db7aa11257cbe1f5cd323f608c78c4",
|
|
38
38
|
"local-discipline-gate.mjs": "e3851157bf1d8bc86eb44212a88acd20acfc2d1152e7d87af6dc2a37afffcd6f",
|
|
39
39
|
"local-discipline-native.mjs": "8b99b5ce951cab3c1426428ad807650c61c0ffb2c346d747b73b99057b382049",
|
|
40
40
|
"local-discipline-operations.mjs": "9fcb7b8889b7718d42e1d37936485d8198d5017d405e0cdbbabdbbd12fcc9d48",
|
|
41
41
|
"local-discipline-repository.mjs": "b6392e02ecae333dfdc8df41f203fd69bed035d88b15b16d112ab1897b3c297c",
|
|
42
42
|
"local-discipline-snapshot.mjs": "8fd35b2cfbece8f64579f144f589a66dec9b6022d50fe931c4c1f886cb83134d",
|
|
43
|
-
"local-runner-discipline.mjs": "
|
|
43
|
+
"local-runner-discipline.mjs": "8d4ada838f81d54963adbb7e433f1ca80ae1934ea126f90c59a49d29742979c4",
|
|
44
44
|
"local-integrity.d.mts": "21357f306234bd841c0d3d90bf2d09f7663f63746f897b9f38190428ae8198b3",
|
|
45
45
|
"local-equivalence-runner.mjs": "df2f406c693c4da7bb30542ec7b4d46a9336f56e9fdd263196c0f3a73d2f6e91",
|
|
46
46
|
"validator-runtime-build.mjs": "da20bd19eb088d79d62e33969b4c075dc2199761b395e40060227f8f86b9519a",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"runtime/validator-runtime-v7.0.44.d.mts": "7797edbfe2f7de24da370204dce4dfd22822a1bd80df93514fff900b98afe36f",
|
|
49
49
|
"snapshot-retention.mjs": "4f8919fe8ec49fb4c23e080a8ad2fa3a9282bca3fffe7cf55c64e89ef316697e",
|
|
50
50
|
"snapshot-cleanup-journal.mjs": "76a9684cc95b4eb076e1298d78a9dff37948b00d9607b8f546c599072d3ed331",
|
|
51
|
-
"IDE-EXECUTION.md": "
|
|
51
|
+
"IDE-EXECUTION.md": "7e312d8069b0f03deba896b26abfa6beab74ba08835aa58c47633f5c8eeb7d35",
|
|
52
52
|
"REUSE-DISCIPLINE.md": "5b214cf313e7f715f52d54e28093a2d038f0f196ca64b15d1c49587b9d8eecf8",
|
|
53
53
|
"reuse-discipline-release.mjs": "2935898d1aa807fb982a78d7ab5cb1abe05e3c8fc945dc0909da8098872aa1ac",
|
|
54
54
|
"local-final-gate16.mjs": "083aec470670e6cdb7ad2ab27b7a51b53a9ec3a8b4121cb9cbfa17d9940892c2",
|
|
@@ -96,11 +96,11 @@
|
|
|
96
96
|
"install-cleanup.mjs": "8a8e1a7dfe37427f86cd4d6e317890d0829894a9949d4216674a716b36edbc54",
|
|
97
97
|
"review-client.mjs": "3491eef68ed533174b31db3c53febb6519b8f088f71a9964644714a9a4c03c77",
|
|
98
98
|
"version.mjs": "1f392147d72c40af4b03b307ea607075fb815af5174fa07e254b95fe12dbf3e7",
|
|
99
|
-
"SKILL.md": "
|
|
100
|
-
"CAPABILITY-MAP.md": "
|
|
99
|
+
"SKILL.md": "684438bcdfefef590e51e4ce02be0a244a26d142a24599258bf3ae24c86bbfa1",
|
|
100
|
+
"CAPABILITY-MAP.md": "9058ee785768b470f26641032dfdaca638e08473d3508ad36deb63f375b84afe",
|
|
101
101
|
"AGREEMENT.md": "f93a68e92c457200dd024610e65f74e478b8f60860f951da818b70561f1b9bf8",
|
|
102
102
|
"DELIVERY-BOUNDARIES.md": "9b9db50ed1bcce9e77ff6527b6bb7eaf12f3b913162ea9cb7fe90db06b393a4f",
|
|
103
|
-
"package.json": "
|
|
103
|
+
"package.json": "19e30a6adde349d335e9a0b4559799a5af6a1b7df7094344dafd411f6ed7f22f",
|
|
104
104
|
"manifest.json": "ab06e307795bc9da5add926752b6c4dbdaf18625090261a035aa356a679c504f",
|
|
105
105
|
"release.mjs": "8b0dc36c47aec72cf9952c9ac1ce5fe27da5ae6829f50cd2339fa101b2eda93c",
|
|
106
106
|
"local-cli.mjs": "41e2a737c8587cb5ce469a7661f6d14b5ea4cd073f8286822fc4dc96b67f56a1",
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
"local-chain-plan.mjs": "e45a9170f569c80f7e5532bbc109dbae3c24b696815316e503cb3779135be648",
|
|
124
124
|
"local-chain-release-labels.mjs": "7ce5350d1062d5236f57f10e7e81713c1101463d5e7f252604ef0c2a30840bfe",
|
|
125
125
|
"local-chain-results.mjs": "0455f72cad574cfef87c2c6c0928d6432bba7e5dbaff521a2b62a9575299ca10",
|
|
126
|
-
"local-approved-plan.mjs": "
|
|
126
|
+
"local-approved-plan.mjs": "c2c88ab4085ff35a1811fe43e9a28699cfc9387eda54bfeafae3e7dd9e8cdb19",
|
|
127
127
|
"local-budget-renewal.mjs": "6b436358066fe33b2f6d871fc90d8e7c551edd6255efd73c0d255d0fd35d5ef8",
|
|
128
128
|
"local-probe.mjs": "9b948db1de1e34c7605c4c454cd4aced66d2b57d2cf4471adbb80bcc76dee635",
|
|
129
129
|
"local-budget-grants.mjs": "c5f6542f5d2c3bfe07f3ca5b5bc849bf05d886b06014f720322d530510775d3d",
|
|
@@ -176,7 +176,7 @@
|
|
|
176
176
|
"local-task-routing.mjs": "6e2e506b2fb0e2e8d8c5147d7df32849b57a4e94a23634f01a355d45735858e4",
|
|
177
177
|
"remote-client.mjs": "0d1ec6506fe53a1e33c31c5ba19d14e30e00e85e11eaf7ff8277ea798b9f36c9",
|
|
178
178
|
"cloud-task-client.mjs": "11acb1a033c94cdfbdfb177a3402ad97f6a5226f577138c8cf6395dc3ebb1a39",
|
|
179
|
-
"scope-runner-client.mjs": "
|
|
179
|
+
"scope-runner-client.mjs": "2afcbb8bc8a26d03f26787cb2ffd9994c4377757d16d54aa0c83752a29e833ea",
|
|
180
180
|
"native-client.mjs": "6f5575909bae9daba643287f876ffa0f9ff1c53a367b32a40145d740456d6605",
|
|
181
181
|
"native-notification.mjs": "8859a26c6f13d222711f763acf49ed8546b3306cf71f990643ac31a5ccaf1ba1",
|
|
182
182
|
"local-peer-status.mjs": "a971bcc69d60bb32b875ca5bb4297d948631d5d2821b812c279613ea059a8b4f",
|
|
@@ -201,7 +201,7 @@
|
|
|
201
201
|
"local-task-handoff-read-authority.mjs": "7d2991cfac4562039232566d25441aa98106ce27bc9b7bcf6df4ff28d738e167",
|
|
202
202
|
"local-task-handoff-write-archive.mjs": "3668f39d8ab5664ed717994cf1b4bcc7ea23e146a0e06291f851d30337faeb77",
|
|
203
203
|
"local-task-handoff-write-receipts.mjs": "e1321127bd6fcad7840e185639f81ae68a8722a0ee4264a7b504a02b27887647",
|
|
204
|
-
"workflow-local-runner.mjs": "
|
|
204
|
+
"workflow-local-runner.mjs": "c74ccd425fd7d4882388338e27f3b25e85c39c1a6cb7935435393844ec13c001",
|
|
205
205
|
"workflow-local-contracts.mjs": "bc52a461727b34c72daa058e1aa77d58206eafad14a93a347537f9969ae51184",
|
|
206
206
|
"workflow-local-client.mjs": "45c954f694d96bf0ea87ee2dc6902a6763dac2cb1df713b6a5e749632054e9af",
|
|
207
207
|
"workflow-local-request.mjs": "4e24c543ce386292a87dc9cd41f90d23375f12079cacffc515c8cd01fd571780",
|
package/releases.json
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.1.56",
|
|
4
|
+
"date": "2026-10-17",
|
|
5
|
+
"zh": "补齐原工作流显式只读步骤及无写入结算的本地观察,复核真实受检仓库字节与权限;修复流程列表空输入兼容,返回精确规划前置条件与范围错误,并配置 GitHub OIDC 发布。",
|
|
6
|
+
"en": "Added explicit read-only workflow steps and local observations without fabricated mutation settlements, checking actual repository bytes and modes; fixed empty-input flow listing, precise planning prerequisite and scope errors, and GitHub OIDC publishing metadata."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.1.55",
|
|
10
|
+
"date": "2026-10-17",
|
|
11
|
+
"zh": "修复任务间消息被技能误拦截,将已授权通信交回主机审批;规范只读和 SDK 命令执行参数;新增原流程计划阶段的显式范围重评与恢复提示,保留原门禁、合同、请求身份及失败历史。",
|
|
12
|
+
"en": "Restored host-approved task messaging, constrained read and SDK execution inputs, and added explicit planning-scope reassessment with phase-correct recovery while retaining original gates, contracts, request identities, and failure history."
|
|
13
|
+
},
|
|
2
14
|
{
|
|
3
15
|
"version": "0.1.54",
|
|
4
16
|
"date": "2026-10-17",
|
package/scope-runner-client.mjs
CHANGED
|
@@ -82,10 +82,16 @@ async function connect(identity, directory, dependencies) {
|
|
|
82
82
|
const observed = await probe({ ...input, ownerId: challenge.ownerId, task: challenge.task, taskDigest: challenge.taskDigest,
|
|
83
83
|
chainDigest: challenge.chainDigest, repositoryId: challenge.repositoryId, challenge: challenge.challenge, signerConfigPath });
|
|
84
84
|
const accepted = await remote('scope-runner-attest', { flowId: identity.flowId, attestation: observed.attestation }, dependencies);
|
|
85
|
+
const next = accepted.nextStep ?? { operation: 'workflow-gate', payload: { targetFiles: [...identity.targetPaths] } };
|
|
86
|
+
if (next.localOperation === 'scope-connect') return { schemaVersion: 'entrorove.scope-connection/1.0', status: 'succeeded',
|
|
87
|
+
flowId: identity.flowId, runnerId: binding.runnerId, scopeEvidence: accepted.scopeEvidence, grantsExecutionAuthority: false,
|
|
88
|
+
nextOperation: 'scope-connect', nextInput: { flowId: identity.flowId, paths: [...identity.targetPaths], importContexts: identity.importContexts }, nextStep: next };
|
|
89
|
+
const resumeOriginalRequest = ['workflow-contract', 'workflow-plan'].includes(next.operation);
|
|
90
|
+
const nextInput = next.operation === 'task-resume' ? { flowId: identity.flowId }
|
|
91
|
+
: { flowId: identity.flowId, operation: next.operation, payload: next.payload ?? {} };
|
|
85
92
|
return { schemaVersion: 'entrorove.scope-connection/1.0', status: 'succeeded', flowId: identity.flowId,
|
|
86
93
|
runnerId: binding.runnerId, scopeEvidence: accepted.scopeEvidence, grantsExecutionAuthority: false,
|
|
87
|
-
nextOperation:
|
|
88
|
-
payload: { targetFiles: [...identity.targetPaths] } } };
|
|
94
|
+
nextOperation: next.operation, ...(resumeOriginalRequest ? { resumeOriginalRequest: true } : { nextInput }), nextStep: next };
|
|
89
95
|
}
|
|
90
96
|
export async function connectScopeRunner(repositoryRoot, input, dependencies) {
|
|
91
97
|
if (!input || Object.keys(input).some(field => !['flowId', 'paths', 'importContexts'].includes(field))
|
|
@@ -286,7 +286,7 @@ async function prepare(input, create = true) {
|
|
|
286
286
|
const root = await realDirectory(input.repositoryRoot, 'ENTROROVE_ROOT_INVALID');
|
|
287
287
|
const home = await realDirectory(input.home ?? os.homedir(), 'ENTROROVE_HOME_INVALID');
|
|
288
288
|
const directory = await privateHierarchy(home, ['.entrorove', 'workflow-local', sha(root).slice(0, 32), plan.flowId, step.stepId], create);
|
|
289
|
-
return { root, plan, lease, step, file: path.join(directory, 'state.json') };
|
|
289
|
+
return { root, home, plan, lease, step, file: path.join(directory, 'state.json') };
|
|
290
290
|
}
|
|
291
291
|
|
|
292
292
|
export async function observeWorkflowLocalStep(rawInput) {
|
|
@@ -323,7 +323,7 @@ export async function runWorkflowLocalStep(rawInput) {
|
|
|
323
323
|
});
|
|
324
324
|
const signer = await loadSigner(authority.root, input.signerConfigPath, authority.plan.runnerKeyId);
|
|
325
325
|
if (typeof approvedRunner.runApprovedPlanObject !== 'function') fail('ENTROROVE_WORKFLOW_RUNNER_UNAVAILABLE', 'approved object plan runner is unavailable');
|
|
326
|
-
execution = await approvedRunner.runApprovedPlanObject({ repositoryRoot: authority.root, executionPlan: authority.step.executionPlan,
|
|
326
|
+
execution = await approvedRunner.runApprovedPlanObject({ repositoryRoot: authority.root, home: authority.home, flowId: authority.plan.flowId, executionPlan: authority.step.executionPlan,
|
|
327
327
|
approvalPath: input.approvalPath, signerConfigPath: input.signerConfigPath, executionDeadlineAt: authority.lease.expiresAt });
|
|
328
328
|
return await finalize(authority, signedResult(authority, execution, signer));
|
|
329
329
|
} catch (error) {
|
|
@@ -7,7 +7,8 @@ import { runWorkflowObservation } from './workflow-observation-runner.mjs';
|
|
|
7
7
|
import { observationRemote, authorizeObservation } from './workflow-observation-remote.mjs';
|
|
8
8
|
|
|
9
9
|
async function connect(input, state, location, dependencies) {
|
|
10
|
-
const
|
|
10
|
+
const fields = ['taskId', 'flowId', 'stepId', 'leaseId', 'generation', ...(Object.hasOwn(input, 'settlementId') ? ['settlementId'] : [])];
|
|
11
|
+
const original = Object.fromEntries(fields.map(key => [key, input[key]]));
|
|
11
12
|
const admitted = state.admitted ?? await observationRemote('admitted', 'member-observation-admit', original, state, location, dependencies);
|
|
12
13
|
const admission = observationAdmission(admitted.admission, input, { allowExpired: Boolean(state.result) });
|
|
13
14
|
await authorizeObservation(admission, dependencies, !state.result);
|
|
@@ -3,14 +3,16 @@ import { canonical, sha256 } from './local-integrity.mjs';
|
|
|
3
3
|
import { mutationId14, mutationPaths14 } from './local-mutation-files.mjs';
|
|
4
4
|
|
|
5
5
|
export const observationFail = message => { throw Object.assign(new Error(message), { code: 'ENTROROVE_LOCAL_OBSERVATION' }); };
|
|
6
|
-
export const
|
|
6
|
+
export const observationReadOnlyIdentityFields = ['taskId', 'flowId', 'stepId', 'leaseId', 'generation'];
|
|
7
|
+
export const observationIdentityFields = [...observationReadOnlyIdentityFields, 'settlementId'];
|
|
7
8
|
export function observationExact(value, fields) {
|
|
8
9
|
if (!value || typeof value !== 'object' || Array.isArray(value)
|
|
9
10
|
|| canonical(Object.keys(value).sort()) !== canonical([...fields].sort())) observationFail('Local observation fields are invalid');
|
|
10
11
|
}
|
|
11
12
|
export function observationInput(input) {
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
const fields = Object.hasOwn(input ?? {}, 'settlementId') ? observationIdentityFields : observationReadOnlyIdentityFields;
|
|
14
|
+
observationExact(input, fields);
|
|
15
|
+
for (const field of fields.filter(field => field !== 'generation')) mutationId14(input[field], field);
|
|
14
16
|
if (!Number.isSafeInteger(input.generation) || input.generation < 1) observationFail('Local observation generation is invalid');
|
|
15
17
|
return input;
|
|
16
18
|
}
|
|
@@ -50,18 +52,23 @@ function signature(value) {
|
|
|
50
52
|
} catch { observationFail('Local observation requires its original pinned public cloud admission signature'); }
|
|
51
53
|
}
|
|
52
54
|
export function observationAdmission(value, input, { allowExpired = false } = {}) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
const readOnly = !Object.hasOwn(input, 'settlementId');
|
|
56
|
+
const fields = readOnly ? observationReadOnlyIdentityFields : observationIdentityFields;
|
|
57
|
+
observationExact(value, [...fields, 'schemaVersion', 'purpose', 'observationId', 'ownerId', 'taskDigest',
|
|
58
|
+
'chainDigest', 'epoch', 'planId', 'planDigest', 'executionPlanSha256', 'repositoryDigest',
|
|
59
|
+
...(readOnly ? ['sourceKind'] : ['settlementDigest']), 'command',
|
|
55
60
|
'sourceManifest', 'protectedFiles', 'issuedAt', 'expiresAt', 'authorityReceipt']);
|
|
56
61
|
signature(value);
|
|
57
62
|
const start = Date.parse(value.issuedAt), end = Date.parse(value.expiresAt);
|
|
58
63
|
if (value.schemaVersion !== 'entrorove.local-observation-admission/1.0' || value.purpose !== 'observe-approved-command'
|
|
59
|
-
|| value.epoch !== 0 ||
|
|
64
|
+
|| value.epoch !== 0 || fields.some(field => value[field] !== input[field])
|
|
65
|
+
|| readOnly && value.sourceKind !== 'original-read-only-plan'
|
|
60
66
|
|| value.repositoryDigest !== sha256(input.repositoryRoot).slice(0, 32) || !Number.isFinite(start) || !Number.isFinite(end)
|
|
61
67
|
|| start > Date.now() || end <= start || end - start > 600_000 || !allowExpired && end <= Date.now()
|
|
62
68
|
|| !/^[a-f0-9-]{36}$/.test(value.ownerId)
|
|
63
|
-
|| ['taskDigest', 'chainDigest', 'planDigest', 'executionPlanSha256',
|
|
64
|
-
|
|
69
|
+
|| ['taskDigest', 'chainDigest', 'planDigest', 'executionPlanSha256', ...(readOnly ? [] : ['settlementDigest'])]
|
|
70
|
+
.some(field => !/^[a-f0-9]{64}$/.test(value[field]))) {
|
|
71
|
+
observationFail('Local observation admission is expired or lost its original task, repository or source binding');
|
|
65
72
|
}
|
|
66
73
|
mutationId14(value.observationId, 'observationId'); mutationId14(value.planId, 'planId');
|
|
67
74
|
command(value.command); files(value.sourceManifest, true); files(value.protectedFiles, false);
|