@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 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 freeze UI paths, verification paths, browser identity, preview origin,
91
- approved component imports, native replacements, central style/token sources,
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 reject introduced inline styles, undeclared components/style imports,
114
- unknown tokens, private fields, whole-object dumping and dynamic UI bypasses.
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
- Unsupported preprocessors/native UI syntax fail explicitly; official research
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, use `task-resume` once and its exact `execution-claim` lease recovery before refreshing a challenge. The server shares one prerequisite check with storage, verifies the original signatures and closed feedback, and atomically records the expired never-issued challenges in the signed old/new lease history. Preserve the old journal; use the returned renewed lease and `localAction`. A blocked response names its unresolved original prerequisite and is not retryable while unchanged. Issued M passes, unknown requests and unresolved processes are not eligible.
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 failures use the machine-readable `entrorove.local-error/1.0` envelope `{schemaVersion,status,code,disposition,message}` on stderr. `status` is `failed` for invalid input or computation errors and `blocked` for missing mutation, lease, snapshot or handoff authority. Formula `add` and `mul` require at least one argument; `div`, `safeDivide` and `percentOf` reject a zero denominator with `DIV_ZERO`. The formula engine declares `round` as half-away-from-zero with two decimal places by default; an engine may declare another `decimalPlaces` value from 0 through 28 in its `roundingPolicy`.
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
 
