@entrorove/skill 0.1.73 → 0.1.78
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CAPABILITY-MAP.md +5 -0
- package/CHANGELOG.md +37 -0
- package/DESIGN-GATES.md +5 -9
- package/DESIGN-STAGED-ACCEPTANCE.md +103 -0
- package/IDE-EXECUTION.md +21 -4
- package/REUSE-DISCIPLINE.md +3 -3
- package/SKILL.md +14 -4
- package/cli.mjs +8 -4
- package/cloud-mutation-authority.mjs +8 -2
- package/cloud-task-client.mjs +10 -0
- package/cloud-watch-client.mjs +8 -2
- package/design-browser-checks.mjs +18 -5
- package/design-browser-pages.mjs +96 -0
- package/design-browser-state.mjs +1 -0
- package/design-browser.mjs +36 -40
- package/design-contracts.mjs +4 -1
- package/design-declaration.mjs +3 -0
- package/design-frameworks.mjs +2 -1
- package/design-gate.mjs +3 -0
- package/design-operations.mjs +4 -1
- package/design-policy.mjs +12 -4
- package/design-release.mjs +6 -0
- package/design-research-network.mjs +82 -0
- package/design-research.mjs +8 -25
- package/design-runner.mjs +3 -0
- package/design-stage-contracts.mjs +12 -0
- package/design-stage.mjs +78 -0
- package/design-storage.mjs +63 -0
- package/fs-request-trace.mjs +67 -0
- package/local-contracts.mjs +1 -1
- package/local-discipline-repository.mjs +4 -2
- package/local-invocations.mjs +6 -0
- package/local-mutation-files.mjs +13 -5
- package/local-probe.mjs +8 -4
- package/local-recovery-budget.mjs +31 -0
- package/local-scope-repository.mjs +18 -3
- package/local-scope-signature.mjs +2 -2
- package/local-source-execution.mjs +47 -0
- package/manifest.json +1 -1
- package/mutation-challenge-refresh.mjs +3 -0
- package/mutation-client.mjs +17 -5
- package/native-client.mjs +4 -1
- package/package.json +14 -1
- package/release-state.json +63 -50
- package/release.mjs +6 -1
- package/releases.json +30 -0
- package/remote-capability-cache.mjs +20 -0
- package/remote-client.mjs +24 -4
- package/scope-amendment-client.mjs +62 -6
- package/scope-amendment-observer.mjs +9 -3
- package/scope-amendment-remote.mjs +20 -6
- package/scope-amendment-retirement.mjs +4 -2
- package/scope-amendment-state.mjs +9 -5
- package/scope-context-client.mjs +7 -0
- package/scope-context-journal.mjs +8 -2
- package/scope-context-remote.mjs +16 -5
- package/scope-evidence-remote.mjs +16 -7
- package/scope-runner-client.mjs +34 -7
- package/scope-runner-journal.mjs +82 -5
- package/scope-write-observation.d.mts +4 -0
- package/scope-write-observation.mjs +43 -0
- package/service-prerequisite.mjs +87 -0
- package/task-entry-client.mjs +3 -0
- package/workflow-observation-client.mjs +2 -0
- package/workflow-observation-remote.mjs +13 -5
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { canonical } from './local-integrity.mjs';
|
|
2
|
+
import { consumeRecovery } from './local-recovery-budget.mjs';
|
|
3
|
+
|
|
4
|
+
const fields = ['schemaVersion', 'error', 'code', 'version', 'requiredSetDigest', 'confirmationUrl',
|
|
5
|
+
'grantsExecutionAuthority', 'recoverable', 'preAdmission', 'knownBeforeEffects'];
|
|
6
|
+
const bound = 4096;
|
|
7
|
+
export function servicePrerequisite(response, value) {
|
|
8
|
+
if (response?.status !== 403 || !value || typeof value !== 'object' || Array.isArray(value)
|
|
9
|
+
|| Object.keys(value).sort().join('\0') !== [...fields].sort().join('\0')
|
|
10
|
+
|| value.schemaVersion !== 'entrorove.account-agreement-required/1.0'
|
|
11
|
+
|| value.error !== 'account-agreement-required' || value.code !== 'ENTROROVE_ACCOUNT_AGREEMENT_REQUIRED'
|
|
12
|
+
|| !/^\d{4}-\d{2}-\d{2}\.[1-9][0-9]{0,5}$/.test(value.version ?? '')
|
|
13
|
+
|| !/^[a-f0-9]{64}$/.test(value.requiredSetDigest ?? '') || value.confirmationUrl !== '/?legal=confirm'
|
|
14
|
+
|| value.grantsExecutionAuthority !== false || value.recoverable !== true
|
|
15
|
+
|| typeof value.preAdmission !== 'boolean' || value.preAdmission !== value.knownBeforeEffects
|
|
16
|
+
|| Buffer.byteLength(JSON.stringify(value)) > bound) return null;
|
|
17
|
+
return Object.fromEntries(fields.map(field => [field, value[field]]));
|
|
18
|
+
}
|
|
19
|
+
export function prerequisiteError(response, value, binding = {}) {
|
|
20
|
+
const prerequisite = servicePrerequisite(response, value);
|
|
21
|
+
if (!prerequisite) return null;
|
|
22
|
+
return Object.assign(new Error('The account must explicitly confirm the current service agreement on Entrorove before retrying the original request'),
|
|
23
|
+
{ code: prerequisite.code, servicePrerequisite: prerequisite, ...binding,
|
|
24
|
+
knownBeforeEffects: binding.outcomeUnknown === true ? false : prerequisite.knownBeforeEffects,
|
|
25
|
+
grantsExecutionAuthority: false, recoverable: true });
|
|
26
|
+
}
|
|
27
|
+
export function prerequisiteFields(error) {
|
|
28
|
+
const value = servicePrerequisite({ status: 403 }, error?.servicePrerequisite);
|
|
29
|
+
return value ? { servicePrerequisite: value, grantsExecutionAuthority: false, recoverable: true,
|
|
30
|
+
interaction: { kind: 'confirm-account-agreement', confirmationUrl: value.confirmationUrl,
|
|
31
|
+
version: value.version, requiredSetDigest: value.requiredSetDigest },
|
|
32
|
+
...(error.knownBeforeTransmission ? { knownBeforeTransmission: true } : {}),
|
|
33
|
+
...(error.knownBeforeEffects ? { knownBeforeEffects: true } : {}) } : {};
|
|
34
|
+
}
|
|
35
|
+
export function discoveryPrerequisite(error, requestId) {
|
|
36
|
+
if (!servicePrerequisite({ status: 403 }, error?.servicePrerequisite)) return error;
|
|
37
|
+
Object.assign(error, { sourceRequestId: error.requestId, requestId, knownBeforeTransmission: true });
|
|
38
|
+
delete error.outcomeUnknown; return error;
|
|
39
|
+
}
|
|
40
|
+
export async function retainPrerequisite(error, state, location, save, field = 'pending') {
|
|
41
|
+
const pending = state[field], value = servicePrerequisite({ status: 403 }, error?.servicePrerequisite);
|
|
42
|
+
const identity = pending?.requestId ?? pending?.uploadId;
|
|
43
|
+
if (!pending || !value || !(error.knownBeforeTransmission || error.knownBeforeEffects)
|
|
44
|
+
|| (error.requestId ?? error.uploadId) !== identity) return false;
|
|
45
|
+
const { servicePrerequisite: _previous, ...original } = pending;
|
|
46
|
+
const receipt = { originalId: identity, original: structuredClone(original), response: value, httpStatus: 403,
|
|
47
|
+
...(error.sourceRequestId ? { sourceRequestId: error.sourceRequestId } : {}),
|
|
48
|
+
...(error.knownBeforeTransmission ? { originalNotSent: true } : {}) };
|
|
49
|
+
pending.servicePrerequisite = receipt;
|
|
50
|
+
state.servicePrerequisiteHistory = [...(state.servicePrerequisiteHistory ?? []), receipt];
|
|
51
|
+
await save(location, state); return true;
|
|
52
|
+
}
|
|
53
|
+
export async function retryKnownPrerequisite(state, location, save, field = 'pending') {
|
|
54
|
+
const pending = state[field], receipt = pending?.servicePrerequisite;
|
|
55
|
+
if (!receipt) return false;
|
|
56
|
+
const value = servicePrerequisite({ status: receipt.httpStatus }, receipt.response);
|
|
57
|
+
const { servicePrerequisite: _receipt, ...original } = pending;
|
|
58
|
+
if (!value || receipt.originalId !== (pending.requestId ?? pending.uploadId)
|
|
59
|
+
|| canonical(receipt.original) !== canonical(original)
|
|
60
|
+
|| !(receipt.originalNotSent === true || value.preAdmission === true && value.knownBeforeEffects === true)) {
|
|
61
|
+
throw new Error('Original service prerequisite receipt is invalid; preserve its journal without retransmission');
|
|
62
|
+
}
|
|
63
|
+
// Persist a new unresolved attempt before sending. If that attempt loses its
|
|
64
|
+
// response, later calls must use GET even though an earlier attempt was denied.
|
|
65
|
+
consumeRecovery(state, 'prerequisite');
|
|
66
|
+
delete pending.servicePrerequisite; await save(location, state); return true;
|
|
67
|
+
}
|
|
68
|
+
export async function fetchPrerequisiteError(response, binding = {}) {
|
|
69
|
+
if (response.status !== 403 || !response.headers.get('content-type')?.startsWith('application/json')) return null;
|
|
70
|
+
const reader = response.body?.getReader(); if (!reader) return null;
|
|
71
|
+
const chunks = []; let size = 0;
|
|
72
|
+
try {
|
|
73
|
+
while (true) {
|
|
74
|
+
const { done, value } = await reader.read(); if (done) break;
|
|
75
|
+
size += value.byteLength; if (size > bound) return null; chunks.push(Buffer.from(value));
|
|
76
|
+
}
|
|
77
|
+
return prerequisiteError(response, JSON.parse(Buffer.concat(chunks).toString('utf8')), binding);
|
|
78
|
+
} catch { return null; } finally { await reader.cancel().catch(() => {}); }
|
|
79
|
+
}
|
|
80
|
+
export function streamPrerequisiteError(value, binding = {}) {
|
|
81
|
+
if (!['ENTROROVE_ACCOUNT_AGREEMENT_REQUIRED', 'account-agreement-required'].includes(value?.code)
|
|
82
|
+
|| value.recoverable !== true || value.grantsExecutionAuthority !== false) return null;
|
|
83
|
+
const prerequisite = servicePrerequisite({ status: 403 }, value.prerequisite);
|
|
84
|
+
if (!prerequisite || prerequisite.preAdmission || prerequisite.knownBeforeEffects) return null;
|
|
85
|
+
// This is a running stream notification, not a proof about prior effects.
|
|
86
|
+
return prerequisiteError({ status: 403 }, prerequisite, { ...binding, outcomeUnknown: true });
|
|
87
|
+
}
|
package/task-entry-client.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { requestTransport, parseTransportResponse, trustedOrigin } from './remote-transport.mjs';
|
|
2
2
|
import { runCloudTask } from './cloud-task-client.mjs';
|
|
3
3
|
import { displayIdeEvent, ideClientHeaders, ideTaskIdentity } from './ide-presentation.mjs';
|
|
4
|
+
import { prerequisiteError } from './service-prerequisite.mjs';
|
|
4
5
|
|
|
5
6
|
/** New IDE tasks use the authenticated account preference; resumes keep their original route. */
|
|
6
7
|
export async function routeTaskEntry(data, token, dependencies) {
|
|
@@ -20,6 +21,8 @@ export async function routeTaskEntry(data, token, dependencies) {
|
|
|
20
21
|
headers: { authorization: `Bearer ${token}`,...ideClientHeaders() },timeoutMs: 15000,
|
|
21
22
|
});
|
|
22
23
|
const preference = parseTransportResponse(response, 'cloud-sandbox preference');
|
|
24
|
+
const prerequisite = prerequisiteError(response, preference, { knownBeforeTransmission: true });
|
|
25
|
+
if (prerequisite) throw prerequisite;
|
|
23
26
|
if (!response.ok || typeof preference?.enabled !== 'boolean') {
|
|
24
27
|
throw new Error(`Cloud sandbox preference unavailable: HTTP ${response.status}. Task entry stopped.`);
|
|
25
28
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { prerequisiteFields } from './service-prerequisite.mjs';
|
|
1
2
|
import { bootstrapCloudMutationAuthority } from './cloud-mutation-authority.mjs';
|
|
2
3
|
import { observationInput } from './workflow-observation-wire.mjs';
|
|
3
4
|
|
|
@@ -31,6 +32,7 @@ function blocked(error, input) {
|
|
|
31
32
|
&& Number.isSafeInteger(cause.rejection.generation) && cause.rejection.generation > 0
|
|
32
33
|
&& ['member-observation-admit', 'member-observation-report'].includes(cause.rejection.operation);
|
|
33
34
|
return { schemaVersion: 'entrorove.local-observation-connection/1.0', status: 'blocked',
|
|
35
|
+
...prerequisiteFields(cause),
|
|
34
36
|
...(safeId(input?.flowId) ? { flowId: input.flowId } : {}), independentValidation: 'pending', code,
|
|
35
37
|
message: messages[code] ?? 'Local observation could not continue; its original command and result remain preserved',
|
|
36
38
|
...(recoverable ? { prerequisite: { status: 'blocked', code, recoverable: true, ...(reason ? { reason } : {}),
|
|
@@ -6,6 +6,7 @@ import { acknowledgeAutomaticFeedback, queryAutomaticFeedback, verifyAutomaticFe
|
|
|
6
6
|
import { saveObservationState } from './workflow-observation-state.mjs';
|
|
7
7
|
import { observationFail } from './workflow-observation-wire.mjs';
|
|
8
8
|
import { readInvocations } from './local-invocations.mjs';
|
|
9
|
+
import { prerequisiteError, retainPrerequisite, retryKnownPrerequisite } from './service-prerequisite.mjs';
|
|
9
10
|
|
|
10
11
|
const unknown = requestId => Object.assign(new Error('Original local observation request outcome is unknown; recover its GET without repeating execution'),
|
|
11
12
|
{ code: 'ENTROROVE_LOCAL_OBSERVATION_UNKNOWN', outcomeUnknown: true, requestId });
|
|
@@ -25,12 +26,15 @@ async function recover(pending, dependencies) {
|
|
|
25
26
|
response = await requestTransport(origin, `/api/skill/status/${pending.requestId}`, { headers: {
|
|
26
27
|
authorization: `Bearer ${token}`, ...ideClientHeaders() } });
|
|
27
28
|
result = parseTransportResponse(response, 'original local observation status');
|
|
29
|
+
const prerequisite = prerequisiteError(response, result, { requestId: pending.requestId });
|
|
30
|
+
if (prerequisite) throw Object.assign(unknown(pending.requestId), { servicePrerequisite: prerequisite.servicePrerequisite });
|
|
28
31
|
if (result?.output?.requestId !== pending.requestId || !response.ok && response.status >= 500
|
|
29
32
|
|| ['pending', 'running'].includes(result.output.status)) throw unknown(pending.requestId);
|
|
30
33
|
result.feedback ??= await queryAutomaticFeedback(pending.requestId, dependencies);
|
|
31
34
|
verifyAutomaticFeedback(result.feedback, pending.requestId, pending.requestId);
|
|
32
35
|
result.feedback = await acknowledgeAutomaticFeedback(result.feedback, origin, token, ideClientHeaders());
|
|
33
|
-
} catch { throw unknown(pending.requestId)
|
|
36
|
+
} catch (error) { throw Object.assign(unknown(pending.requestId),
|
|
37
|
+
error?.servicePrerequisite ? { servicePrerequisite: error.servicePrerequisite } : {}); }
|
|
34
38
|
return { response, result };
|
|
35
39
|
}
|
|
36
40
|
function prerequisite(output) {
|
|
@@ -66,21 +70,25 @@ function terminalRejection(result, pending) {
|
|
|
66
70
|
export async function observationRemote(stage, operation, input, state, location, dependencies) {
|
|
67
71
|
const requestId = buildInvocation(operation, input).requestId;
|
|
68
72
|
let response, result;
|
|
69
|
-
|
|
73
|
+
const existing = Boolean(state.pending);
|
|
74
|
+
if (existing) {
|
|
70
75
|
if (state.pending.stage !== stage || state.pending.operation !== operation || state.pending.requestId !== requestId
|
|
71
76
|
|| canonical(state.pending.input) !== canonical(input)) observationFail('Original observation request must finish before another operation');
|
|
77
|
+
}
|
|
78
|
+
if (existing && !await retryKnownPrerequisite(state, location, saveObservationState)) {
|
|
72
79
|
({ response, result } = await recover(state.pending, dependencies));
|
|
73
80
|
} else {
|
|
74
|
-
state.pending = { stage, operation, input, requestId };
|
|
81
|
+
if (!existing) state.pending = { stage, operation, input, requestId };
|
|
75
82
|
try { await saveObservationState(location, state); } catch { throw notSent(requestId); }
|
|
76
83
|
try { ({ response, result } = await invokeRemoteResult({ operation, input }, dependencies)); }
|
|
77
|
-
catch {
|
|
84
|
+
catch (error) {
|
|
85
|
+
if (await retainPrerequisite(error, state, location, saveObservationState)) throw error;
|
|
78
86
|
if (await knownNotSent(state.pending, dependencies.home)) {
|
|
79
87
|
delete state.pending;
|
|
80
88
|
try { await saveObservationState(location, state); } catch { throw notSent(requestId); }
|
|
81
89
|
throw notSent(requestId);
|
|
82
90
|
}
|
|
83
|
-
throw unknown(requestId);
|
|
91
|
+
throw Object.assign(unknown(requestId), error?.servicePrerequisite ? { servicePrerequisite: error.servicePrerequisite } : {});
|
|
84
92
|
}
|
|
85
93
|
}
|
|
86
94
|
const statuses = ['reported', 'succeeded', ...(operation === 'member-observation-report' ? ['local-accepted'] : [])];
|