@entrorove/skill 0.1.51 → 0.1.53

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 (51) hide show
  1. package/CAPABILITY-MAP.md +14 -2
  2. package/CHANGELOG.md +12 -0
  3. package/DELIVERY-BOUNDARIES.md +4 -2
  4. package/IDE-EXECUTION.md +25 -3
  5. package/SKILL.md +8 -4
  6. package/cli.mjs +4 -6
  7. package/cloud-mutation-authority.mjs +96 -0
  8. package/connected-cli.mjs +20 -0
  9. package/ide-command.mjs +1 -1
  10. package/ide-hook.mjs +24 -10
  11. package/ide-results.mjs +8 -6
  12. package/ide-session.mjs +44 -1
  13. package/local-approved-plan.mjs +3 -21
  14. package/local-approved-process.mjs +54 -0
  15. package/local-approved-supervisor.mjs +99 -0
  16. package/local-cli.mjs +13 -2
  17. package/local-contracts.mjs +2 -0
  18. package/local-delegated-modify-analysis.mjs +232 -0
  19. package/local-discipline-actions.mjs +2 -0
  20. package/local-discipline-analysis.mjs +11 -5
  21. package/local-discipline-gate.mjs +3 -1
  22. package/local-discipline-repository.mjs +7 -4
  23. package/local-error.mjs +3 -0
  24. package/local-execution.mjs +1 -1
  25. package/local-mutation-authority.mjs +23 -5
  26. package/local-mutation-batch.mjs +1 -0
  27. package/local-mutation-binding.mjs +14 -5
  28. package/local-mutation-consumption.mjs +93 -0
  29. package/local-mutation-pass-store.mjs +6 -4
  30. package/local-mutation-wire.mjs +21 -4
  31. package/local-preserved-mutation-protection.mjs +51 -0
  32. package/local-preserved-mutation-signer.mjs +81 -0
  33. package/local-preserved-mutation-state.mjs +96 -0
  34. package/local-preserved-mutation-wire.mjs +49 -0
  35. package/local-scope-import-expressions.mjs +80 -0
  36. package/local-scope-repository.mjs +32 -21
  37. package/local-typescript-function-reference.mjs +98 -0
  38. package/manifest.json +1 -1
  39. package/mutation-client.mjs +290 -0
  40. package/package.json +22 -3
  41. package/release-state.json +49 -30
  42. package/release.mjs +25 -0
  43. package/releases.json +12 -0
  44. package/remote-client.mjs +2 -1
  45. package/reuse-discipline-release.mjs +3 -0
  46. package/workflow-observation-client.mjs +44 -0
  47. package/workflow-observation-connection.mjs +35 -0
  48. package/workflow-observation-remote.mjs +84 -0
  49. package/workflow-observation-runner.mjs +87 -0
  50. package/workflow-observation-state.mjs +36 -0
  51. package/workflow-observation-wire.mjs +69 -0