@@ -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 changes also require [DESIGN-GATES.md](DESIGN-GATES.md): approved flow, researched layout and official framework rules, generated mockups and original user confirmation before UI implementation. New style rules check only introduced violations against the frozen original source; do not rewrite existing styles in bulk to satisfy them. Final UI delivery additionally requires real functional and visual acceptance.
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.88.
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. An unknown origin remains protected. CSS is parsed as CSS; unverified resource references do not become a complete dependency graph.
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 postcss from 'postcss';
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, rules) {
11
- let tree; try { tree = postcss.parse(source, { from: name }); }
12
- catch { return [issue('css-parse', 'Use supported CSS; uncompiled preprocessors require a separately reviewed compiled CSS source')]; }
13
- const found = [];
14
- tree.walkAtRules(rule => { if (['import', 'tailwind', 'apply'].includes(rule.name)) found.push(issue('style-indirection', rule.toString())); });
15
- tree.walkDecls(declaration => {
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(?:style|v-html|dangerouslySetInnerHTML)\s*=|\{\s*\.\.\./.test(attributes)) found.push(issue('unsafe-markup-projection', match[0]));
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 (/\.(?:css|scss|less|sass|styl)$/.test(module) && !rules.frameworkStyleImports?.includes(module)
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) && ['style', 'dangerouslySetInnerHTML'].includes(node.name.getText(tree))) found.push(issue('unsafe-markup-projection', node.getText(tree)));
59
- if (ts.isPropertyAccessExpression(node) && ['innerHTML', 'outerHTML', 'style', 'insertAdjacentHTML', 'createElement'].includes(node.name.text)) found.push(issue('dynamic-ui-bypass', node.getText(tree)));
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|wxml|wxss|axml|acss)$/i.test(name)) return [issue('unsupported-ui-language', name)];
73
- if (/\.css$/i.test(name)) return styleIssues(source, name, rules);
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 (styles.length) found.push(issue('central-style-source-required', name));
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 write refused: ${introduced.map(item => item.rule).join(', ')}. Follow the approved framework, component, token and field projection rules`);
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);
@@ -1,46 +1,20 @@
1
1
  import postcss from 'postcss';
2
2
 
3
- function invalid(path, line = 1, column = 1) {
4
- throw Object.assign(new Error(`${path}:${line}:${column}: CSS source could not be parsed`), { code: 'ENTROROVE_IMPORT_INVALID' });
5
- }
6
- const increment = (map, key) => map.set(key, (map.get(key) ?? 0) + 1);
7
- function resourceValue(value) {
8
- const syntax = value.replace(/\/\*[\s\S]*?\*\//g, '').replace(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g, '""');
9
- return /(?:url|image-set|expression)\s*\(/i.test(syntax) || syntax.includes('\\');
10
- }
11
- export function cssSourceProjection(path, source) {
12
- if (typeof source !== 'string' || Buffer.byteLength(source) > 2_097_152) invalid(path);
13
- let root;
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
- const { resources } = cssSourceProjection(path, source);
36
- // The accepted graph does not establish stylesheet asset edges. Keep such
37
- // origins unresolved instead of dropping references or claiming completeness.
38
- return { edges: [], dynamic: resources.length > 0,
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 { cssSourceProjection, sameCssResourceBindings } from './local-css-source.mjs';
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
- const original = cssSourceProjection(path, before), candidate = cssSourceProjection(path, after);
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 { fail(); }
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: 'terminal', stability: 'stable' }),
4
- ENTROROVE_OPERATION_UNKNOWN: Object.freeze({ disposition: 'terminal', stability: 'stable' }),
5
- ENTROROVE_LOCAL_FAILURE: Object.freeze({ disposition: 'terminal', stability: 'stable' }),
6
- DIV_ZERO: Object.freeze({ disposition: 'terminal', stability: 'stable' }),
7
- ENTROROVE_MUTATE_NOT_ACCEPTED: Object.freeze({ disposition: 'needs-human', stability: 'stable' }),
8
- ENTROROVE_MUTATION_AUTHORITY_REQUIRED: Object.freeze({ disposition: 'needs-human', stability: 'stable' }),
9
- ENTROROVE_BUDGET_LEASE_EXPIRED: Object.freeze({ disposition: 'retriable', stability: 'stable' }),
10
- ENTROROVE_GIT_READER_UNSUPPORTED: Object.freeze({ disposition: 'terminal', stability: 'stable' }),
11
- ENTROROVE_GIT_UNAVAILABLE: Object.freeze({ disposition: 'needs-human', stability: 'stable' }),
12
- ENTROROVE_DISCIPLINE_NEEDS_HUMAN: Object.freeze({ disposition: 'needs-human', stability: 'stable' }),
13
- ENTROROVE_DISCIPLINE_USAGE_UNKNOWN: Object.freeze({ disposition: 'needs-human', stability: 'stable' }),
14
- ENTROROVE_DISCIPLINE_REVIEW_REQUIRED: Object.freeze({ disposition: 'needs-human', stability: 'stable' }),
15
- ENTROROVE_DISCIPLINE_DATA_SNAPSHOT_REQUIRED: Object.freeze({ disposition: 'needs-human', stability: 'stable' }),
16
- ENTROROVE_MUTATION_DELEGATION_CHANGE: Object.freeze({ disposition: 'terminal', stability: 'stable' }),
17
- ENTROROVE_MUTATION_DELEGATION_INVALID: Object.freeze({ disposition: 'terminal', stability: 'stable' }),
18
- ENTROROVE_IDE_GUARD_REQUIRED: Object.freeze({ disposition: 'needs-human', stability: 'stable' }),
19
- ENTROROVE_IDE_GUARD_UNSUPPORTED: Object.freeze({ disposition: 'terminal', stability: 'stable' }),
20
- ENTROROVE_DESIGN_REQUIRED: Object.freeze({ disposition: 'needs-human', stability: 'stable' }),
21
- ENTROROVE_DESIGN_APPROVAL_REQUIRED: Object.freeze({ disposition: 'needs-human', stability: 'stable' }),
22
- ENTROROVE_DESIGN_LINT: Object.freeze({ disposition: 'terminal', stability: 'stable' }),
23
- ENTROROVE_DESIGN_ACCEPTANCE: Object.freeze({ disposition: 'terminal', stability: 'stable' }),
24
- ENTROROVE_PACKAGE_DOWNGRADE: Object.freeze({ disposition: 'needs-human', stability: 'stable' }),
25
- ENTROROVE_PACKAGE_VERSION_CONFLICT: Object.freeze({ disposition: 'needs-human', stability: 'stable' }),
26
- ENTROROVE_PACKAGE_VERSION_UNKNOWN: Object.freeze({ disposition: 'needs-human', stability: 'stable' }),
27
- ENTROROVE_PACKAGE_IDENTITY_CHANGED: Object.freeze({ disposition: 'needs-human', stability: 'stable' }),
28
- ENTROROVE_PACKAGE_PROVENANCE_REQUIRED: Object.freeze({ disposition: 'needs-human', stability: 'stable' }),
29
- ENTROROVE_ROLLBACK_PROOF_REQUIRED: Object.freeze({ disposition: 'needs-human', stability: 'stable' }),
30
- ENTROROVE_ROLLBACK_HISTORY_INVALID: Object.freeze({ disposition: 'needs-human', stability: 'stable' }),
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
- const catalogued = LOCAL_ERROR_CODE_CATALOG[code];
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 : fallbackCode;
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, disposition: localErrorDisposition(code), message, ...report };
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|dynamic-reference)/.test(item.reason));
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 prerequisite.servicePrerequisite ? JSON.stringify({ status: 'blocked', code: error.code,
44
- message: error.message, ...prerequisite, ...(error.requestId ? { requestId: error.requestId } : {}),
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,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": "entrorove.skill-manifest/1.0",
3
3
  "name": "entrorove",
4
- "version": "0.1.88",
4
+ "version": "0.1.90",
5
5
  "endpoint": "https://entrorove.com",
6
6
  "requestSchemaVersion": "entrorove.skill.request/2.0",
7
7
  "supportedRequestSchemaVersions": [
@@ -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 (!state.refreshOriginal && state.attestation) {
16
- const input = { challengeId: old.challengeId, attestation: state.attestation };
17
- if (!state.pending) {
18
- state.pending = { stage: 'issued', operation: 'mutation-pass-issue', input,
19
- requestId: buildInvocation('mutation-pass-issue', input).requestId };
20
- await save(location, state);
21
- }
22
- if (state.pending.stage === 'issued') {
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');
@@ -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
- return withStateLock(file, () => connect(identity, observed, mutator, journal, dependencies, contextAuthority));
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.88",
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",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": "entrorove.release-state/1.0",
3
- "version": "0.1.88",
4
- "digest": "f62a7cf30d1c8df4311ed963638244c25beee089fba8787e249aeadbec2fb827",
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": "05bed65a4636a2128e9a48d2e910c5f5a25f0e990f181f58008dd09cac0fbcb5",
37
- "mutation-challenge-refresh.mjs": "f24be466c65bdea543e23e71c4c2d8f65ab611a0380a1464e45908e7f35c3e3b",
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": "29ab700f6a308adad5f3984bf9dd2734755a29fbd0e963e630222565846b5942",
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": "289bf58933818ff13b217d0d75afd0b81cc59bc09b4ea88f50a2ab900e9cf1c2",
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": "98ec1220fd6fad71b7c6ffc96e3bf2e130f76b6e60c47c221d4fb83261962a09",
64
- "local-css-source.mjs": "0c76f0a3c940ba721132840ad5daa1fcc5c0b25c45e134f7c14e79fb7f2987a2",
65
- "mutation-client.mjs": "7c5651e12fef417f5f61e71ba6aef3c69d7381a162adbe38c1c61d7234d812ff",
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": "5825cde4254314d2be1b5cf9c81d4f5effd3e3a0e8cba599f3873303ba6ac015",
97
- "REUSE-DISCIPLINE.md": "f1d9c2002c0214bad0aa72aa696b39a4bd6ee4b5a236e804799e22de3418c686",
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": "76adb33d0faf0cfcdef7583a2f4835eccc7e49811480e2fd2b09779a81c0cbab",
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": "c0662a9dac3b6cccc374a6f57f7c7128741c3705c2c48c06509eda52ba493aaf",
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": "f51af624f4970dfec84361ea73d5bf9e630a4e155e3929e63ed773a5f18a7a9f",
179
+ "SKILL.md": "87390287c0717e52969598e48f48b103d999f8c330dc63d0b246b467ebca3e9a",
179
180
  "CAPABILITY-MAP.md": "57805ba6a4b4fc79ffadb3bd1436999f82c295feac7bdc230bdc123f67662546",
180
181
  "AGREEMENT.md": "f93a68e92c457200dd024610e65f74e478b8f60860f951da818b70561f1b9bf8",
181
182
  "DELIVERY-BOUNDARIES.md": "cb23ce71b2197085c8441ba7c5b6c05fb21f2a954bf66b4d60cfef8b15895b8b",
182
- "package.json": "6bf43114da579429168e2344bf49ce2d3dd9d4496eb149279ba3bfa211321989",
183
+ "package.json": "ec045f7ba5d84bdd18c941e16bd6ab4b4d9b10bf2c355809ef22a1930845bfe6",
183
184
  "manifest.json": "ab06e307795bc9da5add926752b6c4dbdaf18625090261a035aa356a679c504f",
184
- "release.mjs": "2070c03cbaaa64387452d07687c6ce8d6c4ff6cfeb2b10094082621e10e8588f",
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": "c0d23310569dd73a697d375acf1fe8d720ebc53e07cb1e86865b6639fbcc0ad3",
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": "5f74be6d699c35f9ac3f251adaff682c1b736b47561f40d695a5ce9f94a8a2c9",
264
+ "remote-client.mjs": "5677f843a7609b15e8d632f22dbddf0ab970c10775218982377495f90fde7a10",
262
265
  "service-prerequisite.mjs": "7ac5a66d8b423941a5a2cef3aecce18a681aaf2b1a61612fecfa17f5d99f302d",
263
- "local-source-execution.mjs": "88f6ce1ea50d0a7b92e0cb47d535f8140491c52d9c6c0dfddf91ea90397a36f5",
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)) throw new Error(`Invalid ${operation} input: ${validate.errors.map(error => `${error.instancePath || '/'} ${error.message}`).join('; ')}`);
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,2 @@
1
+ export function skillRepairGuidance(issue?: Record<string, any>, context?: Record<string, any>): Record<string, any>;
2
+ export function withSkillRepair<T>(value: T, context?: Record<string, any>): T;
@@ -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
+ }