@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.
Files changed (65) hide show
  1. package/CAPABILITY-MAP.md +5 -0
  2. package/CHANGELOG.md +37 -0
  3. package/DESIGN-GATES.md +5 -9
  4. package/DESIGN-STAGED-ACCEPTANCE.md +103 -0
  5. package/IDE-EXECUTION.md +21 -4
  6. package/REUSE-DISCIPLINE.md +3 -3
  7. package/SKILL.md +14 -4
  8. package/cli.mjs +8 -4
  9. package/cloud-mutation-authority.mjs +8 -2
  10. package/cloud-task-client.mjs +10 -0
  11. package/cloud-watch-client.mjs +8 -2
  12. package/design-browser-checks.mjs +18 -5
  13. package/design-browser-pages.mjs +96 -0
  14. package/design-browser-state.mjs +1 -0
  15. package/design-browser.mjs +36 -40
  16. package/design-contracts.mjs +4 -1
  17. package/design-declaration.mjs +3 -0
  18. package/design-frameworks.mjs +2 -1
  19. package/design-gate.mjs +3 -0
  20. package/design-operations.mjs +4 -1
  21. package/design-policy.mjs +12 -4
  22. package/design-release.mjs +6 -0
  23. package/design-research-network.mjs +82 -0
  24. package/design-research.mjs +8 -25
  25. package/design-runner.mjs +3 -0
  26. package/design-stage-contracts.mjs +12 -0
  27. package/design-stage.mjs +78 -0
  28. package/design-storage.mjs +63 -0
  29. package/fs-request-trace.mjs +67 -0
  30. package/local-contracts.mjs +1 -1
  31. package/local-discipline-repository.mjs +4 -2
  32. package/local-invocations.mjs +6 -0
  33. package/local-mutation-files.mjs +13 -5
  34. package/local-probe.mjs +8 -4
  35. package/local-recovery-budget.mjs +31 -0
  36. package/local-scope-repository.mjs +18 -3
  37. package/local-scope-signature.mjs +2 -2
  38. package/local-source-execution.mjs +47 -0
  39. package/manifest.json +1 -1
  40. package/mutation-challenge-refresh.mjs +3 -0
  41. package/mutation-client.mjs +17 -5
  42. package/native-client.mjs +4 -1
  43. package/package.json +14 -1
  44. package/release-state.json +63 -50
  45. package/release.mjs +6 -1
  46. package/releases.json +30 -0
  47. package/remote-capability-cache.mjs +20 -0
  48. package/remote-client.mjs +24 -4
  49. package/scope-amendment-client.mjs +62 -6
  50. package/scope-amendment-observer.mjs +9 -3
  51. package/scope-amendment-remote.mjs +20 -6
  52. package/scope-amendment-retirement.mjs +4 -2
  53. package/scope-amendment-state.mjs +9 -5
  54. package/scope-context-client.mjs +7 -0
  55. package/scope-context-journal.mjs +8 -2
  56. package/scope-context-remote.mjs +16 -5
  57. package/scope-evidence-remote.mjs +16 -7
  58. package/scope-runner-client.mjs +34 -7
  59. package/scope-runner-journal.mjs +82 -5
  60. package/scope-write-observation.d.mts +4 -0
  61. package/scope-write-observation.mjs +43 -0
  62. package/service-prerequisite.mjs +87 -0
  63. package/task-entry-client.mjs +3 -0
  64. package/workflow-observation-client.mjs +2 -0
  65. 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
+ }
@@ -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
- if (state.pending) {
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'] : [])];