@entrorove/skill 0.1.88 → 0.1.90
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/CHANGELOG.md +10 -0
- package/DESIGN-GATES.md +9 -5
- package/IDE-EXECUTION.md +2 -2
- package/REUSE-DISCIPLINE.md +1 -1
- package/SKILL.md +5 -2
- package/connected-cli.mjs +2 -1
- package/design-lint.mjs +17 -28
- package/local-css-source.mjs +15 -41
- package/local-delegated-modify-analysis.mjs +10 -7
- package/local-error.mjs +36 -36
- package/local-preserved-mutation-protection.mjs +1 -1
- package/local-source-execution.mjs +4 -4
- package/manifest.json +1 -1
- package/mutation-challenge-refresh.mjs +9 -16
- package/mutation-client.mjs +26 -5
- package/mutation-connection-state.mjs +6 -1
- package/mutation-lease-recovery.mjs +56 -0
- package/package.json +4 -1
- package/release-state.json +22 -19
- package/release.mjs +2 -2
- package/releases.json +12 -0
- package/remote-client.mjs +8 -2
- package/skill-repair-guidance.d.mts +2 -0
- package/skill-repair-guidance.mjs +61 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.90 — 2026-10-23
|
|
4
|
+
|
|
5
|
+
- 删除技能自订的 CSS 设计限制:仅按 PostCSS 解析语法,不再强制 token、样式集中、选择器或资源写法。原 mutation-connect 自动续接一次合格的过期租约,保留原请求和审批;原有数据动态取值不再误当运行时导入拒绝。
|
|
6
|
+
- Remove custom CSS design restrictions and use PostCSS syntax parsing without mandatory tokens, stylesheet organization, frozen selectors or resource notation. Automatically continue one eligible expired lease in the original mutation-connect command, retain original requests and approval, and distinguish existing computed data access from runtime imports.
|
|
7
|
+
|
|
8
|
+
## 0.1.89 — 2026-10-23
|
|
9
|
+
|
|
10
|
+
- 把普通不符合项从终止或一律转人工改为 IDE 修复后继续:统一返回文件或字段、适用标准和下一步,复用原授权及服务端恢复动作;保留原失败结果。过期挑战只恢复已有请求,不再补发必然失败的签发请求。
|
|
11
|
+
- Return actionable IDE repair guidance with files or fields, standards and next steps instead of terminal or blanket human escalation. Reuse original authorization and recovery actions; remove synthetic expired-challenge issue requests while retaining failure evidence.
|
|
12
|
+
|
|
3
13
|
## 0.1.88 — 2026-10-23
|
|
4
14
|
|
|
5
15
|
- 修复 CSS 资源识别误将 overscroll-behavior、scroll-behavior 等标准属性当作外部绑定的问题;仅识别 legacy behavior、-moz-binding 及真实资源语法,保留未核验资源的只读约束。
|
package/DESIGN-GATES.md
CHANGED
|
@@ -87,8 +87,8 @@ image generation. These records are not an independent signature
|
|
|
87
87
|
from the external image/design tool. Show the actual host tool result and
|
|
88
88
|
mockup/prototype views. Never present an owned test bitmap as a real user design.
|
|
89
89
|
|
|
90
|
-
Rules
|
|
91
|
-
|
|
90
|
+
Rules record UI paths, verification paths, browser identity, preview origin,
|
|
91
|
+
the project's component imports and chosen style/token sources,
|
|
92
92
|
public field projections and executable interaction/layout/style assertions.
|
|
93
93
|
Every page/state/theme mockup needs its own scenario. Each scenario has an
|
|
94
94
|
`entryPageId`, entry `route`, ordered original flow edges, a final mockup index,
|
|
@@ -110,8 +110,9 @@ the combined `workflow-discipline-review`, with the same original proof checks.
|
|
|
110
110
|
|
|
111
111
|
UI paths and UI content are detected without an opt-out flag. Guarded writes,
|
|
112
112
|
batches, deletes/moves and approved runners check the approved baseline. Source
|
|
113
|
-
checks
|
|
114
|
-
|
|
113
|
+
checks report actual syntax errors and inspect private fields, whole-object
|
|
114
|
+
dumping and dynamic UI bypasses. Inline styles, stylesheet locations, tokens
|
|
115
|
+
and CSS values do not create SDK permission requirements.
|
|
115
116
|
Framework component libraries require their approved standard controls.
|
|
116
117
|
Put new appearance rules in the approved central style/token files.
|
|
117
118
|
|
|
@@ -163,7 +164,10 @@ An expired authority never renews itself. These gates cannot repair or invent
|
|
|
163
164
|
historical missing process results.
|
|
164
165
|
|
|
165
166
|
Static rules cover the implemented HTML/JS/TS/JSX/Vue/Svelte and CSS subset.
|
|
166
|
-
|
|
167
|
+
CSS uses the PostCSS syntax parser; it does not enforce mandatory tokens,
|
|
168
|
+
literal values, selector retention, stylesheet locations, resource notation,
|
|
169
|
+
or a central stylesheet. Inline styles and CSS imports are valid project choices.
|
|
170
|
+
Preprocessors use the project's own compiler. Official research
|
|
167
171
|
does not prove every framework API or language scenario was verified. Browser
|
|
168
172
|
checks remain necessary; visual judgment follows the original user's retained or explicitly delegated acceptance boundary.
|
|
169
173
|
|
package/IDE-EXECUTION.md
CHANGED
|
@@ -199,7 +199,7 @@ An editing pass does not certify test execution, independent validation, merge o
|
|
|
199
199
|
|
|
200
200
|
An expired unissued member M challenge can recover only through the original `mutation-connect` input. The SDK first recovers any original pending issue with authenticated GET and real feedback acknowledgement. The server requires the exact expired signed challenge, closed acknowledged requests, no issued or unsettled pass, the same original producer and predecessor settlement, and fresh unchanged scope/identity checks. It appends a new signed challenge bound to that expired predecessor. The SDK archives the old challenge, snapshot, attestation and failures before making a new snapshot. Issued passes and unknown requests cannot use this refresh. A lost refresh response permits only its original GET; never edit the journal or repeat an uncertain POST.
|
|
201
201
|
|
|
202
|
-
If the original execution lease has also expired,
|
|
202
|
+
If the original execution lease has also expired, `mutation-connect` automatically reads the original `task-resume` guidance and performs one eligible `execution-claim` recovery or continuation before reconnecting with the returned `localAction`. Callers keep their original input; do not manually replace lease IDs or repeat approval. The SDK preserves the old journal and exact request; a lost renewal response is recovered with its original GET rather than another POST. The server verifies the existing signatures and closed feedback and records the old/new lease history. Issued M passes, unknown requests and unresolved processes still use their actual original recovery instead of this renewal.
|
|
203
203
|
|
|
204
204
|
For a decision, include `input.task.interaction` with `type` (`confirm`, `choice`, `multi` or `input`), a `question`, `options` (two or more with unique `id` and `label`, or an empty array for input), and a `callback` containing its `operation` and `payload`. Medium- and high-risk decisions also need a `riskDescription`. The decision waits for an explicit answer; it never chooses a default on timeout.
|
|
205
205
|
|
|
@@ -207,7 +207,7 @@ Use `npx @entrorove/skill status <requestId>` after a connection loss. It perfor
|
|
|
207
207
|
|
|
208
208
|
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, operation groups and `requiresPass` metadata. `local capabilities <group>` filters that same 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.
|
|
209
209
|
|
|
210
|
-
Local CLI
|
|
210
|
+
Local CLI results that need correction use the machine-readable `entrorove.local-error/1.0` envelope `{schemaVersion,status,code,disposition,message,repair}`. The operation's `failed` or `blocked` status records that it has not executed successfully; `disposition: needs-repair` directs the IDE to correct the reported file or field according to `repair.standard`, then follow `repair.nextStep` within the original authorization. Do not terminate the whole task or automatically ask the user again. Unknown outcomes require the original GET before further mutations. Formula `add` and `mul` require at least one argument; `div`, `safeDivide` and `percentOf` need a nonzero denominator (`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`.
|
|
211
211
|
|
|
212
212
|
### Read-only peer status
|
|
213
213
|
|
package/REUSE-DISCIPLINE.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Apply these rules when adding to or changing an existing system. Reuse is assessed inside the current accepted repository; external projects, databases, accounts, sessions and keys are outside scope.
|
|
4
4
|
|
|
5
|
-
UI
|
|
5
|
+
UI work follows the user's requirements and the project's chosen framework. Read [DESIGN-GATES.md](DESIGN-GATES.md) only for an already selected design workflow; do not impose a new design system, generate replacement mockups or repeat approval for an existing authorized edit. CSS checks use the PostCSS parser for syntax only: selectors, properties, values, custom properties, resource notation, inline styles and stylesheet organization are project choices, not SDK permission gates. Preprocessor syntax belongs to the project's native compiler.
|
|
6
6
|
|
|
7
7
|
Snapshot scans stream whole-repository integrity hashes and bound semantic work to 1 MiB per source, 8 MiB of retained source, 512 files and 200,000 syntax nodes. Original target paths and proposed files receive capacity first. A resource-limited non-target source stays in the integrity comparison and is recorded as unresolved; `scanComplete` is false and absent references cannot prove a symbol unused. A target that exceeds a bound returns `ENTROROVE_DISCIPLINE_ANALYSIS_LIMIT` with its path and the exceeded limit. These are execution memory limits, never size-based review exemptions or approval thresholds. Preserve the original flow, signer, scope and queue during recovery. Upgrade with the standard managed installer, then retry the original `scope-connect` input; do not create a replacement task or bypass the host guard with an arbitrary Node environment wrapper.
|
|
8
8
|
|
package/SKILL.md
CHANGED
|
@@ -5,7 +5,7 @@ description: Use the Entrorove SDK for a selected task's planning, calculation,
|
|
|
5
5
|
|
|
6
6
|
# entrorove · 熵巡
|
|
7
7
|
|
|
8
|
-
Version: 0.1.
|
|
8
|
+
Version: 0.1.90.
|
|
9
9
|
|
|
10
10
|
Use Entrorove only for the task and repository selected by the user. Reading or installing it does not select another task. Keep the original account, flow, scope, approvals and failure records. Follow the user's scope, cost limits and prohibitions on tests or image generation. Entrorove is independent of other projects and systems.
|
|
11
11
|
|
|
@@ -20,6 +20,8 @@ Use Entrorove only for the task and repository selected by the user. Reading or
|
|
|
20
20
|
|
|
21
21
|
## Stop wasteful recovery
|
|
22
22
|
|
|
23
|
+
A returned `repair` is the IDE's next work, not a rejection of the whole task. Follow its reported file or field, standard and next step; correct the candidate or prerequisite within the existing authorization and continue the original controlled entry. `needs-repair` replaces terminal failure and automatic human escalation for ordinary nonconformance. Ask the user only for genuinely missing information or an uncovered action. Preserve the original failed result; repair guidance does not claim that the pending operation already executed.
|
|
24
|
+
|
|
23
25
|
A deterministic invalid input or missing prerequisite is not retryable unchanged. Report the exact code and missing field once, fix that prerequisite, then continue the original request. Do not use repeated `task-resume`, fresh nonces or another executor to get past the same refusal.
|
|
24
26
|
|
|
25
27
|
An unknown transmission permits only authenticated GET for its original request ID. Recover and acknowledge that result before another mutation. A capability discovery failure is separate: never poll its `sourceRequestId` as business execution. Retain the journal, request history and recovery budget; a restart does not reset them. See [recovery contracts](IDE-EXECUTION.md) only for the reported failure.
|
|
@@ -28,7 +30,8 @@ For an expired lease, use the exact recovery returned by the service. An expired
|
|
|
28
30
|
|
|
29
31
|
## Read details only when needed
|
|
30
32
|
|
|
31
|
-
- Existing-system edits: [REUSE-DISCIPLINE.md](REUSE-DISCIPLINE.md). Reuse the current implementation; inspect only the changed unit and relevant dependencies.
|
|
33
|
+
- Existing-system edits: [REUSE-DISCIPLINE.md](REUSE-DISCIPLINE.md). Reuse the current implementation; inspect only the changed unit and relevant dependencies. CSS checks use the PostCSS parser for syntax, without mandatory styling conventions, frozen selectors or resource notation. Design choices belong to the user and project.
|
|
34
|
+
- `mutation-connect` handles one server-approved original lease recovery automatically when its lease has expired. Keep the original input and journal; no manual lease parameter replacement or repeated approval is needed. An unknown pending operation is recovered with its original GET before any renewal.
|
|
32
35
|
- UI design or acceptance: [DESIGN-GATES.md](DESIGN-GATES.md). Recover and reuse the original design, images, receipts and approval. Generate only a missing baseline or an explicitly requested visual change; a repair, new message or schema correction never requires another image. Existing applicable approval covers implementation fixes within its scope.
|
|
33
36
|
- Native entrypoints: `native <domain> <operation>`; domains are aimlock, blueprint, calctool, swarm, validator, mergeguard, archguard and confirm-protocol. `local capabilities` describes local scope, snapshot, mutation and evidence contracts. Never guess signatures or synthesize successful receipts.
|
|
34
37
|
- Credentials, routing, callbacks, scope changes, queues and lifecycle recovery: read the relevant section of [IDE-EXECUTION.md](IDE-EXECUTION.md), not the whole manual by default. Keep credentials in the private account store.
|
package/connected-cli.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { runMutationConnect, runMutationSettle } from './mutation-client.mjs';
|
|
|
3
3
|
import { runWorkflowObserveStep } from './workflow-observation-client.mjs';
|
|
4
4
|
import { runScopeAmend } from './scope-amendment-client.mjs';
|
|
5
5
|
import { runScopeContext } from './scope-context-client.mjs';
|
|
6
|
+
import { withSkillRepair } from './skill-repair-guidance.mjs';
|
|
6
7
|
|
|
7
8
|
const commands = Object.freeze({
|
|
8
9
|
'scope-connect': runScopeConnect,
|
|
@@ -16,7 +17,7 @@ export const isConnectedCommand = command => Object.hasOwn(commands, command);
|
|
|
16
17
|
|
|
17
18
|
export async function runConnectedCommand(command, root, input, dependencies) {
|
|
18
19
|
if (!isConnectedCommand(command)) throw new Error('Unsupported connected command');
|
|
19
|
-
const result = await commands[command](root, input, dependencies);
|
|
20
|
+
const result = withSkillRepair(await commands[command](root, input, dependencies), { operation: command, input });
|
|
20
21
|
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
21
22
|
const expected = command === 'mutation-settle' ? ['reported', 'succeeded']
|
|
22
23
|
: command === 'workflow-observe-step' ? ['succeeded', 'local-accepted'] : ['succeeded'];
|
package/design-lint.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
|
-
import
|
|
2
|
+
import { parseCssSource } from './local-css-source.mjs';
|
|
3
3
|
import { parse as parseVue } from '@vue/compiler-sfc';
|
|
4
4
|
import { designFail, validateDesignPolicy } from './design-policy.mjs';
|
|
5
5
|
import { designUiAsset } from './design-ui.mjs';
|
|
@@ -7,28 +7,18 @@ import { uniConditionalVariants } from './local-scope-uni-conditional.mjs';
|
|
|
7
7
|
|
|
8
8
|
export { designUiCandidate } from './design-ui.mjs';
|
|
9
9
|
const issue = (rule, detail) => ({ rule, detail });
|
|
10
|
-
function styleIssues(source, name
|
|
11
|
-
|
|
12
|
-
catch
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const { prop, value } = declaration;
|
|
17
|
-
if (declaration.important) found.push(issue('important', declaration.toString()));
|
|
18
|
-
if (prop.startsWith('--')) {
|
|
19
|
-
if (name !== rules.tokenPath || rules.tokens[prop] !== value) found.push(issue('unapproved-token', declaration.toString()));
|
|
20
|
-
} else if (/^(?:color|background(?:-color)?|font(?:-size|-family|-weight)?|padding.*|margin.*|gap|.*-gap|border(?:-color|-radius)?|box-shadow)$/.test(prop)
|
|
21
|
-
&& !/^(?:0|none|inherit|initial|unset|transparent|currentColor|auto)$/.test(value)
|
|
22
|
-
&& !/^var\(--[a-z0-9-]+\)$/.test(value)) found.push(issue('design-token-required', declaration.toString()));
|
|
23
|
-
for (const [, token] of value.matchAll(/var\((--[a-z0-9-]+)/g)) if (!Object.hasOwn(rules.tokens, token)) found.push(issue('unknown-token', token));
|
|
24
|
-
});
|
|
25
|
-
return found;
|
|
10
|
+
function styleIssues(source, name) {
|
|
11
|
+
try { parseCssSource(name, source); return []; }
|
|
12
|
+
catch (error) {
|
|
13
|
+
if (error.code !== 'ENTROROVE_CSS_SYNTAX_INVALID') throw error;
|
|
14
|
+
return [{ ...issue('css-parse', error.message), path: name, line: error.line, column: error.column }];
|
|
15
|
+
}
|
|
26
16
|
}
|
|
27
17
|
function markupIssues(markup, rules, imports) {
|
|
28
18
|
const found = [];
|
|
29
19
|
for (const match of markup.matchAll(/<([A-Za-z][\w.-]*)([^<>]*?)\/?\s*>/g)) {
|
|
30
20
|
const [, tag, attributes] = match;
|
|
31
|
-
if (/\b(?:
|
|
21
|
+
if (/\b(?:v-html|dangerouslySetInnerHTML)\s*=|\{\s*\.\.\./.test(attributes)) found.push(issue('unsafe-markup-projection', match[0]));
|
|
32
22
|
const replacement = rules.replacements.find(item => item.native === tag.toLowerCase());
|
|
33
23
|
if (replacement && tag !== replacement.component) found.push(issue('framework-component-required', match[0]));
|
|
34
24
|
if (tag[0] === tag[0].toLowerCase()) {
|
|
@@ -50,13 +40,11 @@ function scriptIssues(source, name, rules) {
|
|
|
50
40
|
if (!approved.exports.includes(exported)) found.push(issue('unapproved-component-import', node.getText(tree)));
|
|
51
41
|
else imports.add(element.name.text);
|
|
52
42
|
}
|
|
53
|
-
if (
|
|
54
|
-
&& !rules.stylePaths.some(file => module.endsWith(file) || file.endsWith(module.replace(/^\.\//, '')))) found.push(issue('unapproved-style-import', node.getText(tree)));
|
|
55
|
-
if (!module.startsWith('.') && !approved && !rules.frameworkStyleImports?.includes(module)
|
|
43
|
+
if (!module.startsWith('.') && !approved && !rules.frameworkStyleImports?.includes(module) && !/\.(?:css|scss|less|sass|styl)$/.test(module)
|
|
56
44
|
&& !['react', 'react-dom', 'react-dom/client', 'vue', 'svelte', 'svelte/store', 'next/link', 'next/image'].includes(module)) found.push(issue('unapproved-ui-import', node.getText(tree)));
|
|
57
45
|
}
|
|
58
|
-
if (ts.isJsxSpreadAttribute(node) || ts.isJsxAttribute(node) &&
|
|
59
|
-
if (ts.isPropertyAccessExpression(node) && ['innerHTML', 'outerHTML', '
|
|
46
|
+
if (ts.isJsxSpreadAttribute(node) || ts.isJsxAttribute(node) && node.name.getText(tree) === 'dangerouslySetInnerHTML') found.push(issue('unsafe-markup-projection', node.getText(tree)));
|
|
47
|
+
if (ts.isPropertyAccessExpression(node) && ['innerHTML', 'outerHTML', 'insertAdjacentHTML', 'createElement'].includes(node.name.text)) found.push(issue('dynamic-ui-bypass', node.getText(tree)));
|
|
60
48
|
if (ts.isIdentifier(node) && ['eval', 'Function', 'styled'].includes(node.text) || ts.isCallExpression(node)
|
|
61
49
|
&& /^(?:Object\.(?:entries|values|keys)|JSON\.stringify)$/.test(node.expression.getText(tree))
|
|
62
50
|
&& (!ts.isObjectLiteralExpression(node.arguments[0] ?? tree)
|
|
@@ -69,8 +57,9 @@ function scriptIssues(source, name, rules) {
|
|
|
69
57
|
function lintSource(name, source, rules) {
|
|
70
58
|
if (designUiAsset(name)) return [];
|
|
71
59
|
if (typeof source !== 'string' || Buffer.byteLength(source) > 2_097_152) designFail('LINT_LIMIT', 'UI lint requires bounded UTF-8 source');
|
|
72
|
-
if (/\.(?:scss|sass|less|styl
|
|
73
|
-
if (/\.
|
|
60
|
+
if (/\.(?:scss|sass|less|styl)$/i.test(name)) return [];
|
|
61
|
+
if (/\.(?:wxml|axml)$/i.test(name)) return [issue('unsupported-ui-language', name)];
|
|
62
|
+
if (/\.(?:css|wxss|acss)$/i.test(name)) return styleIssues(source, name);
|
|
74
63
|
let markup = source, script = source, styles = [];
|
|
75
64
|
if (/\.vue$/i.test(name)) {
|
|
76
65
|
const { descriptor, errors } = parseVue(source);
|
|
@@ -83,7 +72,7 @@ function lintSource(name, source, rules) {
|
|
|
83
72
|
markup = source.replace(/<(?:script|style)\b[^>]*>[\s\S]*?<\/(?:script|style)>/g, '');
|
|
84
73
|
}
|
|
85
74
|
const parsed = scriptIssues(script, name, rules), found = [...parsed.found, ...markupIssues(markup, rules, parsed.imports)];
|
|
86
|
-
if (
|
|
75
|
+
for (const style of styles) if (!style.lang || style.lang === 'css') found.push(...styleIssues(style.content, name));
|
|
87
76
|
return found;
|
|
88
77
|
}
|
|
89
78
|
export function designLint(name, source, rules) {
|
|
@@ -114,11 +103,11 @@ export function assertDesignLint(policy, name, before, after, approvedBefore = b
|
|
|
114
103
|
const key = JSON.stringify(problem), count = retained.get(key) ?? 0;
|
|
115
104
|
if (count) { retained.set(key, count - 1); return false; } return true;
|
|
116
105
|
});
|
|
117
|
-
if (introduced.length) designFail('LINT', `UI
|
|
106
|
+
if (introduced.length) designFail('LINT', `Correct the reported UI issue before continuing: ${introduced.map(item => item.detail).join('; ')}`);
|
|
118
107
|
return { introduced: 0 };
|
|
119
108
|
}
|
|
120
109
|
function privateFieldIssues(source, name, policy) {
|
|
121
|
-
if (designUiAsset(name)) return [];
|
|
110
|
+
if (designUiAsset(name) || /\.(?:css|wxss|acss|scss|sass|less|styl)$/i.test(name)) return [];
|
|
122
111
|
const fields = new Set(policy.site.pages.flatMap(page => page.internalFields)), found = [];
|
|
123
112
|
if (!fields.size) return found;
|
|
124
113
|
const tree = ts.createSourceFile(name, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
|
package/local-css-source.mjs
CHANGED
|
@@ -1,46 +1,20 @@
|
|
|
1
1
|
import postcss from 'postcss';
|
|
2
2
|
|
|
3
|
-
function
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
try { root = postcss.parse(source, { from: path }); }
|
|
15
|
-
catch (error) { invalid(path, error.line, error.column); }
|
|
16
|
-
const definitions = new Map(), resources = []; let visited = 0;
|
|
17
|
-
root.walk(node => {
|
|
18
|
-
if (++visited > 100_000) invalid(path);
|
|
19
|
-
if (node.type === 'rule') increment(definitions, `selector:${node.selector}`);
|
|
20
|
-
if (node.type === 'decl' && node.prop.startsWith('--')) increment(definitions, `property:${node.prop}`);
|
|
21
|
-
if (node.type === 'atrule') {
|
|
22
|
-
const name = node.name.toLowerCase();
|
|
23
|
-
increment(definitions, `at-rule:${name}:${['media', 'supports', 'container'].includes(name) ? '' : node.params}`);
|
|
24
|
-
if (['import', 'namespace'].includes(name) || name.includes('\\') || resourceValue(node.params)) {
|
|
25
|
-
resources.push({ kind: 'at-rule', name: node.name, value: node.params, line: node.source.start.line, column: node.source.start.column });
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
if (node.type === 'decl' && (/^(?:behavior|-moz-binding)$/i.test(node.prop) || resourceValue(node.value))) {
|
|
29
|
-
resources.push({ kind: 'declaration', name: node.prop, value: node.value, line: node.source.start.line, column: node.source.start.column });
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
return { definitions, resources };
|
|
3
|
+
export function parseCssSource(path, source) {
|
|
4
|
+
if (typeof source !== 'string' || Buffer.byteLength(source) > 2_097_152) {
|
|
5
|
+
throw Object.assign(new Error('CSS input must be UTF-8 text within the 2 MiB parser limit'),
|
|
6
|
+
{ code: 'ENTROROVE_INPUT_INVALID', path });
|
|
7
|
+
}
|
|
8
|
+
try { return postcss.parse(source, { from: path }); }
|
|
9
|
+
catch (error) {
|
|
10
|
+
const line = error.line ?? 1, column = error.column ?? 1;
|
|
11
|
+
throw Object.assign(new Error(`${path}:${line}:${column}: ${error.reason ?? 'Invalid CSS syntax'}`),
|
|
12
|
+
{ code: 'ENTROROVE_CSS_SYNTAX_INVALID', path, line, column });
|
|
13
|
+
}
|
|
33
14
|
}
|
|
34
15
|
export function cssCandidateImports(path, source) {
|
|
35
|
-
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
return { edges: [], dynamic:
|
|
39
|
-
...(resources.length ? { unresolvedImports: resources.map(({ line, column }) => ({ path, line, column,
|
|
40
|
-
kind: 'CSSResource', reason: 'css-resource-reference-not-inspected',
|
|
41
|
-
message: 'Stylesheet resource references require actual verified dependency evidence; this source remains protected read-only' })) } : {}) };
|
|
42
|
-
}
|
|
43
|
-
export function sameCssResourceBindings(before, after) {
|
|
44
|
-
const binding = value => JSON.stringify(value.resources.map(({ kind, name, value }) => ({ kind, name, value })));
|
|
45
|
-
return binding(before) === binding(after);
|
|
16
|
+
parseCssSource(path, source);
|
|
17
|
+
// Stylesheets have no executable JavaScript module imports. CSS resource
|
|
18
|
+
// notation and design choices are not reasons to refuse a source edit.
|
|
19
|
+
return { edges: [], dynamic: false };
|
|
46
20
|
}
|
|
@@ -4,10 +4,10 @@ import { canonical } from './local-integrity.mjs';
|
|
|
4
4
|
import { createScopeImportResolver } from './local-scope-import-expressions.mjs';
|
|
5
5
|
import { isDynamicFunctionReference, isTypeOnlyReference } from './local-typescript-function-reference.mjs';
|
|
6
6
|
import { uniConditionalVariants } from './local-scope-uni-conditional.mjs';
|
|
7
|
-
import {
|
|
7
|
+
import { parseCssSource } from './local-css-source.mjs';
|
|
8
8
|
|
|
9
|
-
const fail = () => { throw Object.assign(new Error('Preserved modification changed an existing definition, public contract, dependency, or unsupported source boundary'),
|
|
10
|
-
{ code: 'ENTROROVE_MUTATION_DELEGATION_CHANGE' }); };
|
|
9
|
+
const fail = (details = {}) => { throw Object.assign(new Error('Preserved modification changed an existing definition, public contract, dependency, or unsupported source boundary'),
|
|
10
|
+
{ code: 'ENTROROVE_MUTATION_DELEGATION_CHANGE', ...details }); };
|
|
11
11
|
const printer = ts.createPrinter({ removeComments: true });
|
|
12
12
|
const print = node => node ? printer.printNode(ts.EmitHint.Unspecified, node, node.getSourceFile()) : null;
|
|
13
13
|
function unwrap(node) {
|
|
@@ -234,9 +234,7 @@ export function assertDelegatedSourceModification(input) {
|
|
|
234
234
|
|| Buffer.byteLength(before) > 1_048_576 || Buffer.byteLength(after) > 1_048_576) fail();
|
|
235
235
|
try {
|
|
236
236
|
if (/\.css$/i.test(path)) {
|
|
237
|
-
|
|
238
|
-
if (!sameCssResourceBindings(original, candidate)) fail();
|
|
239
|
-
retained(original.definitions, candidate.definitions);
|
|
237
|
+
parseCssSource(path, after);
|
|
240
238
|
return;
|
|
241
239
|
}
|
|
242
240
|
if (!/\.vue$/i.test(path)) return compareSource(path, before, after);
|
|
@@ -244,5 +242,10 @@ export function assertDelegatedSourceModification(input) {
|
|
|
244
242
|
if (canonical(original.directives) !== canonical(candidate.directives)
|
|
245
243
|
|| canonical(original.variants.map(value => value.key)) !== canonical(candidate.variants.map(value => value.key))) fail();
|
|
246
244
|
for (let index = 0; index < original.variants.length; index++) compareSource(path, original.variants[index].source, candidate.variants[index].source);
|
|
247
|
-
} catch
|
|
245
|
+
} catch (error) {
|
|
246
|
+
if (['ENTROROVE_MUTATION_DELEGATION_CHANGE', 'ENTROROVE_IMPORT_INVALID', 'ENTROROVE_CSS_SYNTAX_INVALID'].includes(error?.code)) {
|
|
247
|
+
throw Object.assign(error, { path });
|
|
248
|
+
}
|
|
249
|
+
fail({ path });
|
|
250
|
+
}
|
|
248
251
|
}
|
package/local-error.mjs
CHANGED
|
@@ -1,33 +1,35 @@
|
|
|
1
|
+
import { skillRepairGuidance } from './skill-repair-guidance.mjs';
|
|
1
2
|
export const LOCAL_ERROR_SCHEMA = 'entrorove.local-error/1.0';
|
|
2
3
|
export const LOCAL_ERROR_CODE_CATALOG = Object.freeze({
|
|
3
|
-
ENTROROVE_INPUT_INVALID: Object.freeze({ disposition: '
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
4
|
+
ENTROROVE_INPUT_INVALID: Object.freeze({ disposition: 'needs-repair', stability: 'stable' }),
|
|
5
|
+
ENTROROVE_CSS_SYNTAX_INVALID: Object.freeze({ disposition: 'needs-repair', stability: 'stable' }),
|
|
6
|
+
ENTROROVE_OPERATION_UNKNOWN: Object.freeze({ disposition: 'needs-repair', stability: 'stable' }),
|
|
7
|
+
ENTROROVE_LOCAL_FAILURE: Object.freeze({ disposition: 'needs-repair', stability: 'stable' }),
|
|
8
|
+
DIV_ZERO: Object.freeze({ disposition: 'needs-repair', stability: 'stable' }),
|
|
9
|
+
ENTROROVE_MUTATE_NOT_ACCEPTED: Object.freeze({ disposition: 'needs-repair', stability: 'stable' }),
|
|
10
|
+
ENTROROVE_MUTATION_AUTHORITY_REQUIRED: Object.freeze({ disposition: 'needs-repair', stability: 'stable' }),
|
|
11
|
+
ENTROROVE_BUDGET_LEASE_EXPIRED: Object.freeze({ disposition: 'needs-repair', stability: 'stable' }),
|
|
12
|
+
ENTROROVE_GIT_READER_UNSUPPORTED: Object.freeze({ disposition: 'needs-repair', stability: 'stable' }),
|
|
13
|
+
ENTROROVE_GIT_UNAVAILABLE: Object.freeze({ disposition: 'needs-repair', stability: 'stable' }),
|
|
14
|
+
ENTROROVE_DISCIPLINE_NEEDS_HUMAN: Object.freeze({ disposition: 'needs-repair', stability: 'stable' }),
|
|
15
|
+
ENTROROVE_DISCIPLINE_USAGE_UNKNOWN: Object.freeze({ disposition: 'needs-repair', stability: 'stable' }),
|
|
16
|
+
ENTROROVE_DISCIPLINE_REVIEW_REQUIRED: Object.freeze({ disposition: 'needs-repair', stability: 'stable' }),
|
|
17
|
+
ENTROROVE_DISCIPLINE_DATA_SNAPSHOT_REQUIRED: Object.freeze({ disposition: 'needs-repair', stability: 'stable' }),
|
|
18
|
+
ENTROROVE_MUTATION_DELEGATION_CHANGE: Object.freeze({ disposition: 'needs-repair', stability: 'stable' }),
|
|
19
|
+
ENTROROVE_MUTATION_DELEGATION_INVALID: Object.freeze({ disposition: 'needs-repair', stability: 'stable' }),
|
|
20
|
+
ENTROROVE_IDE_GUARD_REQUIRED: Object.freeze({ disposition: 'needs-repair', stability: 'stable' }),
|
|
21
|
+
ENTROROVE_IDE_GUARD_UNSUPPORTED: Object.freeze({ disposition: 'needs-repair', stability: 'stable' }),
|
|
22
|
+
ENTROROVE_DESIGN_REQUIRED: Object.freeze({ disposition: 'needs-repair', stability: 'stable' }),
|
|
23
|
+
ENTROROVE_DESIGN_APPROVAL_REQUIRED: Object.freeze({ disposition: 'needs-repair', stability: 'stable' }),
|
|
24
|
+
ENTROROVE_DESIGN_LINT: Object.freeze({ disposition: 'needs-repair', stability: 'stable' }),
|
|
25
|
+
ENTROROVE_DESIGN_ACCEPTANCE: Object.freeze({ disposition: 'needs-repair', stability: 'stable' }),
|
|
26
|
+
ENTROROVE_PACKAGE_DOWNGRADE: Object.freeze({ disposition: 'needs-repair', stability: 'stable' }),
|
|
27
|
+
ENTROROVE_PACKAGE_VERSION_CONFLICT: Object.freeze({ disposition: 'needs-repair', stability: 'stable' }),
|
|
28
|
+
ENTROROVE_PACKAGE_VERSION_UNKNOWN: Object.freeze({ disposition: 'needs-repair', stability: 'stable' }),
|
|
29
|
+
ENTROROVE_PACKAGE_IDENTITY_CHANGED: Object.freeze({ disposition: 'needs-repair', stability: 'stable' }),
|
|
30
|
+
ENTROROVE_PACKAGE_PROVENANCE_REQUIRED: Object.freeze({ disposition: 'needs-repair', stability: 'stable' }),
|
|
31
|
+
ENTROROVE_ROLLBACK_PROOF_REQUIRED: Object.freeze({ disposition: 'needs-repair', stability: 'stable' }),
|
|
32
|
+
ENTROROVE_ROLLBACK_HISTORY_INVALID: Object.freeze({ disposition: 'needs-repair', stability: 'stable' }),
|
|
31
33
|
});
|
|
32
34
|
|
|
33
35
|
const BLOCKED_CODES = new Set([
|
|
@@ -57,17 +59,15 @@ export function localErrorStatus(error) {
|
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
export function localErrorDisposition(code) {
|
|
60
|
-
|
|
61
|
-
if (catalogued) return catalogued.disposition;
|
|
62
|
-
if (/_LEASE_EXPIRED$|_BUSY$|_RECONNECT/.test(code)) return 'retriable';
|
|
63
|
-
if (BLOCKED_CODES.has(code) || /(?:AUTHORITY_REQUIRED|NOT_ACCEPTED|REVOKED)$/.test(code)) return 'needs-human';
|
|
64
|
-
return 'terminal';
|
|
62
|
+
return LOCAL_ERROR_CODE_CATALOG[code]?.disposition ?? 'needs-repair';
|
|
65
63
|
}
|
|
66
64
|
|
|
67
|
-
export function localErrorEnvelope(error, fallbackCode = 'ENTROROVE_LOCAL_FAILURE') {
|
|
68
|
-
const code = typeof error?.code === 'string' && error.code ? error.code
|
|
65
|
+
export function localErrorEnvelope(error, fallbackCode = 'ENTROROVE_LOCAL_FAILURE', context = {}) {
|
|
66
|
+
const code = typeof error?.code === 'string' && error.code ? error.code
|
|
67
|
+
: Array.isArray(error?.issues) ? 'ENTROROVE_INPUT_INVALID' : fallbackCode;
|
|
69
68
|
const message = typeof error?.message === 'string' && error.message ? error.message : 'Local operation failed';
|
|
70
69
|
const report = /^ENTROROVE_DESIGN_/.test(code) && typeof error?.reportPath === 'string' && /^\//.test(error.reportPath)
|
|
71
70
|
&& /^[a-f0-9]{64}$/.test(error.reportDigest ?? '') ? { reportPath: error.reportPath, reportDigest: error.reportDigest } : {};
|
|
72
|
-
return { schemaVersion: LOCAL_ERROR_SCHEMA, status: localErrorStatus({ ...error, code }), code,
|
|
71
|
+
return { schemaVersion: LOCAL_ERROR_SCHEMA, status: localErrorStatus({ ...error, code }), code,
|
|
72
|
+
disposition: localErrorDisposition(code), message, ...report, repair: skillRepairGuidance({ ...error, code }, context) };
|
|
73
73
|
}
|
|
@@ -43,7 +43,7 @@ export async function capturePreservedMutationProtection(input, root, authority,
|
|
|
43
43
|
preservedMutationFail('Preserved mutation targets cannot contain unknown or changed imports');
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
-
const unknown = review.analysis.unresolved.find(item => authority.paths.includes(item.path) && /(?:^import-|dynamic-import
|
|
46
|
+
const unknown = review.analysis.unresolved.find(item => authority.paths.includes(item.path) && /(?:^import-|dynamic-import)/.test(item.reason));
|
|
47
47
|
if (unknown && delegation.purpose === 'preserved-existing-modify') unknownTarget(unknown.path, unknown);
|
|
48
48
|
const result = [];
|
|
49
49
|
for (const expected of delegation.protectedFiles) result.push(await observe(input, root, expected));
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { prerequisiteFields } from './service-prerequisite.mjs';
|
|
2
|
+
import { localErrorEnvelope } from './local-error.mjs';
|
|
2
3
|
export { startFsTrace } from './fs-request-trace.mjs';
|
|
3
4
|
|
|
4
5
|
const flag = '--local-source';
|
|
@@ -40,8 +41,7 @@ export function localSourceInstall(target, managed, bundle, { explicit, hostId,
|
|
|
40
41
|
}
|
|
41
42
|
export function cliErrorText(error) {
|
|
42
43
|
const prerequisite = prerequisiteFields(error);
|
|
43
|
-
return
|
|
44
|
-
|
|
45
|
-
...(error.outcomeUnknown ? { outcomeUnknown: true, repeatPost: false } : {}) })
|
|
46
|
-
: error instanceof SyntaxError ? 'Invalid JSON data' : error.message;
|
|
44
|
+
return JSON.stringify({ ...localErrorEnvelope(error), ...prerequisite,
|
|
45
|
+
...(error.requestId ? { requestId: error.requestId } : {}),
|
|
46
|
+
...(error.outcomeUnknown ? { outcomeUnknown: true, repeatPost: false } : {}) });
|
|
47
47
|
}
|
package/manifest.json
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { canonical, sha256 } from './local-integrity.mjs';
|
|
2
|
-
import { buildInvocation } from './remote-client.mjs';
|
|
3
2
|
import { mutationFail14 } from './local-mutation-files.mjs';
|
|
4
3
|
import { consumeRecovery } from './local-recovery-budget.mjs';
|
|
5
4
|
|
|
@@ -12,22 +11,16 @@ export async function refreshExpiredMutationChallenge(context, callbacks) {
|
|
|
12
11
|
if (old.verificationMode !== 'member-local' || !old.writeDelegation || old.runnerKeyId !== null) {
|
|
13
12
|
fail('Expired challenge recovery requires the original member-local editing authority');
|
|
14
13
|
}
|
|
15
|
-
if (
|
|
16
|
-
const input =
|
|
17
|
-
if (
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
try { await remote('issued', 'mutation-pass-issue', input, state, location, dependencies); }
|
|
24
|
-
catch (error) {
|
|
25
|
-
if (state.pending || error.output?.status !== 'blocked'
|
|
26
|
-
|| !['ENTROROVE_MUTATION_LEASE', 'ENTROROVE_MUTATION_EXPIRED'].includes(error.output.code)
|
|
27
|
-
|| state.feedback?.acknowledgement?.stored !== true) throw error;
|
|
28
|
-
}
|
|
29
|
-
if (state.issued) return state.challenged;
|
|
14
|
+
if (state.pending?.stage === 'issued') {
|
|
15
|
+
const input = state.pending.input;
|
|
16
|
+
if (input.challengeId !== old.challengeId) fail('Recover the pending original challenge before refresh');
|
|
17
|
+
try { await remote('issued', 'mutation-pass-issue', input, state, location, dependencies, { readOnlyRecovery: true }); }
|
|
18
|
+
catch (error) {
|
|
19
|
+
if (state.pending || error.output?.status !== 'blocked'
|
|
20
|
+
|| !['ENTROROVE_MUTATION_LEASE', 'ENTROROVE_MUTATION_EXPIRED'].includes(error.output.code)
|
|
21
|
+
|| state.feedback?.acknowledgement?.stored !== true) throw error;
|
|
30
22
|
}
|
|
23
|
+
if (state.issued) return state.challenged;
|
|
31
24
|
}
|
|
32
25
|
if (state.pending && state.pending.stage !== 'refreshed') fail('Recover only the original pending mutation request before challenge refresh');
|
|
33
26
|
if (!Array.isArray(state.attempts ?? []) || (state.attempts?.length ?? 0) >= 512) fail('Original mutation challenge history reached its bound');
|
package/mutation-client.mjs
CHANGED
|
@@ -20,9 +20,11 @@ import { scopeAmendmentRefreshAction } from './scope-amendment-reference.mjs';
|
|
|
20
20
|
import { readMutationConnection as readConnection } from './mutation-connection-state.mjs';
|
|
21
21
|
import { readMutationEnvironmentObservation } from './local-mutation-environment-observation.mjs';
|
|
22
22
|
import { refreshExpiredMutationChallenge } from './mutation-challenge-refresh.mjs';
|
|
23
|
+
import { recoverMutationLease, mutationLeaseExpired } from './mutation-lease-recovery.mjs';
|
|
23
24
|
import { scopeContextObservation, scopeContextSnapshotInput } from './scope-context-observation.mjs';
|
|
24
25
|
import { prerequisiteError, retainPrerequisite, retryKnownPrerequisite, prerequisiteFields } from './service-prerequisite.mjs';
|
|
25
26
|
import { consumeRecovery, recoveryFailureFields } from './local-recovery-budget.mjs';
|
|
27
|
+
import { skillRepairGuidance } from './skill-repair-guidance.mjs';
|
|
26
28
|
|
|
27
29
|
const fields = ['taskId', 'flowId', 'stepId', 'leaseId', 'generation', 'runnerId', 'paths'];
|
|
28
30
|
const fail = message => mutationFail14('ENTROROVE_MUTATION_CONNECTION_BINDING', message);
|
|
@@ -64,16 +66,17 @@ async function knownNotSent(error, pending, home) {
|
|
|
64
66
|
return !records.length || records.at(-1).event === 'not-sent';
|
|
65
67
|
} catch (cause) { return cause.code === 'ENOENT'; }
|
|
66
68
|
}
|
|
67
|
-
async function remote(stage, operation, input, state, location, dependencies) {
|
|
69
|
+
async function remote(stage, operation, input, state, location, dependencies, { readOnlyRecovery = false } = {}) {
|
|
68
70
|
const requestId = buildInvocation(operation, input).requestId;
|
|
69
71
|
let response, result;
|
|
70
72
|
const existing = Boolean(state.pending);
|
|
73
|
+
if (readOnlyRecovery && !existing) fail('Read-only recovery requires its original pending request');
|
|
71
74
|
if (existing) {
|
|
72
75
|
if (state.pending.stage !== stage || state.pending.operation !== operation || canonical(state.pending.input) !== canonical(input)) {
|
|
73
76
|
fail('Recover the original mutation request before another operation or input');
|
|
74
77
|
}
|
|
75
78
|
}
|
|
76
|
-
if (existing && !await retryKnownPrerequisite(state, location, save)) {
|
|
79
|
+
if (existing && (readOnlyRecovery || !await retryKnownPrerequisite(state, location, save))) {
|
|
77
80
|
consumeRecovery(state, 'status'); await save(location, state);
|
|
78
81
|
({ response, result } = await recoverRemote(state.pending, dependencies));
|
|
79
82
|
} else {
|
|
@@ -210,7 +213,7 @@ function connected(identity, state) {
|
|
|
210
213
|
purpose: state.issued.pass.writeDelegation.purpose, grantsExecutionAuthority: false, grantsValidatorAuthority: false,
|
|
211
214
|
...(state.feedback ? { feedback: state.feedback } : {}) };
|
|
212
215
|
}
|
|
213
|
-
export async function connectMutation(repositoryRoot, input, dependencies) {
|
|
216
|
+
export async function connectMutation(repositoryRoot, input, dependencies, { recoverLease = true } = {}) {
|
|
214
217
|
const original = inputIdentity(input);
|
|
215
218
|
const local = { repositoryRoot, home: dependencies.home, taskId: original.taskId, flowId: original.flowId, stepId: original.stepId };
|
|
216
219
|
const location = await preservedMutationLocation(local);
|
|
@@ -226,7 +229,23 @@ export async function connectMutation(repositoryRoot, input, dependencies) {
|
|
|
226
229
|
const identity = { ...original, repositoryRoot: location.root, apiOrigin: trustedOrigin(dependencies.apiOrigin()), mutationKeyId: mutator.mutationKeyId };
|
|
227
230
|
const directory = path.join(location.directory, 'connections'); await privateMutationDirectory14(directory, { privateFrom: location.stateRoot });
|
|
228
231
|
const file = path.join(directory, `${sha256(canonical(identity))}.json`), journal = { ...location, file };
|
|
229
|
-
|
|
232
|
+
const outcome = await withStateLock(file, async () => {
|
|
233
|
+
const state = await readConnection(journal, identity);
|
|
234
|
+
const recover = () => recoverMutationLease({ state, identity: original, location: journal, dependencies }, { remote, save });
|
|
235
|
+
if (recoverLease) {
|
|
236
|
+
const renewed = await recover();
|
|
237
|
+
if (renewed) return { renewed };
|
|
238
|
+
}
|
|
239
|
+
try { return { result: await connect(identity, observed, mutator, journal, dependencies, contextAuthority) }; }
|
|
240
|
+
catch (error) {
|
|
241
|
+
if (!recoverLease || !mutationLeaseExpired(error)) throw error;
|
|
242
|
+
const current = await readConnection(journal, identity);
|
|
243
|
+
const renewed = await recoverMutationLease({ state: current, identity: original, location: journal, dependencies }, { remote, save });
|
|
244
|
+
if (!renewed) throw error;
|
|
245
|
+
return { renewed };
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
return outcome.renewed ? connectMutation(repositoryRoot, outcome.renewed, dependencies, { recoverLease: false }) : outcome.result;
|
|
230
249
|
}
|
|
231
250
|
function settlementBinding(output, pass, receipt, expectedSettlementId) {
|
|
232
251
|
if (output?.settled !== true || typeof output.settlementId !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(output.settlementId)
|
|
@@ -307,6 +326,7 @@ async function runMutationClient(operation, repositoryRoot, input, dependencies,
|
|
|
307
326
|
...prerequisiteFields(error),
|
|
308
327
|
message: `Original mutation request outcome is unknown; rerun ${operation} with the same input to recover only its original GET, without repeating its POST`,
|
|
309
328
|
nextAction: { operation, requestId, method: 'GET', reuseOriginalInput: true, repeatPost: false },
|
|
329
|
+
repair: skillRepairGuidance({ ...error, outcomeUnknown: true }, { operation, input }),
|
|
310
330
|
};
|
|
311
331
|
const typed = typeof error?.code === 'string' && error.code.startsWith('ENTROROVE_');
|
|
312
332
|
const knownBeforeTransmission = preflight || error?.knownBeforeTransmission === true;
|
|
@@ -317,7 +337,8 @@ async function runMutationClient(operation, repositoryRoot, input, dependencies,
|
|
|
317
337
|
...(requestId ? { requestId } : {}), ...(knownBeforeTransmission ? { knownBeforeTransmission: true } : {}),
|
|
318
338
|
recovery: { operation, reuseOriginalInput: true, preserveJournal: true,
|
|
319
339
|
...(knownBeforeTransmission ? { remoteAttemptedThisRun: false } : {}) },
|
|
320
|
-
...(error?.output ? { output: error.output } : {}), ...recoveryFailureFields(error)
|
|
340
|
+
...(error?.output ? { output: error.output } : {}), ...recoveryFailureFields(error),
|
|
341
|
+
repair: error?.output?.repair ?? skillRepairGuidance(error, { operation, input }) };
|
|
321
342
|
}
|
|
322
343
|
}
|
|
323
344
|
export const runMutationConnect = (repositoryRoot, input, dependencies) => runMutationClient('mutation-connect', repositoryRoot, input, dependencies, connectMutation);
|
|
@@ -8,9 +8,14 @@ export async function readMutationConnection(location, identity) {
|
|
|
8
8
|
try { value = await readMutationJson14(location.file); }
|
|
9
9
|
catch (error) { if (error.code !== 'ENOENT') throw error; }
|
|
10
10
|
if (!value) return { schemaVersion: mutationConnectionSchema, identity };
|
|
11
|
+
const pending = value.pending;
|
|
12
|
+
const leaseRecovery = pending?.stage === 'leaseRenewed' && pending.operation === 'continue'
|
|
13
|
+
&& pending.input?.flowId === identity.flowId && pending.input.operation === 'execution-claim'
|
|
14
|
+
&& pending.input.payload?.stepId === identity.stepId
|
|
15
|
+
&& Boolean(pending.input.payload.leaseRecovery || pending.input.payload.leaseContinuation);
|
|
11
16
|
if (value.schemaVersion !== mutationConnectionSchema || canonical(value.identity) !== canonical(identity)
|
|
12
17
|
|| value.pending && (value.pending.requestId !== buildInvocation(value.pending.operation, value.pending.input).requestId
|
|
13
|
-
|| !['mutation-snapshot-challenge', 'mutation-pass-issue', 'mutation-pass-settle'].includes(value.pending.operation))) {
|
|
18
|
+
|| !leaseRecovery && !['mutation-snapshot-challenge', 'mutation-pass-issue', 'mutation-pass-settle'].includes(value.pending.operation))) {
|
|
14
19
|
mutationFail14('ENTROROVE_MUTATION_CONNECTION_BINDING', 'Mutation connection journal differs from the original repository, request or physical key');
|
|
15
20
|
}
|
|
16
21
|
return value;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { canonical } from './local-integrity.mjs';
|
|
2
|
+
import { buildInvocation, invokeRemoteResult } from './remote-client.mjs';
|
|
3
|
+
import { consumeRecovery } from './local-recovery-budget.mjs';
|
|
4
|
+
|
|
5
|
+
export const mutationLeaseExpired = error => ['ENTROROVE_MUTATION_LEASE', 'ENTROROVE_MUTATION_EXPIRED'].includes(error?.code);
|
|
6
|
+
function originalPrerequisite(output) {
|
|
7
|
+
const issue = output?.localAction ?? output?.nextStep?.localAction ?? output;
|
|
8
|
+
if (['blocked', 'failed', 'incomplete'].includes(issue?.status) || output?.canContinue === false) {
|
|
9
|
+
throw Object.assign(new Error(issue.instruction ?? issue.message ?? 'Repair the original reported prerequisite before continuing'),
|
|
10
|
+
{ code: issue.code ?? 'ENTROROVE_MUTATION_LEASE_RECOVERY', output, nextStep: output.nextStep, automaticRetryAllowed: false });
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function currentAction(output, identity) {
|
|
14
|
+
const action = output?.localAction ?? output?.nextStep?.localAction;
|
|
15
|
+
const input = action?.input;
|
|
16
|
+
if (action?.status !== 'ready' || !/^npx @entrorove\/skill mutation-connect(?:\s|$)/.test(action.command ?? '')
|
|
17
|
+
|| ['taskId', 'flowId', 'stepId', 'runnerId'].some(key => input?.[key] !== identity[key])
|
|
18
|
+
|| !Number.isSafeInteger(input.generation) || input.generation < identity.generation
|
|
19
|
+
|| !Array.isArray(input.paths) || identity.paths.some(path => !input.paths.includes(path))
|
|
20
|
+
|| (input.previousPassId ?? null) !== (identity.previousPassId ?? null)) return null;
|
|
21
|
+
return { ...identity, leaseId: input.leaseId, generation: input.generation, paths: identity.paths };
|
|
22
|
+
}
|
|
23
|
+
export async function recoverMutationLease(context, callbacks) {
|
|
24
|
+
const { state, identity, location, dependencies } = context;
|
|
25
|
+
const { remote, save } = callbacks;
|
|
26
|
+
if (state.issued || state.gatePassPath || state.pending && state.pending.stage !== 'leaseRenewed') return null;
|
|
27
|
+
const expired = Date.parse(state.challenged?.challenge?.expiresAt) <= Date.now();
|
|
28
|
+
if (!expired && !state.pending && !state.leaseRenewed && !mutationLeaseExpired(state.failures?.at(-1))) return null;
|
|
29
|
+
let input = state.pending?.input;
|
|
30
|
+
if (!input) {
|
|
31
|
+
const { response, result } = await invokeRemoteResult({ operation: 'task-resume', input: { flowId: identity.flowId } }, dependencies);
|
|
32
|
+
originalPrerequisite(result.output);
|
|
33
|
+
if (!response.ok || result.output?.status !== 'succeeded' || result.output.canContinue !== true) return null;
|
|
34
|
+
const next = result.output.nextStep;
|
|
35
|
+
const current = currentAction(result.output, identity) ?? currentAction(next, identity);
|
|
36
|
+
if (current && canonical(current) !== canonical(identity)) return current;
|
|
37
|
+
input = next?.operation === 'continue' ? next.input : null;
|
|
38
|
+
if (input?.flowId !== identity.flowId || input.operation !== 'execution-claim' || input.payload?.stepId !== identity.stepId
|
|
39
|
+
|| !input.payload.leaseRecovery && !input.payload.leaseContinuation) return null;
|
|
40
|
+
consumeRecovery(state, 'prerequisite', `${input.payload.leaseRecovery?.leaseId ?? input.payload.leaseContinuation.leaseId}:lease`);
|
|
41
|
+
await save(location, state);
|
|
42
|
+
}
|
|
43
|
+
if (state.leaseRenewed && state.leaseRenewed.requestId !== buildInvocation('continue', input).requestId) {
|
|
44
|
+
state.leaseRenewalHistory = [...(state.leaseRenewalHistory ?? []), state.leaseRenewed];
|
|
45
|
+
delete state.leaseRenewed;
|
|
46
|
+
await save(location, state);
|
|
47
|
+
}
|
|
48
|
+
const continued = state.leaseRenewed ?? await remote('leaseRenewed', 'continue', input, state, location, dependencies,
|
|
49
|
+
{ readOnlyRecovery: Boolean(state.pending) });
|
|
50
|
+
originalPrerequisite(continued);
|
|
51
|
+
const recovered = currentAction(continued, identity);
|
|
52
|
+
if (!recovered) throw Object.assign(new Error('Original lease recovery returned no eligible mutation action; use its original next step'),
|
|
53
|
+
{ code: 'ENTROROVE_MUTATION_LEASE_RECOVERY', output: continued, nextStep: continued.nextStep, automaticRetryAllowed: false });
|
|
54
|
+
if (canonical(recovered) === canonical(identity)) return null;
|
|
55
|
+
return recovered;
|
|
56
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@entrorove/skill",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.90",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"entrorove": "cli.mjs"
|
|
@@ -57,6 +57,8 @@
|
|
|
57
57
|
"local-integrity.d.mts",
|
|
58
58
|
"local-evidence-verification.mjs",
|
|
59
59
|
"local-error.mjs",
|
|
60
|
+
"skill-repair-guidance.mjs",
|
|
61
|
+
"skill-repair-guidance.d.mts",
|
|
60
62
|
"local-formula.mjs",
|
|
61
63
|
"local-ledger.mjs",
|
|
62
64
|
"local-chain-bindings.mjs",
|
|
@@ -112,6 +114,7 @@
|
|
|
112
114
|
"scope-amendment-consumption.mjs",
|
|
113
115
|
"mutation-connection-state.mjs",
|
|
114
116
|
"mutation-challenge-refresh.mjs",
|
|
117
|
+
"mutation-lease-recovery.mjs",
|
|
115
118
|
"scope-amendment-reference.mjs",
|
|
116
119
|
"cloud-task-client.mjs",
|
|
117
120
|
"cloud-task-images.mjs",
|
package/release-state.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "entrorove.release-state/1.0",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"digest": "
|
|
3
|
+
"version": "0.1.90",
|
|
4
|
+
"digest": "569bf144ff27c1833d132331ff456cd0ae658c8f89b8ffd669628590c80bf354",
|
|
5
5
|
"files": {
|
|
6
6
|
"local-recovery-budget.mjs": "be3af07db8e4cc96782edb130e827039c7a99bbe8275a92f0f741533716017f3",
|
|
7
7
|
"remote-capability-cache.mjs": "9c22067e1a21eb2a209747a8ab2f27c0496b14a22a7e0f5654840e2035148df0",
|
|
@@ -33,8 +33,9 @@
|
|
|
33
33
|
"cli-input.mjs": "44dc2692714023b622a9370c13cabc09c7c39e6aea6e7183b80ae5bcd2e8d24e",
|
|
34
34
|
"scope-amendment-retirement.mjs": "44ceec3200c4b1e8fb45e38436f34e173803b5051074d6041a670bb8c4bc2215",
|
|
35
35
|
"scope-amendment-consumption.mjs": "12a1dfd1429350cc9503957224398524c077aed6c697228c9870aa95c245ee7d",
|
|
36
|
-
"mutation-connection-state.mjs": "
|
|
37
|
-
"mutation-challenge-refresh.mjs": "
|
|
36
|
+
"mutation-connection-state.mjs": "d1086c16c6f9c27afc68a1fa2ee0b5466033292d1de02b627c6d3555eaa1684c",
|
|
37
|
+
"mutation-challenge-refresh.mjs": "e84f2bb599a738c8c821ea2fc19f780c75c1de0d9b26e04fe829273b2fd8a8d5",
|
|
38
|
+
"mutation-lease-recovery.mjs": "1425f9e5d5ee27722479192b78114191610c0197244696b7dc910099d85bef53",
|
|
38
39
|
"local-scope-font.mjs": "a69f9da932ed41aa4718036f98f75c0b17ba7f616a5c8bf5623fa9ed697499fa",
|
|
39
40
|
"local-scope-zip.mjs": "46c61d201a570c769fc4896fc19090e790765af5f28596557a288bd9b762c69e",
|
|
40
41
|
"member-producer-repair-authority.mjs": "1e84e5e77ba5ee9a0dcaa4d843061c7b5447191852d7b2fb603b91cfa7c796e4",
|
|
@@ -43,7 +44,7 @@
|
|
|
43
44
|
"local-mutation-finder-observation.mjs": "438dd33e62e03e02b55897a28b3ba59d12734b6e5142525b299f77c21537280b",
|
|
44
45
|
"local-finder-store.mjs": "227191c52174386ace6391e072b78a6e4cc658a6d9dd37036ffd7e97bbf4336a",
|
|
45
46
|
"local-finder-plist.mjs": "c83ddab2375a54d612d395a5518db8ae05212437d82bf3f8b76930a8859c8481",
|
|
46
|
-
"connected-cli.mjs": "
|
|
47
|
+
"connected-cli.mjs": "0ad57a29c482c77b3cfed7dc25bbc7c713b58f7934d28ca68f834013ffc8c59b",
|
|
47
48
|
"local-approved-process.mjs": "f37266b4fc14af358b2781b05034c7e733fcb049a72300250c8dd0f6edb0cad8",
|
|
48
49
|
"local-approved-supervisor.mjs": "87142a4c13d27c8265cc1fd7fc9088988da9bbb02134164e1a7c5417da926ac7",
|
|
49
50
|
"local-mutation-consumption.mjs": "1b033655f817ec576410b7f91f1640b8651f3cff0668154fb8e6e14e7b31d8f4",
|
|
@@ -56,13 +57,13 @@
|
|
|
56
57
|
"workflow-observation-interruption.mjs": "08339e313a0ac0e706bfe6fbfa6b2235e9c41e8b0f55a1aa6bf64af07a9b228b",
|
|
57
58
|
"workflow-observation-state.mjs": "a525046df9b3de75b10d2bf35c2b6d7719f12a647e4aee1f1e85dbdbfb167321",
|
|
58
59
|
"workflow-observation-wire.mjs": "df0d5e4f8fde0be8bf06c6c4590cd158b4da05ebe6ef3169d2366db706f3327f",
|
|
59
|
-
"local-preserved-mutation-protection.mjs": "
|
|
60
|
+
"local-preserved-mutation-protection.mjs": "f047bf83bb4c5906e9813cfd9e1e727f10bfff3e38ca87c1b49580ab7af04d5b",
|
|
60
61
|
"local-preserved-mutation-signer.mjs": "2b0771cbdd18ba00267aa441b7892cd04feab9f8543ae5e2703430b1e580747f",
|
|
61
62
|
"local-preserved-mutation-state.mjs": "337a172b2d0a03b0d5a89b8483afdd9a043a4999fe38f87679633c98c450126b",
|
|
62
63
|
"local-preserved-mutation-wire.mjs": "6768ce5713d70f7c0bd05f2286bc4b32cd9b8bb1d3c8b9cc7a451cb9b176824d",
|
|
63
|
-
"local-delegated-modify-analysis.mjs": "
|
|
64
|
-
"local-css-source.mjs": "
|
|
65
|
-
"mutation-client.mjs": "
|
|
64
|
+
"local-delegated-modify-analysis.mjs": "d1bc9327bccb614d270b197487d66da31cae91762776d1dd77c2b60ef2bc6cb7",
|
|
65
|
+
"local-css-source.mjs": "29d6b7faca95ecbcb9100926360522423b5b97e40db976c38860c95c5e981889",
|
|
66
|
+
"mutation-client.mjs": "66a776c54e3b6d7065ca56c34592619657d44671634dc19fee4d67abd13209ed",
|
|
66
67
|
"cloud-mutation-authority.mjs": "8898b4e5cd18b3574523d58d0b6337f72869c6e3ae58dcffe827f36f551700d3",
|
|
67
68
|
"ide-code-mode.mjs": "f8ba4de33632ab4c6df52dab52f392414b4632c7e94b218b8065677970c075c7",
|
|
68
69
|
"ide-command.mjs": "b3c008816f53e3cd3aa97bc792baf0b7fc068e839b150bd2cfc6cb06d17a6825",
|
|
@@ -93,8 +94,8 @@
|
|
|
93
94
|
"runtime/validator-runtime-v7.0.44.d.mts": "7797edbfe2f7de24da370204dce4dfd22822a1bd80df93514fff900b98afe36f",
|
|
94
95
|
"snapshot-retention.mjs": "ca46aeb36b1a5a24fe7e967031a71b9a3d33f40cb715d344402cbe0dc9c425cb",
|
|
95
96
|
"snapshot-cleanup-journal.mjs": "76a9684cc95b4eb076e1298d78a9dff37948b00d9607b8f546c599072d3ed331",
|
|
96
|
-
"IDE-EXECUTION.md": "
|
|
97
|
-
"REUSE-DISCIPLINE.md": "
|
|
97
|
+
"IDE-EXECUTION.md": "0879d8a4a3ecd8e4123e409d34c3d3ddbb6713353f7c7dc1b49cc706d2c6efd0",
|
|
98
|
+
"REUSE-DISCIPLINE.md": "4e7bf6d3531d868f2cd44e4b7a999b3e776d8866b463c44a16c3fed72da92ce2",
|
|
98
99
|
"install-identity.mjs": "41b8c8d8b3e265d2057a07bf7dc98066c34d6b05c65bf7159ca9609c01ee4646",
|
|
99
100
|
"package-archive.mjs": "e06db8d709db8710bead254ee950b78928088be01c58dcea383beaa5eb574e59",
|
|
100
101
|
"package-replacement.mjs": "02ca3fa4ada2addc479f8cb1b8099cd1b574adfeaa2f3c8ecf612ac2cb9127d3",
|
|
@@ -112,7 +113,7 @@
|
|
|
112
113
|
"design-declaration.mjs": "4c4be3731bcf169e15a100c5261a34686ddd43b780dfa8667a8d7c36766e5237",
|
|
113
114
|
"design-frameworks.mjs": "57c316420269d3b46044f14306c5f807e0e87d5a527b3c9995a7066b2b3da952",
|
|
114
115
|
"design-gate.mjs": "7d06a7065951f97b6abc10bba4bb517200c3d0b2720354b878f98f5cdeab3b19",
|
|
115
|
-
"design-lint.mjs": "
|
|
116
|
+
"design-lint.mjs": "0ccb44dffdbdd500ada2c046d97de07b9330fe9f99f6edc69890deade912355e",
|
|
116
117
|
"design-local-contracts.mjs": "f331bd1d21f30994596bd9c46008e551f3ed2b6d5eea19d4546c7da0663b768a",
|
|
117
118
|
"design-operations.mjs": "dfad99258e016fa55a783551ae968915405599207b2407e04e2daf637b6b79d6",
|
|
118
119
|
"design-policy.d.mts": "48fed97fca0f97fef786f182f666d7e6e33efb814cda1991239e1c38984cf80e",
|
|
@@ -125,7 +126,7 @@
|
|
|
125
126
|
"design-stage-contracts.mjs": "f4242876fd6472096dd22609868e955a54c9269d6edd7bff399c6083ece52848",
|
|
126
127
|
"design-storage.mjs": "af02cf3fb59f9ef7c7d6276b43e6a53e3d305a77492e3cb824dd05d52adc2fd3",
|
|
127
128
|
"design-ui.mjs": "6dd3c5a97ec67e8f1c23e57bcab5eb64380c520befada6f264c4bfcf6b11c9f7",
|
|
128
|
-
"DESIGN-GATES.md": "
|
|
129
|
+
"DESIGN-GATES.md": "f6a96ac21617adff2d841bbeaef63312920543c75ff3c163cae099b79ce1f2ef",
|
|
129
130
|
"DESIGN-STAGED-ACCEPTANCE.md": "90ee32e08fa4bfd56fcb8f9ffb19f6a10217203448b6676bcf58c4ea6e03ba6c",
|
|
130
131
|
"design-release.mjs": "03863c95bec899782c1bed09c14f0e302ec2ec7bc221a36a4f7c91e501734b72",
|
|
131
132
|
"local-final-gate16.mjs": "083aec470670e6cdb7ad2ab27b7a51b53a9ec3a8b4121cb9cbfa17d9940892c2",
|
|
@@ -175,13 +176,13 @@
|
|
|
175
176
|
"install-cleanup.mjs": "8a8e1a7dfe37427f86cd4d6e317890d0829894a9949d4216674a716b36edbc54",
|
|
176
177
|
"review-client.mjs": "3491eef68ed533174b31db3c53febb6519b8f088f71a9964644714a9a4c03c77",
|
|
177
178
|
"version.mjs": "85284d4cd5e3314b449e6ae124243f96397bcc274817b5983a79cfdd903d46fd",
|
|
178
|
-
"SKILL.md": "
|
|
179
|
+
"SKILL.md": "87390287c0717e52969598e48f48b103d999f8c330dc63d0b246b467ebca3e9a",
|
|
179
180
|
"CAPABILITY-MAP.md": "57805ba6a4b4fc79ffadb3bd1436999f82c295feac7bdc230bdc123f67662546",
|
|
180
181
|
"AGREEMENT.md": "f93a68e92c457200dd024610e65f74e478b8f60860f951da818b70561f1b9bf8",
|
|
181
182
|
"DELIVERY-BOUNDARIES.md": "cb23ce71b2197085c8441ba7c5b6c05fb21f2a954bf66b4d60cfef8b15895b8b",
|
|
182
|
-
"package.json": "
|
|
183
|
+
"package.json": "ec045f7ba5d84bdd18c941e16bd6ab4b4d9b10bf2c355809ef22a1930845bfe6",
|
|
183
184
|
"manifest.json": "ab06e307795bc9da5add926752b6c4dbdaf18625090261a035aa356a679c504f",
|
|
184
|
-
"release.mjs": "
|
|
185
|
+
"release.mjs": "052ccff9d9aedb072ae6e9ae5ef394febe5898dc8e7a555acb3e8a26d30a2b97",
|
|
185
186
|
"local-cli.mjs": "6c12beed3c43a484e1afd266055179dec17d56a9c04846d67a279445d50da095",
|
|
186
187
|
"local-state-lock.mjs": "1dbf64264642a0851faf043e07ee9f66d2ad0508e9f799b0626246e42fae893e",
|
|
187
188
|
"local-execution.mjs": "721ed7d29bbe4ce6d088909cd84ffc440978c3c9eb0946c49a4281ee685fe3aa",
|
|
@@ -189,7 +190,9 @@
|
|
|
189
190
|
"local-evidence-verification.mjs": "9b7dea31777505272f63616fd62af7603338f703bb1285fc565d6451715c7ce0",
|
|
190
191
|
"local-formula.mjs": "706d5ec0cb2caf95a547531a81ca746fe246d4c53d902e03595a3b360e8c85af",
|
|
191
192
|
"local-ledger.mjs": "2009fb68aadcd7292b72f1d0d99f378d03644be1c5b4b4150d9f6a0b3f7c15ce",
|
|
192
|
-
"local-error.mjs": "
|
|
193
|
+
"local-error.mjs": "1352db0ebbf36b9c67e1a1664516f3c64918293ae15eb646b410595c3979aad8",
|
|
194
|
+
"skill-repair-guidance.mjs": "080920b60ff75eb8a04fd63226382f7351e89fef2d3f870645c39bfdd2783634",
|
|
195
|
+
"skill-repair-guidance.d.mts": "bfa38222e75155a07a7950703d92b8e7d9d140f1339aed8e1665b1fdcd8e3469",
|
|
193
196
|
"blueprint-criterion-wire.mjs": "fb6b7dcce0b97a699e3e8968372767a3fddd83fedc750998573f789177243072",
|
|
194
197
|
"blueprint-criterion-runner.mjs": "05413fea9d2985806adcbfbd53ca0760ca6a850a52957f0cbde43b753a3b7904",
|
|
195
198
|
"local-chain-bindings.mjs": "b8eff999ccd17b53c614a3c9f5376da78fcaa8632bb3e07bc1adcceda2e75f6c",
|
|
@@ -258,9 +261,9 @@
|
|
|
258
261
|
"local-scope-state.mjs": "7a6d275d6267a4d0bb55582e1bcc7654c502ecba9221b84b2149a98f0ee7518f",
|
|
259
262
|
"local-scope-workspaces.mjs": "8a75ce7b6639b5682370b5738c5c2a90408e3130729608e008f03053cd50351f",
|
|
260
263
|
"local-task-routing.mjs": "6e2e506b2fb0e2e8d8c5147d7df32849b57a4e94a23634f01a355d45735858e4",
|
|
261
|
-
"remote-client.mjs": "
|
|
264
|
+
"remote-client.mjs": "5677f843a7609b15e8d632f22dbddf0ab970c10775218982377495f90fde7a10",
|
|
262
265
|
"service-prerequisite.mjs": "7ac5a66d8b423941a5a2cef3aecce18a681aaf2b1a61612fecfa17f5d99f302d",
|
|
263
|
-
"local-source-execution.mjs": "
|
|
266
|
+
"local-source-execution.mjs": "1ec7488f4fa854bdc4d07a03a205eacbfb0092ed5c1128822f6cf82be69ef742",
|
|
264
267
|
"fs-request-trace.mjs": "972eacbe21537bc102047127327449a6a4b0f0c65d75e3472370de91dedf4d2f",
|
|
265
268
|
"cloud-task-client.mjs": "ab3e0296363b6af10eaa38a09dd43b4ae6e5704d699c7a5032fee1b7817fa0dc",
|
|
266
269
|
"scope-runner-client.mjs": "b8bf4aff4d45c2a7e92afd43e9ba6062d220dd42f2009bd91a18a516a2a1bc28",
|
package/release.mjs
CHANGED
|
@@ -14,7 +14,7 @@ const sourceFiles = [
|
|
|
14
14
|
'local-recovery-budget.mjs', 'remote-capability-cache.mjs', 'scope-write-observation.mjs', 'scope-write-observation.d.mts',
|
|
15
15
|
'release-source-groups.mjs', 'local-scope-build-read.mjs', 'local-scope-build-io.mjs', 'local-scope-build-facts.mjs', 'local-scope-build-data.mjs', 'local-scope-build-readset.mjs',
|
|
16
16
|
...scopeEvidenceSourceFiles, ...SCOPE_CONTEXT_SOURCE_FILES, 'cloud-locale.mjs', 'cloud-locale.d.mts', 'cloud-task-images.mjs', 'model-images.mjs', 'model-images.d.mts', 'cli-input.mjs',
|
|
17
|
-
'scope-amendment-retirement.mjs', 'scope-amendment-consumption.mjs', 'mutation-connection-state.mjs', 'mutation-challenge-refresh.mjs',
|
|
17
|
+
'scope-amendment-retirement.mjs', 'scope-amendment-consumption.mjs', 'mutation-connection-state.mjs', 'mutation-challenge-refresh.mjs', 'mutation-lease-recovery.mjs',
|
|
18
18
|
'local-scope-font.mjs', 'local-scope-zip.mjs', 'member-producer-repair-authority.mjs',
|
|
19
19
|
'local-mutation-completion-evidence.mjs', 'local-mutation-environment-observation.mjs', 'local-mutation-finder-observation.mjs', 'local-finder-store.mjs', 'local-finder-plist.mjs',
|
|
20
20
|
...memberContinuationSourceFiles,
|
|
@@ -42,7 +42,7 @@ const sourceFiles = [
|
|
|
42
42
|
'cli.mjs', 'ide-selection.mjs', 'ide-exit.mjs', 'install-cleanup.mjs', 'review-client.mjs', 'version.mjs', 'SKILL.md', 'CAPABILITY-MAP.md', 'AGREEMENT.md', 'DELIVERY-BOUNDARIES.md',
|
|
43
43
|
'package.json', 'manifest.json', 'release.mjs',
|
|
44
44
|
'local-cli.mjs', 'local-state-lock.mjs', 'local-execution.mjs', 'local-integrity.mjs', 'local-evidence-verification.mjs', 'local-formula.mjs', 'local-ledger.mjs',
|
|
45
|
-
'local-error.mjs',
|
|
45
|
+
'local-error.mjs', 'skill-repair-guidance.mjs', 'skill-repair-guidance.d.mts',
|
|
46
46
|
'blueprint-criterion-wire.mjs', 'blueprint-criterion-runner.mjs',
|
|
47
47
|
'local-chain-bindings.mjs', 'local-chain-broker.mjs', 'local-chain-contracts.mjs', 'local-chain-controller.mjs',
|
|
48
48
|
'local-chain-human.mjs', 'local-chain-lock.mjs', 'local-chain-operations.mjs', 'local-chain-plan.mjs',
|
package/releases.json
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.1.90",
|
|
4
|
+
"date": "2026-10-23",
|
|
5
|
+
"zh": "删除技能自订的 CSS 设计限制:仅按 PostCSS 解析语法,不再强制 token、样式集中、选择器或资源写法。原 mutation-connect 自动续接一次合格的过期租约,保留原请求和审批;原有数据动态取值不再误当运行时导入拒绝。",
|
|
6
|
+
"en": "Remove custom CSS design restrictions and use PostCSS syntax parsing without mandatory tokens, stylesheet organization, frozen selectors or resource notation. Automatically continue one eligible expired lease in the original mutation-connect command, retain original requests and approval, and distinguish existing computed data access from runtime imports."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.1.89",
|
|
10
|
+
"date": "2026-10-23",
|
|
11
|
+
"zh": "把普通不符合项从终止或一律转人工改为 IDE 修复后继续:统一返回文件或字段、适用标准和下一步,复用原授权及服务端恢复动作;保留原失败结果。过期挑战只恢复已有请求,不再补发必然失败的签发请求。",
|
|
12
|
+
"en": "Return actionable IDE repair guidance with files or fields, standards and next steps instead of terminal or blanket human escalation. Reuse original authorization and recovery actions; remove synthetic expired-challenge issue requests while retaining failure evidence."
|
|
13
|
+
},
|
|
2
14
|
{
|
|
3
15
|
"version": "0.1.88",
|
|
4
16
|
"date": "2026-10-23",
|
package/remote-client.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import { routeTaskEntry } from './task-entry-client.mjs';
|
|
|
9
9
|
import { cleanupCompletedSnapshots } from './snapshot-retention.mjs';
|
|
10
10
|
import { prerequisiteError, discoveryPrerequisite } from './service-prerequisite.mjs';
|
|
11
11
|
import { discoveredCapabilities } from './remote-capability-cache.mjs';
|
|
12
|
+
import { withSkillRepair } from './skill-repair-guidance.mjs';
|
|
12
13
|
|
|
13
14
|
export const apiUrl = 'https://entrorove.com/api/skill/invoke';
|
|
14
15
|
|
|
@@ -194,7 +195,7 @@ export async function postInvocation(call, token, home, feedbackRequired = false
|
|
|
194
195
|
result.snapshotCleanup = { status: 'blocked', code: 'ENTROROVE_SNAPSHOT_CLEANUP_BLOCKED',
|
|
195
196
|
message: 'Execution and feedback are complete; cache cleanup encountered an unsafe entry and preserved it.' };
|
|
196
197
|
}
|
|
197
|
-
return { response, result };
|
|
198
|
+
return { response, result: withSkillRepair(result, { operation: call.body.operation, input: call.body.input }) };
|
|
198
199
|
}
|
|
199
200
|
|
|
200
201
|
function unknownInvocation(requestId, cause = {}) {
|
|
@@ -217,7 +218,12 @@ function validateMachineCapability(operation, input, capabilities) {
|
|
|
217
218
|
const schema = capabilities?.operationSchemas?.[operation];
|
|
218
219
|
if (!capabilities?.operations?.includes(operation) || !schema) throw new Error(`Capability discovery does not support ${operation}`);
|
|
219
220
|
const validate = discoveredAjv().compile(schema);
|
|
220
|
-
if (!validate(input))
|
|
221
|
+
if (!validate(input)) {
|
|
222
|
+
const issues = validate.errors.slice(0, 16).map(error => ({ path: error.instancePath || '/',
|
|
223
|
+
message: error.message, ...(error.keyword === 'required' ? { missingField: error.params.missingProperty } : {}) }));
|
|
224
|
+
throw Object.assign(new Error(`Correct the reported ${operation} input fields before continuing`),
|
|
225
|
+
{ code: 'ENTROROVE_INPUT_INVALID', issues, knownBeforeEffects: true, automaticRetryAllowed: false });
|
|
226
|
+
}
|
|
221
227
|
validateDiscoveredChecks(input, capabilities.inputChecks?.[operation]);
|
|
222
228
|
if (operation === 'continue') {
|
|
223
229
|
const payloadSchema = capabilities.workflowOperations?.operationSchemas?.[input.operation];
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const rules = [
|
|
2
|
+
[/CSS_SYNTAX/, 'css-syntax',
|
|
3
|
+
'Correct the PostCSS syntax error at the reported file, line and column, then continue the original edit. Layout, selectors, tokens, properties and resource notation are the project author’s choices.'],
|
|
4
|
+
[/INPUT|SCHEMA|OPERATION_UNKNOWN|DIV_ZERO/, 'operation-schema',
|
|
5
|
+
'Correct the reported fields using the cached operation schema and original bindings. Then submit the corrected input.'],
|
|
6
|
+
[/DELEGATION_CHANGE/, 'original-write-contract',
|
|
7
|
+
'Revise the candidate to preserve the original definitions, public contracts and imports. Use the reported file and original modify declaration.'],
|
|
8
|
+
[/DELEGATION_INVALID|IMPORT_INVALID/, 'source-dependency-evidence',
|
|
9
|
+
'Inspect the reported source and unresolved origin. Correct the source analysis or candidate within the original scope; retain unknown origins rather than declaring a complete graph.'],
|
|
10
|
+
[/DESIGN/, 'approved-design-rules',
|
|
11
|
+
'Reuse the original design and approval. Correct the reported candidate rule or missing evidence; do not regenerate images or repeat completed design stages.'],
|
|
12
|
+
[/LEASE|EXPIRED/, 'original-live-authority',
|
|
13
|
+
'Use the original task-resume recovery once. Continue with its exact returned lease or localAction, preserving the old journal and failure history.'],
|
|
14
|
+
[/SCOPE|SNAPSHOT/, 'original-source-baseline',
|
|
15
|
+
'Reconcile the reported paths and source hashes with the original scope. Use its supported refresh or recovery; retain the actual original baseline.'],
|
|
16
|
+
[/BINDING|REPLAY|ACCOUNT|RECEIVER|REVOKED|SIGNATURE/, 'original-account-and-identity',
|
|
17
|
+
'Recover the original account and task state, then use its current matching identifiers and registered signer. Resolve a real ownership change with its current owner.'],
|
|
18
|
+
[/PACKAGE|ROLLBACK/, 'package-provenance',
|
|
19
|
+
'Select the correct package identity and version or recover the original rollback proof. Preserve the installed bytes until the replacement has valid evidence.'],
|
|
20
|
+
[/DISCIPLINE|MUTATE_NOT_ACCEPTED|AUTHORITY_REQUIRED/, 'original-authorization-and-write-plan',
|
|
21
|
+
'Recover and reuse the original approval and declared write action. Correct the candidate or missing evidence within that boundary; ask only for an actual uncovered action.'],
|
|
22
|
+
];
|
|
23
|
+
export function skillRepairGuidance(issue = {}, context = {}) {
|
|
24
|
+
const code = typeof issue.code === 'string' ? issue.code : 'ENTROROVE_LOCAL_FAILURE';
|
|
25
|
+
const unknown = issue.outcomeUnknown === true || /OUTCOME_UNKNOWN|RECOVERY_REQUIRED/.test(code);
|
|
26
|
+
const rule = rules.find(([pattern]) => pattern.test(code));
|
|
27
|
+
const requestId = /^[a-f0-9]{64}$/.test(issue.requestId ?? '') ? issue.requestId : undefined;
|
|
28
|
+
const flowId = context.flowId ?? context.input?.flowId ?? issue.flowId;
|
|
29
|
+
const originalNext = issue.nextStep && typeof issue.nextStep === 'object' ? issue.nextStep
|
|
30
|
+
: issue.nextAction && typeof issue.nextAction === 'object' ? issue.nextAction : null;
|
|
31
|
+
const path = issue.path ?? context.input?.targetPath;
|
|
32
|
+
const reported = issue.issues ?? issue.diagnostic?.issues;
|
|
33
|
+
const fields = (Array.isArray(reported) ? reported : []).slice(0, 16).map(item => ({
|
|
34
|
+
path: [...(Array.isArray(item.path) ? item.path : typeof item.path === 'string'
|
|
35
|
+
? item.path.split('/').filter(Boolean).map(part => part.replace(/~1/g, '/').replace(/~0/g, '~'))
|
|
36
|
+
: typeof item.field === 'string' ? [item.field] : []),
|
|
37
|
+
...(typeof item.missingField === 'string' ? [item.missingField] : [])].slice(0, 16),
|
|
38
|
+
...(typeof item.message === 'string' ? { message: item.message.slice(0, 1000) } : {}),
|
|
39
|
+
}));
|
|
40
|
+
return { schemaVersion: 'entrorove.ide-repair/1.0',
|
|
41
|
+
mode: unknown ? 'recover-original-result' : 'repair-and-continue',
|
|
42
|
+
taskAction: 'preserve-original-task', code,
|
|
43
|
+
standard: unknown ? 'known-original-outcome' : rule?.[1] ?? 'reported-operation-contract',
|
|
44
|
+
...(typeof path === 'string' ? { path } : {}), ...(fields.length ? { fields } : {}),
|
|
45
|
+
...(Number.isInteger(issue.line) ? { line: issue.line } : {}),
|
|
46
|
+
...(Number.isInteger(issue.column) ? { column: issue.column } : {}),
|
|
47
|
+
instruction: unknown
|
|
48
|
+
? 'Recover only the original request result with GET and acknowledge it before another mutation.'
|
|
49
|
+
: rule?.[2] ?? 'Use the original diagnostic to repair the missing prerequisite or candidate. Continue the same task after correction; do not end the task because this operation needs repair.',
|
|
50
|
+
nextStep: unknown && requestId ? { operation: 'status', method: 'GET', requestId }
|
|
51
|
+
: originalNext ?? (/LEASE|EXPIRED/.test(code) && typeof flowId === 'string'
|
|
52
|
+
? { operation: 'task-resume', input: { flowId } }
|
|
53
|
+
: { action: 'prepare-corrected-input', ...(context.operation ? { operation: context.operation } : {}) }),
|
|
54
|
+
repeatUnchangedInput: false, grantsMutationAuthority: false };
|
|
55
|
+
}
|
|
56
|
+
export function withSkillRepair(value, context = {}) {
|
|
57
|
+
if (!value || typeof value !== 'object') return value;
|
|
58
|
+
const issue = value.output ?? value;
|
|
59
|
+
if (!['blocked', 'failed', 'incomplete'].includes(issue?.status)) return value;
|
|
60
|
+
return { ...value, repair: issue.repair ?? value.repair ?? skillRepairGuidance(issue, context) };
|
|
61
|
+
}
|