@entrorove/skill 0.1.64 → 0.1.67
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 +23 -0
- package/CHANGELOG.md +15 -0
- package/DELIVERY-BOUNDARIES.md +2 -0
- package/DESIGN-GATES.md +160 -0
- package/REUSE-DISCIPLINE.md +15 -1
- package/SKILL.md +4 -2
- package/blueprint-criterion-runner.mjs +1 -1
- package/cli.mjs +11 -4
- package/design-browser-checks.mjs +86 -0
- package/design-browser-identity.mjs +13 -0
- package/design-browser-state.mjs +41 -0
- package/design-browser.mjs +181 -0
- package/design-context.mjs +34 -0
- package/design-contracts.d.mts +4 -0
- package/design-contracts.mjs +77 -0
- package/design-declaration.mjs +17 -0
- package/design-frameworks.mjs +4 -0
- package/design-gate.mjs +19 -0
- package/design-lint.mjs +115 -0
- package/design-local-contracts.mjs +15 -0
- package/design-operations.mjs +81 -0
- package/design-policy.d.mts +5 -0
- package/design-policy.mjs +137 -0
- package/design-release.mjs +25 -0
- package/design-research.mjs +99 -0
- package/design-runner.mjs +31 -0
- package/design-store.mjs +116 -0
- package/design-ui.mjs +24 -0
- package/install-identity.mjs +16 -0
- package/local-cli.mjs +3 -1
- package/local-contracts.mjs +2 -0
- package/local-discipline-actions.mjs +5 -0
- package/local-discipline-batch.mjs +12 -3
- package/local-discipline-contracts.mjs +15 -3
- package/local-discipline-gate.mjs +9 -2
- package/local-error.mjs +18 -1
- package/local-execution.mjs +2 -1
- package/local-mutation-authority.mjs +2 -0
- package/local-runner-discipline.mjs +17 -3
- package/local-scope-zip.mjs +21 -0
- package/manifest.json +1 -1
- package/package-archive.mjs +54 -0
- package/package-replacement.mjs +48 -0
- package/package.json +34 -6
- package/release-state.json +50 -24
- package/release.mjs +3 -1
- package/releases.json +18 -0
- package/reuse-discipline-release.mjs +3 -1
- package/rollback-history.mjs +51 -0
- package/rollback-runner.mjs +77 -0
- package/version.mjs +20 -3
- package/workflow-local-runner.mjs +9 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { canonical, sha256 } from './local-integrity.mjs';
|
|
3
|
+
import { mutationFail14, mutationRead14, mutationTarget14 } from './local-mutation-files.mjs';
|
|
4
|
+
import { archiveFormat } from './package-archive.mjs';
|
|
5
|
+
import { assertPackageReplacement } from './package-replacement.mjs';
|
|
6
|
+
import { assertRollbackCandidate, rememberRollbackBaseline } from './rollback-history.mjs';
|
|
7
|
+
|
|
8
|
+
const rollbackVerbs = new Set(['reset', 'restore', 'revert', 'checkout', 'switch']);
|
|
9
|
+
function gitOperation(args) {
|
|
10
|
+
for (let index = 0; index < args.length; index++) {
|
|
11
|
+
const value = args[index];
|
|
12
|
+
if (['-C', '-c', '--git-dir', '--work-tree', '--namespace', '--config-env'].includes(value)) { index++; continue; }
|
|
13
|
+
if (value.startsWith('-')) continue;
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
return '';
|
|
17
|
+
}
|
|
18
|
+
export function assertNoBlindRollback(policy) {
|
|
19
|
+
const executable = path.basename(policy.executable).replace(/\.exe$/i, '').toLowerCase(), args = policy.args ?? [];
|
|
20
|
+
const direct = executable === 'git' && rollbackVerbs.has(gitOperation(args));
|
|
21
|
+
const shell = ['sh', 'bash', 'zsh', 'dash', 'cmd', 'powershell', 'pwsh'].includes(executable);
|
|
22
|
+
const script = args.join(' ');
|
|
23
|
+
const indirect = shell && /(?:^|[;&|\n\s])(?:\S*\/)?git\s+(?:(?:-C|-c|--git-dir|--work-tree)\s+\S+\s+)*(?:reset|restore|revert|checkout|switch)(?:\s|$)/i.test(script);
|
|
24
|
+
if (direct || indirect) mutationFail14('ENTROROVE_ROLLBACK_PROOF_REQUIRED',
|
|
25
|
+
'Direct rollback commands cannot run through an ordinary validation plan; use the independently signed exact-image rollback entry, preserving original transaction and failure records');
|
|
26
|
+
if (shell && /(?:^|[;&|\n\s])(?:\S*\/)?(?:npm|pnpm|yarn|bun)\s+(?:i|install|add|update|upgrade|ci|exec|dlx)(?:\s|$)/i.test(script)) {
|
|
27
|
+
mutationFail14('ENTROROVE_PACKAGE_PROVENANCE_REQUIRED', 'Shell-wrapped package replacement cannot prove its destination or frozen inputs; use a direct frozen package-manager plan or the managed installer');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function assertFrozenInstall(authority) {
|
|
31
|
+
const policy = authority.plan.policy, executable = path.basename(policy.executable).replace(/\.(?:exe|cmd)$/i, '').toLowerCase();
|
|
32
|
+
const args = policy.args ?? [], operation = args.find(value => ['i', 'install', 'add', 'update', 'upgrade', 'ci', 'exec', 'dlx'].includes(value));
|
|
33
|
+
if (!['npm', 'pnpm', 'yarn', 'bun'].includes(executable) || !operation) return;
|
|
34
|
+
const install = ['install', 'i', 'ci'].includes(operation);
|
|
35
|
+
const frozen = executable === 'npm' ? operation === 'ci' : args.includes('--frozen-lockfile') || executable === 'yarn' && args.includes('--immutable');
|
|
36
|
+
const files = authority.plan.files ?? [], manifest = files.some(file => file.path === 'package.json');
|
|
37
|
+
const lock = files.some(file => (executable === 'npm' ? ['package-lock.json', 'npm-shrinkwrap.json']
|
|
38
|
+
: executable === 'pnpm' ? ['pnpm-lock.yaml'] : executable === 'yarn' ? ['yarn.lock'] : ['bun.lock', 'bun.lockb']).includes(file.path));
|
|
39
|
+
const positional = args.slice(args.indexOf(operation) + 1).filter((value, index, rest) => !value.startsWith('-') && !(index && rest[index - 1].startsWith('--')));
|
|
40
|
+
if (!install || !frozen || !manifest || !lock || positional.length || args.some(value => /^(?:--prefix|--dir|--cwd|--global|-g|--force)(?:=|$)/.test(value))) {
|
|
41
|
+
mutationFail14('ENTROROVE_PACKAGE_PROVENANCE_REQUIRED', 'Ordinary runner package installation requires its actual frozen manifest/lock in the original tested source set and no supplied package or destination override; use the managed installer for skill updates');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const packagePath = target => /(?:^|\/)(?:package|manifest)\.json$/i.test(target) || archiveFormat(target, Buffer.alloc(0));
|
|
45
|
+
export async function replacementRunnerBefore(input, authority) {
|
|
46
|
+
assertNoBlindRollback(authority.plan.policy);
|
|
47
|
+
assertFrozenInstall(authority);
|
|
48
|
+
const originals = new Map(), writes = authority.plan.policy.discipline?.writes ?? [];
|
|
49
|
+
for (const write of writes) {
|
|
50
|
+
const current = await mutationTarget14(authority.root, write.targetPath, true);
|
|
51
|
+
if (current.exists) {
|
|
52
|
+
if (current.stat.size > 64 * 1024 * 1024) {
|
|
53
|
+
if (packagePath(write.targetPath)) mutationFail14('ENTROROVE_PACKAGE_VERSION_UNKNOWN', 'Package baseline exceeds bounded inspection; no process was started');
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const bytes = (await mutationRead14(input, authority.root, write.targetPath)).content;
|
|
57
|
+
if (packagePath(write.targetPath) || archiveFormat(write.targetPath, bytes)) originals.set(write.targetPath, bytes);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return originals;
|
|
61
|
+
}
|
|
62
|
+
export async function replacementRunnerAfter(input, authority, before, after, changed, originals) {
|
|
63
|
+
const proof = { kind: 'approved-process-comparison', authorityDigest: authority.planFile?.sha256 ?? sha256(canonical(authority.plan)),
|
|
64
|
+
baselineManifestDigest: sha256(canonical(before)) };
|
|
65
|
+
for (const target of changed) {
|
|
66
|
+
const previous = before.files.find(file => file.path === target), current = after.files.find(file => file.path === target);
|
|
67
|
+
if (!previous || !current || previous.kind !== 'file' || current.kind !== 'file') continue;
|
|
68
|
+
await assertRollbackCandidate(input, authority.root, target, previous.sha256, current.sha256);
|
|
69
|
+
if (previous.sha256 !== current.sha256 && (packagePath(target) || originals.has(target))) {
|
|
70
|
+
if (!originals.has(target)) mutationFail14('ENTROROVE_PACKAGE_VERSION_UNKNOWN', 'Process replaced an undeclared package without a verifiable original image; preserve this failed run and unknown effects');
|
|
71
|
+
const state = await mutationTarget14(authority.root, target);
|
|
72
|
+
if (state.stat.size > 64 * 1024 * 1024) mutationFail14('ENTROROVE_PACKAGE_VERSION_UNKNOWN', 'Replacement exceeds bounded package inspection');
|
|
73
|
+
assertPackageReplacement(target, originals.get(target), (await mutationRead14(input, authority.root, target)).content);
|
|
74
|
+
}
|
|
75
|
+
await rememberRollbackBaseline(input, authority.root, target, previous.sha256, proof);
|
|
76
|
+
}
|
|
77
|
+
}
|
package/version.mjs
CHANGED
|
@@ -1,8 +1,25 @@
|
|
|
1
|
+
// SemVer 2 precedence, without Number rounding or lexical version ordering.
|
|
2
|
+
export function versionParts(value) {
|
|
3
|
+
const match = typeof value === 'string' && /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/.exec(value);
|
|
4
|
+
if (!match || match[4]?.split('.').some(part => /^\d+$/.test(part) && part.length > 1 && part[0] === '0')) {
|
|
5
|
+
throw Object.assign(new Error('Package version must be an unambiguous SemVer value'), { code: 'ENTROROVE_PACKAGE_VERSION_UNKNOWN' });
|
|
6
|
+
}
|
|
7
|
+
return { core: match.slice(1, 4).map(BigInt), pre: match[4]?.split('.') ?? [] };
|
|
8
|
+
}
|
|
1
9
|
export function compareVersions(left, right) {
|
|
2
|
-
const a = left
|
|
3
|
-
const b = right.split('.').map(Number);
|
|
10
|
+
const a = versionParts(left), b = versionParts(right);
|
|
4
11
|
for (let index = 0; index < 3; index += 1) {
|
|
5
|
-
if (a[index] !== b[index]) return a[index]
|
|
12
|
+
if (a.core[index] !== b.core[index]) return a.core[index] > b.core[index] ? 1 : -1;
|
|
13
|
+
}
|
|
14
|
+
if (!a.pre.length || !b.pre.length) return a.pre.length === b.pre.length ? 0 : a.pre.length ? -1 : 1;
|
|
15
|
+
for (let index = 0; index < Math.max(a.pre.length, b.pre.length); index += 1) {
|
|
16
|
+
const x = a.pre[index], y = b.pre[index];
|
|
17
|
+
if (x === y) continue;
|
|
18
|
+
if (x === undefined || y === undefined) return x === undefined ? -1 : 1;
|
|
19
|
+
const numericX = /^\d+$/.test(x), numericY = /^\d+$/.test(y);
|
|
20
|
+
if (numericX && numericY) return BigInt(x) > BigInt(y) ? 1 : -1;
|
|
21
|
+
if (numericX !== numericY) return numericX ? -1 : 1;
|
|
22
|
+
return x > y ? 1 : -1;
|
|
6
23
|
}
|
|
7
24
|
return 0;
|
|
8
25
|
}
|
|
@@ -312,6 +312,15 @@ export async function observeWorkflowLocalStep(rawInput) {
|
|
|
312
312
|
return { ...structuredClone(state.result), cached: true };
|
|
313
313
|
}));
|
|
314
314
|
}
|
|
315
|
+
export function verifyOriginalWorkflowPlan(value) {
|
|
316
|
+
const configured = publicKey(process.env.ENTROROVE_WORKFLOW_PLAN_PUBLIC_KEY?.trim(), 'ENTROROVE_WORKFLOW_AUTHORITY_KEY');
|
|
317
|
+
const plan = validatePlan(verifyAuthority(value, configured));
|
|
318
|
+
lifetime(plan.acceptedAt, plan.expiresAt, MAX_PLAN_MS, Date.now());
|
|
319
|
+
return plan;
|
|
320
|
+
}
|
|
321
|
+
export function verifyOriginalWorkflowAuthority(value) {
|
|
322
|
+
return verifyAuthority(value, publicKey(process.env.ENTROROVE_WORKFLOW_PLAN_PUBLIC_KEY?.trim(), 'ENTROROVE_WORKFLOW_AUTHORITY_KEY'));
|
|
323
|
+
}
|
|
315
324
|
export async function runWorkflowLocalStep(rawInput) {
|
|
316
325
|
let input; try { input = structuredClone(rawInput); } catch { fail('ENTROROVE_WORKFLOW_JSON', 'workflow values must be finite JSON'); }
|
|
317
326
|
const authority = await prepare(input);
|