@@ -0,0 +1,69 @@
1
+ import { createPublicKey, verify } from 'node:crypto';
2
+ import { canonical, sha256 } from './local-integrity.mjs';
3
+ import { mutationId14, mutationPaths14 } from './local-mutation-files.mjs';
4
+
5
+ export const observationFail = message => { throw Object.assign(new Error(message), { code: 'ENTROROVE_LOCAL_OBSERVATION' }); };
6
+ export const observationIdentityFields = ['taskId', 'flowId', 'stepId', 'leaseId', 'generation', 'settlementId'];
7
+ export function observationExact(value, fields) {
8
+ if (!value || typeof value !== 'object' || Array.isArray(value)
9
+ || canonical(Object.keys(value).sort()) !== canonical([...fields].sort())) observationFail('Local observation fields are invalid');
10
+ }
11
+ export function observationInput(input) {
12
+ observationExact(input, observationIdentityFields);
13
+ for (const field of observationIdentityFields.filter(field => field !== 'generation')) mutationId14(input[field], field);
14
+ if (!Number.isSafeInteger(input.generation) || input.generation < 1) observationFail('Local observation generation is invalid');
15
+ return input;
16
+ }
17
+ function command(value) {
18
+ observationExact(value, ['executable', 'executableSha256', 'args', 'environment', 'timeoutMs', 'maxOutputBytes', 'requiredExitCode']);
19
+ if (typeof value.executable !== 'string' || !value.executable.startsWith('/') || /[\u0000-\u001f\u007f]/.test(value.executable)
20
+ || !/^[a-f0-9]{64}$/.test(value.executableSha256) || !Array.isArray(value.args) || value.args.length > 128
21
+ || value.args.some(arg => typeof arg !== 'string' || arg.length > 4096 || arg.includes('\0'))
22
+ || !value.environment || typeof value.environment !== 'object' || Array.isArray(value.environment)
23
+ || Object.keys(value.environment).length > 64 || Object.entries(value.environment).some(([key, val]) =>
24
+ !/^[A-Za-z_][A-Za-z0-9_]*$/.test(key) || typeof val !== 'string' || val.length > 16_384 || val.includes('\0'))
25
+ || !Number.isSafeInteger(value.timeoutMs) || value.timeoutMs < 1 || value.timeoutMs > 300_000
26
+ || !Number.isSafeInteger(value.maxOutputBytes) || value.maxOutputBytes < 1 || value.maxOutputBytes > 1_048_576
27
+ || value.requiredExitCode !== 0) observationFail('Local observation command differs from its bounded frozen policy');
28
+ }
29
+ function files(values, modes) {
30
+ if (!Array.isArray(values) || !values.length || values.length > (modes ? 512 : 2_000)) observationFail('Local observation manifest is invalid');
31
+ const paths = mutationPaths14(values.map(file => file.path));
32
+ if (canonical(paths) !== canonical(values.map(file => file.path))) observationFail('Local observation manifest must be sorted and unique');
33
+ for (const file of values) {
34
+ observationExact(file, ['path', 'sha256', ...(modes ? ['mode'] : [])]);
35
+ if (!/^[a-f0-9]{64}$/.test(file.sha256) || modes && file.mode !== null
36
+ && (!Number.isSafeInteger(file.mode) || file.mode < 0 || file.mode > 511)) observationFail('Local observation file baseline is invalid');
37
+ }
38
+ }
39
+ function signature(value) {
40
+ try {
41
+ const encoded = process.env.ENTROROVE_WORKFLOW_PLAN_PUBLIC_KEY, der = Buffer.from(encoded ?? '', 'base64');
42
+ const key = createPublicKey({ key: der, type: 'spki', format: 'der' }), receipt = value.authorityReceipt;
43
+ observationExact(receipt, ['schemaVersion', 'keyId', 'payloadDigest', 'signature']);
44
+ const { authorityReceipt: _, ...core } = value, bytes = Buffer.from(canonical(core));
45
+ const signed = Buffer.from(receipt.signature, 'base64url');
46
+ if (key.asymmetricKeyType !== 'ed25519' || key.export({ type: 'spki', format: 'der' }).toString('base64') !== encoded
47
+ || receipt.schemaVersion !== 'entrorove.workflow-authority-receipt/1.0' || receipt.keyId !== sha256(der)
48
+ || receipt.payloadDigest !== sha256(bytes) || bytes.length > 1_048_576
49
+ || signed.length !== 64 || signed.toString('base64url') !== receipt.signature || !verify(null, bytes, key, signed)) throw new Error();
50
+ } catch { observationFail('Local observation requires its original pinned public cloud admission signature'); }
51
+ }
52
+ export function observationAdmission(value, input, { allowExpired = false } = {}) {
53
+ observationExact(value, [...observationIdentityFields, 'schemaVersion', 'purpose', 'observationId', 'ownerId', 'taskDigest',
54
+ 'chainDigest', 'epoch', 'planId', 'planDigest', 'executionPlanSha256', 'repositoryDigest', 'settlementDigest', 'command',
55
+ 'sourceManifest', 'protectedFiles', 'issuedAt', 'expiresAt', 'authorityReceipt']);
56
+ signature(value);
57
+ const start = Date.parse(value.issuedAt), end = Date.parse(value.expiresAt);
58
+ if (value.schemaVersion !== 'entrorove.local-observation-admission/1.0' || value.purpose !== 'observe-approved-command'
59
+ || value.epoch !== 0 || observationIdentityFields.some(field => value[field] !== input[field])
60
+ || value.repositoryDigest !== sha256(input.repositoryRoot).slice(0, 32) || !Number.isFinite(start) || !Number.isFinite(end)
61
+ || start > Date.now() || end <= start || end - start > 600_000 || !allowExpired && end <= Date.now()
62
+ || !/^[a-f0-9-]{36}$/.test(value.ownerId)
63
+ || ['taskDigest', 'chainDigest', 'planDigest', 'executionPlanSha256', 'settlementDigest'].some(field => !/^[a-f0-9]{64}$/.test(value[field]))) {
64
+ observationFail('Local observation admission is expired or lost its original task, repository or settlement');
65
+ }
66
+ mutationId14(value.observationId, 'observationId'); mutationId14(value.planId, 'planId');
67
+ command(value.command); files(value.sourceManifest, true); files(value.protectedFiles, false);
68
+ return value;
69
+ }