@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.
- package/CAPABILITY-MAP.md +14 -2
- package/CHANGELOG.md +12 -0
- package/DELIVERY-BOUNDARIES.md +4 -2
- package/IDE-EXECUTION.md +25 -3
- package/SKILL.md +8 -4
- package/cli.mjs +4 -6
- package/cloud-mutation-authority.mjs +96 -0
- package/connected-cli.mjs +20 -0
- package/ide-command.mjs +1 -1
- package/ide-hook.mjs +24 -10
- package/ide-results.mjs +8 -6
- package/ide-session.mjs +44 -1
- package/local-approved-plan.mjs +3 -21
- package/local-approved-process.mjs +54 -0
- package/local-approved-supervisor.mjs +99 -0
- package/local-cli.mjs +13 -2
- package/local-contracts.mjs +2 -0
- package/local-delegated-modify-analysis.mjs +232 -0
- package/local-discipline-actions.mjs +2 -0
- package/local-discipline-analysis.mjs +11 -5
- package/local-discipline-gate.mjs +3 -1
- package/local-discipline-repository.mjs +7 -4
- package/local-error.mjs +3 -0
- package/local-execution.mjs +1 -1
- package/local-mutation-authority.mjs +23 -5
- package/local-mutation-batch.mjs +1 -0
- package/local-mutation-binding.mjs +14 -5
- package/local-mutation-consumption.mjs +93 -0
- package/local-mutation-pass-store.mjs +6 -4
- package/local-mutation-wire.mjs +21 -4
- package/local-preserved-mutation-protection.mjs +51 -0
- package/local-preserved-mutation-signer.mjs +81 -0
- package/local-preserved-mutation-state.mjs +96 -0
- package/local-preserved-mutation-wire.mjs +49 -0
- package/local-scope-import-expressions.mjs +80 -0
- package/local-scope-repository.mjs +32 -21
- package/local-typescript-function-reference.mjs +98 -0
- package/manifest.json +1 -1
- package/mutation-client.mjs +290 -0
- package/package.json +22 -3
- package/release-state.json +49 -30
- package/release.mjs +25 -0
- package/releases.json +12 -0
- package/remote-client.mjs +2 -1
- package/reuse-discipline-release.mjs +3 -0
- package/workflow-observation-client.mjs +44 -0
- package/workflow-observation-connection.mjs +35 -0
- package/workflow-observation-remote.mjs +84 -0
- package/workflow-observation-runner.mjs +87 -0
- package/workflow-observation-state.mjs +36 -0
- 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
|
+
}
|