@entrorove/skill 0.1.22 → 0.1.24
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/AGREEMENT.md +6 -6
- package/CHANGELOG.md +10 -0
- package/SKILL.md +12 -4
- package/archguard-runtime-build.mjs +15 -0
- package/blueprint-criterion-runner.mjs +110 -0
- package/blueprint-criterion-wire.mjs +78 -0
- package/calctool-runtime-build.mjs +59 -0
- package/calctool-runtime-entry.mjs +92 -0
- package/cli.mjs +18 -83
- package/local-approved-plan.mjs +74 -15
- package/local-archguard-ast.mjs +371 -0
- package/local-archguard-checkpoint.mjs +123 -0
- package/local-archguard-components.mjs +177 -0
- package/local-archguard-contract.mjs +109 -0
- package/local-archguard-contracts.mjs +42 -0
- package/local-archguard-evaluate.mjs +120 -0
- package/local-archguard-language-ast.mjs +165 -0
- package/local-archguard-mutation-fence.mjs +28 -0
- package/local-archguard-operations.mjs +25 -0
- package/local-archguard-scope.mjs +64 -0
- package/local-archguard-signing.mjs +52 -0
- package/local-archguard-snapshot.mjs +126 -0
- package/local-archguard-state.mjs +200 -0
- package/local-archguard.mjs +74 -0
- package/local-budget-git-attributes.mjs +49 -0
- package/local-budget-git-loader.mjs +61 -0
- package/local-budget-git-native-attributes.mjs +81 -0
- package/local-budget-git-native-darwin.mjs +104 -0
- package/local-budget-git-native-linux.mjs +93 -0
- package/local-budget-git-native-reads.mjs +88 -0
- package/local-budget-git-native-request.mjs +53 -0
- package/local-budget-git-native-runtime.mjs +18 -0
- package/local-budget-git-native.c +313 -0
- package/local-budget-git-native.mjs +109 -0
- package/local-budget-git-outcome.mjs +33 -0
- package/local-budget-git-scratch-loader.mjs +158 -0
- package/local-budget-git-scratch.mjs +112 -0
- package/local-budget-git-system-attributes.mjs +44 -0
- package/local-budget-git.mjs +92 -0
- package/local-budget-grants.mjs +82 -0
- package/local-budget-handoff-completion.mjs +70 -0
- package/local-budget-handoff-contracts.mjs +53 -0
- package/local-budget-handoff.mjs +229 -0
- package/local-budget-lock.mjs +55 -0
- package/local-budget-reads.mjs +135 -0
- package/local-budget-renewal.mjs +86 -34
- package/local-budget-source-bindings.mjs +51 -0
- package/local-budget-store.mjs +131 -0
- package/local-budget-transition.mjs +46 -0
- package/local-budget.mjs +185 -0
- package/local-calctool-cli.mjs +22 -0
- package/local-calctool-contracts.mjs +42 -0
- package/local-calctool-runtime.mjs +10 -0
- package/local-calctool-storage.mjs +101 -0
- package/local-calctool.mjs +103 -0
- package/local-cli.mjs +49 -16
- package/local-contract-types.mjs +46 -0
- package/local-contracts.mjs +124 -0
- package/local-credential.mjs +101 -0
- package/local-decimal.mjs +85 -0
- package/local-evidence-verification.mjs +199 -0
- package/local-execution.mjs +115 -304
- package/local-final-gate16-bundle.mjs +21 -0
- package/local-final-gate16-checks.mjs +88 -0
- package/local-final-gate16-contract.mjs +15 -0
- package/local-final-gate16-plan.mjs +66 -0
- package/local-final-gate16-proof.mjs +75 -0
- package/local-final-gate16-release.mjs +17 -0
- package/local-final-gate16-state.mjs +70 -0
- package/local-final-gate16-worker.mjs +19 -0
- package/local-final-gate16.mjs +137 -0
- package/local-formula-ast.mjs +97 -0
- package/local-formula-engine.mjs +111 -0
- package/local-formula-tests.mjs +61 -0
- package/local-formula.mjs +25 -125
- package/local-integrity.mjs +22 -41
- package/local-invocations.mjs +107 -0
- package/local-ledger.mjs +2 -7
- package/local-merge-archguard-verification.d.mts +44 -0
- package/local-merge-archguard-verification.mjs +240 -0
- package/local-merge-archguard.mjs +138 -0
- package/local-merge-artifacts.mjs +86 -0
- package/local-merge-authority.mjs +89 -0
- package/local-merge-contracts.mjs +56 -0
- package/local-merge-controller.mjs +170 -0
- package/local-merge-head.mjs +56 -0
- package/local-merge-journal.mjs +77 -0
- package/local-merge-mutation.mjs +97 -0
- package/local-merge-rollback.mjs +126 -0
- package/local-merge-snapshot-rollback.mjs +26 -0
- package/local-merge-storage.mjs +103 -0
- package/local-merge-targets.mjs +47 -0
- package/local-merge-transaction.mjs +133 -0
- package/local-mutation-audit.mjs +34 -0
- package/local-mutation-authority.mjs +96 -0
- package/local-mutation-batch-receipt.mjs +84 -0
- package/local-mutation-batch.mjs +143 -0
- package/local-mutation-binding.mjs +86 -0
- package/local-mutation-files.mjs +245 -0
- package/local-mutation-pass-store.mjs +63 -0
- package/local-mutation-snapshot.mjs +200 -0
- package/local-mutation-wire.mjs +89 -0
- package/local-probe.mjs +133 -11
- package/local-scope-candidate.mjs +28 -0
- package/local-scope-context.mjs +28 -0
- package/local-scope-files.mjs +106 -0
- package/local-scope-git-attributes.mjs +200 -0
- package/local-scope-git-comparison.mjs +8 -0
- package/local-scope-git-history.mjs +72 -0
- package/local-scope-git-reads.mjs +66 -0
- package/local-scope-git.mjs +124 -0
- package/local-scope-repository.mjs +249 -0
- package/local-scope-signature.mjs +70 -0
- package/local-scope-snapshot.mjs +39 -0
- package/local-scope-state.mjs +204 -0
- package/local-scope-workspaces.mjs +83 -0
- package/local-swarm-authority.mjs +143 -0
- package/local-swarm-baseline.mjs +32 -0
- package/local-swarm-completion.mjs +102 -0
- package/local-swarm-contracts.mjs +47 -0
- package/local-swarm-mutation.mjs +142 -0
- package/local-swarm-priority.mjs +237 -0
- package/local-swarm-state.mjs +102 -0
- package/local-swarm-storage.mjs +135 -0
- package/local-swarm.mjs +179 -0
- package/local-task-handoff-completion-wire.mjs +71 -0
- package/local-task-handoff-contracts.mjs +66 -0
- package/local-task-handoff-fence.mjs +478 -0
- package/local-task-handoff-observation.mjs +38 -0
- package/local-task-handoff-overlay-wire.mjs +94 -0
- package/local-task-handoff-read-authority.mjs +236 -0
- package/local-task-handoff-storage.mjs +73 -0
- package/local-task-handoff-write-archive.mjs +80 -0
- package/local-task-handoff-write-constraints.mjs +66 -0
- package/local-task-handoff-write-receipts.mjs +99 -0
- package/local-task-handoff-write-wire.mjs +99 -0
- package/local-task-routing.mjs +202 -22
- package/manifest.json +1 -1
- package/package.json +153 -3
- package/release-state.json +153 -17
- package/release.mjs +238 -4
- package/releases.json +12 -0
- package/remote-client.mjs +127 -36
- package/remote-transport.mjs +80 -0
- package/review-client.mjs +38 -0
- package/runtime/archguard-runtime-v7.0.44.mjs +314 -0
- package/runtime/calctool-runtime-v7.0.43.mjs +1887 -0
- package/runtime/git-native-darwin-v1.mjs +5 -0
- package/runtime/git-native-linux-v1.mjs +5 -0
- package/task-message-cli.mjs +47 -0
- package/task-message-entry.mjs +159 -0
- package/workflow-local-client.mjs +35 -0
- package/workflow-local-contracts.mjs +58 -0
- package/workflow-local-request.mjs +53 -0
- package/workflow-local-runner.mjs +350 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { randomUUID, sign } from 'node:crypto';
|
|
2
|
+
import { canonical, sha256 } from './local-integrity.mjs';
|
|
3
|
+
import { scopeRead, scopeTarget } from './local-scope-files.mjs';
|
|
4
|
+
import { loadTaskHandoffSigner } from './local-task-handoff-observation.mjs';
|
|
5
|
+
import { receiverClaim } from './local-task-handoff-overlay-wire.mjs';
|
|
6
|
+
import { checkHandoffWriteConstraints } from './local-task-handoff-write-constraints.mjs';
|
|
7
|
+
import { verifyHandoffWriteArchive, verifyTaskHandoffWriteArchives } from './local-task-handoff-write-archive.mjs';
|
|
8
|
+
import { handoffWriteFail as fail, writeReceiptIdentity, verifyWriteReceipt, verifyWriteNativeResult, assertWriteReceiptFields } from './local-task-handoff-write-wire.mjs';
|
|
9
|
+
|
|
10
|
+
function sourceReadContext(ctx) { return Object.freeze({ home: ctx.home, repositoryRoot: ctx.root, flowId: ctx.flowId }); }
|
|
11
|
+
async function fileObservation(ctx, relative) {
|
|
12
|
+
const actual = await scopeTarget(ctx.root, relative, true);
|
|
13
|
+
return actual.exists ? { exists: true, sha256: (await scopeRead(ctx.root, relative, sourceReadContext(ctx))).sha256 }
|
|
14
|
+
: { exists: false, sha256: sha256('') };
|
|
15
|
+
}
|
|
16
|
+
async function verifyActualScope(ctx, expected) {
|
|
17
|
+
const observed = new Map();
|
|
18
|
+
for (const [relative, baseline] of expected) {
|
|
19
|
+
const actual = await fileObservation(ctx, relative);
|
|
20
|
+
if (canonical(actual) !== canonical(baseline)) fail('actual scoped file changed outside the complete trusted write history');
|
|
21
|
+
observed.set(relative, actual);
|
|
22
|
+
}
|
|
23
|
+
return observed;
|
|
24
|
+
}
|
|
25
|
+
function receiptCore(archive, ctx, pending, outcome, after, result, key) {
|
|
26
|
+
const now = Date.now(), architecture = result?.architectureCheckpoint ?? null;
|
|
27
|
+
return { schemaVersion: 'entrorove.task-handoff-write-checkpoint/1.0', receiptId: randomUUID(),
|
|
28
|
+
...writeReceiptIdentity(archive.claim, ctx), sequence: pending.sequence, previousReceiptDigest: pending.previousReceiptDigest,
|
|
29
|
+
blockId: pending.blockId, path: pending.path, beforeSha256: pending.beforeSha256, proposedAfterSha256: pending.proposedAfterSha256,
|
|
30
|
+
actualAfterSha256: after.sha256, beforeExists: pending.beforeExists, actualAfterExists: after.exists,
|
|
31
|
+
evaluationKind: architecture ? 'native-architecture' : outcome === 'blocked' ? 'scope-budget' : 'scope-only',
|
|
32
|
+
writeOutcome: outcome, checkpoint: architecture?.checkpoint ?? null, ledgerEntry: architecture?.ledgerEntry ?? null,
|
|
33
|
+
observedAt: pending.observedAt, keyId: key.config.keyId, nonce: pending.nonce, issuedAt: new Date(now).toISOString(),
|
|
34
|
+
expiresAt: new Date(now + key.config.receiptTtlMs).toISOString() };
|
|
35
|
+
}
|
|
36
|
+
async function saveCompleted(state, ctx, persist, archive, receipt) {
|
|
37
|
+
verifyWriteReceipt(receipt, archive.claim, ctx);
|
|
38
|
+
const updated = { ...archive, pending: null, receipts: [...archive.receipts, receipt],
|
|
39
|
+
interrupted: receipt.checkpoint?.drift.trend === 'red' };
|
|
40
|
+
await verifyTaskHandoffWriteArchives({ ...state, writeArchive: updated }, ctx);
|
|
41
|
+
await persist({ ...state, writeArchive: updated });
|
|
42
|
+
}
|
|
43
|
+
export async function runHandoffTrustedWrite({ input, context: ctx, state, persist, manifest, snapshotDirectory, performActualWrite }) {
|
|
44
|
+
if (!state?.writeArchive || state.writeArchive.pending || state.writeArchive.interrupted) fail('unknown pending or interrupted write prevents another physical write');
|
|
45
|
+
const archive = state.writeArchive, verified = await verifyHandoffWriteArchive(archive, ctx);
|
|
46
|
+
if (canonical(manifest) !== canonical(archive.manifest)) fail('physical write changed its accepted original snapshot');
|
|
47
|
+
const { pass } = receiverClaim(archive.claim, ctx), expected = verified.files.get(input.targetPath);
|
|
48
|
+
if (!expected) fail('physical write is outside its original exact snapshot paths');
|
|
49
|
+
const actual = (await verifyActualScope(ctx, verified.files)).get(input.targetPath);
|
|
50
|
+
const key = await loadTaskHandoffSigner(ctx, process.env.ENTROROVE_TASK_HANDOFF_WRITE_SIGNER_CONFIG, pass.runnerKeyId);
|
|
51
|
+
const content = typeof input.content === 'string' ? input.content : JSON.stringify(input.content);
|
|
52
|
+
if (typeof content !== 'string') fail('actual proposed content must be text or JSON');
|
|
53
|
+
const last = archive.receipts.at(-1), nonce = randomUUID();
|
|
54
|
+
const pending = { blockId: `${pass.passId}:${nonce}`, path: input.targetPath, beforeSha256: actual.sha256,
|
|
55
|
+
beforeExists: actual.exists, proposedAfterSha256: sha256(content), observedAt: new Date().toISOString(), nonce,
|
|
56
|
+
sequence: archive.receipts.length + 1, previousReceiptDigest: last ? sha256(canonical(last)) : null };
|
|
57
|
+
// Persist before the native check or physical rename; a crash retains an explicit unknown outcome.
|
|
58
|
+
await persist({ ...state, writeArchive: { ...archive, pending } });
|
|
59
|
+
let result, rejected;
|
|
60
|
+
try {
|
|
61
|
+
result = await checkHandoffWriteConstraints({ pass, manifest, root: ctx.root, snapshotDirectory,
|
|
62
|
+
targetPath: input.targetPath, content: input.content, blockId: pending.blockId, beforeSha256: actual.sha256, readContext: sourceReadContext(ctx),
|
|
63
|
+
history: pass.architectureAuthority ? [...pass.architectureAuthority.ledger.slice(pass.architectureAuthority.driftEpochStart), ...verified.entries] : [] });
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (error.code !== 'ENTROROVE_HANDOFF_SCOPE_BUDGET'
|
|
66
|
+
&& !(error.code === 'ENTROROVE_HANDOFF_WRITE_CONSTRAINT' && /cumulative changed lines.*inherited scope budget/i.test(error.message))) throw error;
|
|
67
|
+
rejected = error;
|
|
68
|
+
}
|
|
69
|
+
const native = result?.architectureCheckpoint;
|
|
70
|
+
if (native && (!native.checkpoint.writeAllowed || native.checkpoint.status !== 'passed' || native.checkpoint.rollbackRequired)) {
|
|
71
|
+
rejected = Object.assign(new Error(`Original locked architecture or AST rules blocked the physical write: ${JSON.stringify(native.checkpoint.findings)}`),
|
|
72
|
+
{ code: 'ENTROROVE_HANDOFF_WRITE_CONSTRAINT' });
|
|
73
|
+
}
|
|
74
|
+
const projected = rejected ? actual : { exists: true, sha256: pending.proposedAfterSha256 };
|
|
75
|
+
const planned = receiptCore(archive, ctx, pending, rejected ? 'blocked' : 'retained', projected, result, key);
|
|
76
|
+
assertWriteReceiptFields({ ...planned, signature: 'A'.repeat(86) });
|
|
77
|
+
await verifyWriteNativeResult(planned, pass.architectureAuthority, verified.entries);
|
|
78
|
+
const bounded = { ...archive, receipts: [...archive.receipts, { ...planned, signature: 'A'.repeat(86) }] };
|
|
79
|
+
if (Buffer.byteLength(canonical(bounded.receipts)) > 768_000
|
|
80
|
+
|| Buffer.byteLength(canonical([bounded, state.writeArchiveHistory])) > 8 * 1024 * 1024) fail('write receipt archive exceeds its explicit limit before physical rename');
|
|
81
|
+
receiverClaim(archive.claim, ctx); // Recheck the live signed grant immediately before any physical rename.
|
|
82
|
+
await verifyActualScope(ctx, verified.files);
|
|
83
|
+
let output;
|
|
84
|
+
if (!rejected) {
|
|
85
|
+
const original = manifest.files.find(file => file.path === input.targetPath);
|
|
86
|
+
output = await performActualWrite({ input, baseline: { ...original, exists: actual.exists,
|
|
87
|
+
sha256: actual.exists ? actual.sha256 : sha256('entrorove.missing-file/1.0') } });
|
|
88
|
+
if (output?.written !== true) fail('physical write did not return its actual retained outcome; reconcile the pending record');
|
|
89
|
+
}
|
|
90
|
+
const after = await fileObservation(ctx, input.targetPath);
|
|
91
|
+
if (rejected ? canonical(after) !== canonical(actual) : !after.exists || after.sha256 !== pending.proposedAfterSha256) fail('actual physical outcome is unknown; pending record is retained');
|
|
92
|
+
const finalScope = new Map(verified.files); finalScope.set(input.targetPath, projected);
|
|
93
|
+
await verifyActualScope(ctx, finalScope);
|
|
94
|
+
const core = receiptCore(archive, ctx, pending, rejected ? 'blocked' : 'retained', after, result, key);
|
|
95
|
+
const receipt = { ...core, signature: sign(null, Buffer.from(canonical(core)), key.privateKey).toString('base64url') };
|
|
96
|
+
await saveCompleted(state, ctx, persist, archive, receipt);
|
|
97
|
+
if (rejected) throw rejected;
|
|
98
|
+
return output;
|
|
99
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import Ajv from 'ajv';
|
|
2
|
+
import { createPublicKey, verify } from 'node:crypto';
|
|
3
|
+
import { canonical, sha256 } from './local-integrity.mjs';
|
|
4
|
+
import { integer, ledgerIdentifier, nullable, object, relativePath, sha256 as hash } from './local-contract-types.mjs';
|
|
5
|
+
import { archguardOperationSchemas, evaluateArchguardDrift } from './local-archguard-evaluate.mjs';
|
|
6
|
+
import { canonicalHandoffSignature, receiverClaim } from './local-task-handoff-overlay-wire.mjs';
|
|
7
|
+
|
|
8
|
+
const native = await archguardOperationSchemas();
|
|
9
|
+
const exact = fields => object(fields, Object.keys(fields)), text = { type: 'string' }, bool = { type: 'boolean' };
|
|
10
|
+
const count = integer(0), time = { type: 'string', pattern: '^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$' };
|
|
11
|
+
const array = items => ({ type: 'array', items });
|
|
12
|
+
const location = object({ line: count, column: count, start: count, end: count, syntaxKind: text,
|
|
13
|
+
parser: text, semanticSyntaxKind: text, dependency: text, namespace: text }, ['line', 'column']);
|
|
14
|
+
const evidence = object({ reason: text, dependency: text, declarations: array(text), locations: array(location),
|
|
15
|
+
allowedExtensions: array(text), actual: count, budget: count, matches: count, offsets: array(count),
|
|
16
|
+
diagnostics: array(object({ ...location.properties, code: { type: ['string', 'integer'] }, message: text }, ['line', 'column', 'code'])), extension: text }, []);
|
|
17
|
+
const nonempty = { type: 'string', minLength: 1 };
|
|
18
|
+
const finding = exact({ severity: { enum: ['P0', 'P1', 'P2'] }, ruleId: nonempty, entityRef: nonempty, message: nonempty, category: nonempty, blocking: bool, evidence });
|
|
19
|
+
const drift = exact({ trend: { enum: ['green', 'yellow', 'red'] }, totalBlocks: count, recentBlocks: count, blockedBlocks: count,
|
|
20
|
+
violationRate: { type: 'number', minimum: 0, maximum: 100 }, consecutiveBlocks: count, repeatedRuleIds: array(text), action: nonempty });
|
|
21
|
+
const checkpoint = exact({ status: { enum: ['passed', 'blocked'] }, writeAllowed: bool, rollbackRequired: bool,
|
|
22
|
+
findings: { ...array(finding), maxItems: 10000 }, metrics: exact({ path: relativePath,
|
|
23
|
+
inspectionScope: { enum: ['first-party', 'dependency-lockfile', 'third-party', 'generated'] }, complexityChecked: bool,
|
|
24
|
+
fileLines: count, maxFunctionLines: count, maxNestingDepth: count, animations: count, transitions: count }), drift, instruction: nonempty });
|
|
25
|
+
export const HANDOFF_WRITE_CHECKPOINT_SCHEMA = exact({
|
|
26
|
+
schemaVersion: { const: 'entrorove.task-handoff-write-checkpoint/1.0' }, receiptId: ledgerIdentifier,
|
|
27
|
+
handoffId: ledgerIdentifier, messageRequestId: ledgerIdentifier, ownerId: { type: 'string', pattern: '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$' },
|
|
28
|
+
previousOwnerTaskId: ledgerIdentifier, previousOwnerFlowId: ledgerIdentifier, previousOwnerTaskDigest: hash,
|
|
29
|
+
previousOwnerChainDigest: hash, previousOwnerEpoch: integer(1), receiverTaskId: ledgerIdentifier, receiverFlowId: ledgerIdentifier,
|
|
30
|
+
receiverTaskDigest: hash, receiverChainDigest: hash, receiverEpoch: integer(1), localRepositoryDigest: { type: 'string', pattern: '^[0-9a-f]{32}$' },
|
|
31
|
+
architectureAuthorityDigest: nullable(hash), scopeArtifactDigest: nullable(hash), contractDigest: nullable(hash), contractRevision: nullable(integer(1)),
|
|
32
|
+
initialLedgerDigest: nullable(hash), initialHeadDigest: nullable(hash), passDigest: hash, leaseDigest: hash, generation: { const: 1 }, snapshotDigest: hash,
|
|
33
|
+
sequence: integer(1), previousReceiptDigest: nullable(hash), blockId: ledgerIdentifier, path: relativePath,
|
|
34
|
+
beforeSha256: hash, proposedAfterSha256: hash, actualAfterSha256: hash, beforeExists: bool, actualAfterExists: bool,
|
|
35
|
+
evaluationKind: { enum: ['scope-only', 'scope-budget', 'native-architecture'] }, writeOutcome: { enum: ['retained', 'blocked'] },
|
|
36
|
+
checkpoint: nullable(checkpoint), ledgerEntry: nullable(native['drift-status'].input.properties.ledger.items), observedAt: time,
|
|
37
|
+
keyId: hash, nonce: ledgerIdentifier, issuedAt: time, expiresAt: time, signature: { type: 'string', pattern: '^[A-Za-z0-9_-]{86}$' } });
|
|
38
|
+
const ajv = new Ajv({ strict: false }), validate = ajv.compile(HANDOFF_WRITE_CHECKPOINT_SCHEMA);
|
|
39
|
+
const same = (left, right) => canonical(left) === canonical(right);
|
|
40
|
+
export function handoffWriteFail(message) { throw Object.assign(new Error(`Trusted handoff write archive: ${message}`), { code: 'ENTROROVE_HANDOFF_WRITE_ARCHIVE' }); }
|
|
41
|
+
export function writeReceiptIdentity(claim, ctx) {
|
|
42
|
+
const { pass, lease, challenge } = receiverClaim(claim, ctx, false), architecture = pass.architectureAuthority;
|
|
43
|
+
return { handoffId: pass.handoffId, messageRequestId: pass.messageRequestId, ownerId: pass.ownerId,
|
|
44
|
+
previousOwnerTaskId: pass.previousOwnerTaskId, previousOwnerFlowId: pass.previousOwnerFlowId,
|
|
45
|
+
previousOwnerTaskDigest: challenge.previousOwnerTaskDigest, previousOwnerChainDigest: challenge.previousOwnerChainDigest,
|
|
46
|
+
previousOwnerEpoch: pass.epoch, receiverTaskId: pass.receiverTaskId, receiverFlowId: pass.flowId,
|
|
47
|
+
receiverTaskDigest: pass.taskDigest, receiverChainDigest: pass.chainDigest, receiverEpoch: pass.receiverEpoch,
|
|
48
|
+
localRepositoryDigest: pass.localRepositoryDigest, architectureAuthorityDigest: architecture ? sha256(canonical(architecture)) : null,
|
|
49
|
+
scopeArtifactDigest: architecture?.scopeArtifactDigest ?? null, contractDigest: architecture?.contractDigest ?? null,
|
|
50
|
+
contractRevision: architecture?.revision ?? null, initialLedgerDigest: architecture?.ledgerDigest ?? null,
|
|
51
|
+
initialHeadDigest: architecture?.headDigest ?? null, passDigest: sha256(canonical(claim.pass)), leaseDigest: sha256(canonical(claim.lease)),
|
|
52
|
+
generation: lease.generation, snapshotDigest: pass.scopeSnapshotDigest };
|
|
53
|
+
}
|
|
54
|
+
function runnerKey(keyId) {
|
|
55
|
+
try {
|
|
56
|
+
const encoded = process.env.ENTROROVE_TASK_HANDOFF_RUNNER_PUBLIC_KEY, der = Buffer.from(encoded ?? '', 'base64');
|
|
57
|
+
const key = createPublicKey({ key: der, format: 'der', type: 'spki' });
|
|
58
|
+
if (key.asymmetricKeyType !== 'ed25519' || key.export({ format: 'der', type: 'spki' }).toString('base64') !== encoded
|
|
59
|
+
|| sha256(der) !== keyId) handoffWriteFail('trusted registered runner signing identity changed');
|
|
60
|
+
return key;
|
|
61
|
+
} catch { handoffWriteFail('trusted registered runner signing identity is unavailable'); }
|
|
62
|
+
}
|
|
63
|
+
export function verifyWriteReceipt(receipt, claim, ctx) {
|
|
64
|
+
assertWriteReceiptFields(receipt);
|
|
65
|
+
const identity = writeReceiptIdentity(claim, ctx), { signature, ...core } = receipt;
|
|
66
|
+
if (!Object.entries(identity).every(([key, value]) => same(value, receipt[key]))
|
|
67
|
+
|| receipt.keyId !== claim.pass.runnerKeyId
|
|
68
|
+
|| !verify(null, Buffer.from(canonical(core)), runnerKey(receipt.keyId), canonicalHandoffSignature(signature))) handoffWriteFail('receipt signature or frozen original identity changed');
|
|
69
|
+
const observed = Date.parse(receipt.observedAt), issued = Date.parse(receipt.issuedAt), expires = Date.parse(receipt.expiresAt);
|
|
70
|
+
if (![observed, issued, expires].every(Number.isFinite) || observed < Math.max(Date.parse(claim.pass.issuedAt), Date.parse(claim.lease.issuedAt))
|
|
71
|
+
|| observed > Math.min(Date.parse(claim.pass.expiresAt), Date.parse(claim.lease.expiresAt))
|
|
72
|
+
|| issued < observed || issued > Date.now() || expires <= issued || expires - issued > 300_000) handoffWriteFail('actual observation or signing time exceeded its original grant');
|
|
73
|
+
return receipt;
|
|
74
|
+
}
|
|
75
|
+
export function assertWriteReceiptFields(receipt) {
|
|
76
|
+
if (!validate(receipt)) handoffWriteFail(`receipt fields are invalid: ${ajv.errorsText(validate.errors)}`);
|
|
77
|
+
}
|
|
78
|
+
export async function verifyWriteNativeResult(receipt, authority, entries) {
|
|
79
|
+
const result = receipt.checkpoint, entry = receipt.ledgerEntry;
|
|
80
|
+
if (receipt.evaluationKind !== 'native-architecture') {
|
|
81
|
+
if (result !== null || entry !== null || (receipt.evaluationKind === 'scope-budget' && receipt.writeOutcome !== 'blocked')
|
|
82
|
+
|| (receipt.evaluationKind === 'scope-only' && authority !== null)) handoffWriteFail('scope-only evidence fabricated a native result');
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
if (!authority || !result || !entry) handoffWriteFail('actual native checkpoint and original authority are required');
|
|
86
|
+
const expected = { blockId: receipt.blockId, path: receipt.path, contractRevision: receipt.contractRevision,
|
|
87
|
+
beforeSha256: receipt.beforeSha256, afterSha256: receipt.proposedAfterSha256 };
|
|
88
|
+
const rules = [...new Set(result.findings.map(row => row.ruleId))].sort();
|
|
89
|
+
const blocking = [...new Set(result.findings.filter(row => row.blocking).map(row => row.ruleId))].sort();
|
|
90
|
+
if (!Object.entries(expected).every(([key, value]) => same(entry[key], value)) || !same(entry.ruleIds, rules)
|
|
91
|
+
|| !same(entry.blockingRuleIds, blocking) || result.metrics.path !== receipt.path || result.findings.some(row => row.entityRef !== receipt.path)
|
|
92
|
+
|| entry.status !== result.status || (blocking.length > 0) !== (entry.status === 'blocked')
|
|
93
|
+
|| entry.rollbackRequired !== (entry.status === 'blocked') || result.rollbackRequired !== entry.rollbackRequired
|
|
94
|
+
|| result.writeAllowed !== (entry.status === 'passed') || receipt.writeOutcome !== (entry.status === 'passed' ? 'retained' : 'blocked')) handoffWriteFail('native ledger and actual physical result are inconsistent');
|
|
95
|
+
const actualDrift = await evaluateArchguardDrift([...authority.ledger.slice(authority.driftEpochStart), ...entries, entry]);
|
|
96
|
+
if (!same(actualDrift, result.drift) || result.instruction !== (actualDrift.trend === 'red'
|
|
97
|
+
? 'rollback-block-and-interrupt-aimlock' : entry.rollbackRequired ? 'rollback-block-and-correct' : 'accept-block')) handoffWriteFail('actual ordered native drift or instruction changed');
|
|
98
|
+
return actualDrift;
|
|
99
|
+
}
|
package/local-task-routing.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { operationSchemasFor } from './local-contracts.mjs';
|
|
1
2
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
3
|
import { promises as fs } from 'node:fs';
|
|
3
4
|
import os from 'node:os';
|
|
@@ -6,31 +7,210 @@ import { withStateLock } from './local-state-lock.mjs';
|
|
|
6
7
|
|
|
7
8
|
export const TASK_ROUTING_SCHEMA = 'entrorove.task-routing/1.0';
|
|
8
9
|
const ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
|
|
9
|
-
function fail(code, message) {
|
|
10
|
+
function fail(code, message) {
|
|
11
|
+
const error = new Error(message);
|
|
12
|
+
error.code = code;
|
|
13
|
+
throw error;
|
|
14
|
+
}
|
|
10
15
|
function digest(value) { return createHash('sha256').update(value).digest('hex'); }
|
|
11
|
-
function canonical(value) {
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
function canonical(value) {
|
|
17
|
+
if (Array.isArray(value)) return `[${value.map(canonical).join(',')}]`;
|
|
18
|
+
if (value && typeof value === 'object') {
|
|
19
|
+
return `{${Object.keys(value).sort().map(key => `${JSON.stringify(key)}:${canonical(value[key])}`).join(',')}}`;
|
|
20
|
+
}
|
|
21
|
+
return JSON.stringify(value);
|
|
22
|
+
}
|
|
23
|
+
function homeRoot(home = os.homedir()) {
|
|
24
|
+
if (typeof home !== 'string' || !path.isAbsolute(home)) fail('ENTROROVE_HOME_INVALID', 'home must be absolute');
|
|
25
|
+
return path.join(home, '.entrorove');
|
|
26
|
+
}
|
|
27
|
+
async function repo(value) {
|
|
28
|
+
if (typeof value !== 'string' || !path.isAbsolute(value)) fail('ENTROROVE_ROOT_INVALID', 'repositoryRoot must be absolute');
|
|
29
|
+
const stat = await fs.lstat(value);
|
|
30
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) fail('ENTROROVE_ROOT_INVALID', 'repositoryRoot must be a real directory');
|
|
31
|
+
return fs.realpath(value);
|
|
32
|
+
}
|
|
14
33
|
function statePath(home, root) { return path.join(homeRoot(home), 'task-routing', digest(root).slice(0, 32), 'state.json'); }
|
|
15
|
-
async function privateDir(directory) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
async function
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
34
|
+
async function privateDir(directory) {
|
|
35
|
+
await fs.mkdir(directory, { recursive: true, mode: 0o700 });
|
|
36
|
+
const stat = await fs.lstat(directory);
|
|
37
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) fail('ENTROROVE_STATE_INVALID', 'state directory must be a real directory');
|
|
38
|
+
if (process.platform !== 'win32' && (stat.mode & 0o777) !== 0o700) await fs.chmod(directory, 0o700);
|
|
39
|
+
}
|
|
40
|
+
async function load(input, create = true) {
|
|
41
|
+
const root = await repo(input.repositoryRoot);
|
|
42
|
+
const file = statePath(input.home, root);
|
|
43
|
+
if (create) await privateDir(path.dirname(file));
|
|
44
|
+
try {
|
|
45
|
+
const stat = await fs.lstat(file);
|
|
46
|
+
if (!stat.isFile() || stat.isSymbolicLink()) fail('ENTROROVE_STATE_INVALID', 'task routing state must be a regular file');
|
|
47
|
+
const state = JSON.parse(await fs.readFile(file, 'utf8'));
|
|
48
|
+
if (state.schemaVersion !== TASK_ROUTING_SCHEMA || state.repositoryDigest !== digest(root).slice(0, 32)) {
|
|
49
|
+
fail('ENTROROVE_TASK_STATE_INVALID', 'task routing state identity mismatch');
|
|
50
|
+
}
|
|
51
|
+
return { root, file, state };
|
|
52
|
+
} catch (cause) {
|
|
53
|
+
if (cause.code !== 'ENOENT') throw cause;
|
|
54
|
+
if (!create) fail('ENTROROVE_TASK_UNKNOWN', 'Task routing state does not exist; describe the task first');
|
|
55
|
+
const state = { schemaVersion: TASK_ROUTING_SCHEMA, repositoryDigest: digest(root).slice(0, 32),
|
|
56
|
+
sequence: 0, tasks: [], messages: [], records: [] };
|
|
57
|
+
await save(file, state);
|
|
58
|
+
return { root, file, state };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async function save(file, state) {
|
|
62
|
+
await privateDir(path.dirname(file));
|
|
63
|
+
const temp = `${file}.${randomUUID()}.tmp`;
|
|
64
|
+
await fs.writeFile(temp, `${canonical(state)}\n`, { flag: 'wx', mode: 0o600 });
|
|
65
|
+
await fs.rename(temp, file);
|
|
66
|
+
if (process.platform !== 'win32') await fs.chmod(file, 0o600);
|
|
67
|
+
}
|
|
68
|
+
function requireId(value, label) {
|
|
69
|
+
if (typeof value !== 'string' || !ID.test(value)) fail('ENTROROVE_ID_INVALID', `${label} must be a safe identifier`);
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
72
|
+
function taskRecord(state, taskId) {
|
|
73
|
+
const task = state.tasks.find(item => item.taskId === taskId);
|
|
74
|
+
if (!task) fail('ENTROROVE_TASK_UNKNOWN', `unknown task: ${taskId}`);
|
|
75
|
+
return task;
|
|
76
|
+
}
|
|
77
|
+
async function record(authority, event, data, nextState = authority.state) {
|
|
78
|
+
const entry = { sequence: authority.state.sequence + 1, event, at: new Date().toISOString(), ...data };
|
|
79
|
+
const state = { ...nextState, sequence: entry.sequence,
|
|
80
|
+
records: [...authority.state.records, { ...entry, digest: digest(canonical(entry)) }] };
|
|
81
|
+
await save(authority.file, state);
|
|
82
|
+
authority.state = state;
|
|
83
|
+
return entry;
|
|
84
|
+
}
|
|
85
|
+
async function routingLock(input, action) {
|
|
86
|
+
const root = await repo(input.repositoryRoot);
|
|
87
|
+
return withStateLock(statePath(input.home, root), action);
|
|
88
|
+
}
|
|
89
|
+
function originalDescription(value) {
|
|
90
|
+
return { taskId: value.taskId, goal: value.goal, ownerId: value.ownerId ?? null, checkpoint: value.checkpoint ?? null };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function describeUnlocked(input) {
|
|
94
|
+
const taskId = requireId(input.taskId ?? `task-${randomUUID()}`, 'taskId');
|
|
95
|
+
if (typeof input.goal !== 'string' || !input.goal.trim()) fail('ENTROROVE_TASK_GOAL_REQUIRED', 'goal is required');
|
|
96
|
+
const authority = await load(input);
|
|
97
|
+
const original = originalDescription({ ...input, taskId });
|
|
98
|
+
const task = authority.state.tasks.find(item => item.taskId === taskId);
|
|
99
|
+
if (task) {
|
|
100
|
+
if (canonical(originalDescription(task)) !== canonical(original)
|
|
101
|
+
|| (input.createdAt !== undefined && input.createdAt !== task.createdAt)) {
|
|
102
|
+
return { schemaVersion: TASK_ROUTING_SCHEMA, status: 'blocked', code: 'ENTROROVE_TASK_ID_REUSED' };
|
|
103
|
+
}
|
|
104
|
+
return { schemaVersion: TASK_ROUTING_SCHEMA, status: 'reported', task };
|
|
105
|
+
}
|
|
106
|
+
const next = { ...original, createdAt: input.createdAt ?? new Date().toISOString(),
|
|
107
|
+
goalDigest: digest(canonical(original)), status: 'pending', messages: [], resumedAt: null };
|
|
108
|
+
const state = { ...authority.state, tasks: [...authority.state.tasks, next] };
|
|
109
|
+
await record(authority, 'task-describe', { taskId }, state);
|
|
110
|
+
return { schemaVersion: TASK_ROUTING_SCHEMA, status: 'reported', task: next };
|
|
111
|
+
}
|
|
112
|
+
function originalMessage(value) {
|
|
113
|
+
return { messageId: value.messageId, taskId: value.taskId, body: value.body ?? null,
|
|
114
|
+
destination: value.destination ?? null, source: value.source ?? null };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function routeUnlocked(input) {
|
|
118
|
+
const taskId = requireId(input.taskId, 'taskId');
|
|
119
|
+
const messageId = requireId(input.messageId ?? `message-${randomUUID()}`, 'messageId');
|
|
120
|
+
const authority = await load(input);
|
|
121
|
+
const task = taskRecord(authority.state, taskId);
|
|
122
|
+
const original = originalMessage({ ...input, messageId, taskId, destination: input.destination ?? task.ownerId });
|
|
123
|
+
const previous = authority.state.messages.find(item => item.messageId === messageId);
|
|
124
|
+
if (previous) {
|
|
125
|
+
if (canonical(originalMessage(previous)) !== canonical(original)) {
|
|
126
|
+
return { schemaVersion: TASK_ROUTING_SCHEMA, status: 'blocked', code: 'ENTROROVE_MESSAGE_ID_REUSED' };
|
|
127
|
+
}
|
|
128
|
+
return { schemaVersion: TASK_ROUTING_SCHEMA, status: 'reported', message: previous, duplicate: true };
|
|
129
|
+
}
|
|
130
|
+
if (['blocked', 'failed', 'completed'].includes(task.status)) {
|
|
131
|
+
return { schemaVersion: TASK_ROUTING_SCHEMA, status: 'blocked', code: 'ENTROROVE_TASK_TERMINAL' };
|
|
132
|
+
}
|
|
133
|
+
const message = { ...original, messageDigest: digest(canonical(original)), status: 'routed',
|
|
134
|
+
routedAt: new Date().toISOString(), delivery: 'pending' };
|
|
135
|
+
const state = { ...authority.state, messages: [...authority.state.messages, message],
|
|
136
|
+
tasks: authority.state.tasks.map(item => item.taskId === taskId && item.status === 'pending'
|
|
137
|
+
? { ...item, status: 'routed' } : item) };
|
|
138
|
+
await record(authority, 'message-route', { taskId, messageId, destination: message.destination }, state);
|
|
139
|
+
return { schemaVersion: TASK_ROUTING_SCHEMA, status: 'reported', message };
|
|
140
|
+
}
|
|
141
|
+
async function acceptUnlocked(input) {
|
|
142
|
+
const taskId = requireId(input.taskId, 'taskId');
|
|
143
|
+
const messageId = requireId(input.messageId, 'messageId');
|
|
144
|
+
const ownerId = requireId(input.ownerId, 'ownerId');
|
|
145
|
+
const authority = await load(input);
|
|
146
|
+
const message = authority.state.messages.find(item => item.taskId === taskId && item.messageId === messageId);
|
|
147
|
+
if (!message || !['routed', 'accepted'].includes(message.status)) {
|
|
148
|
+
return { schemaVersion: TASK_ROUTING_SCHEMA, status: 'blocked', code: 'ENTROROVE_MESSAGE_NOT_ROUTABLE' };
|
|
149
|
+
}
|
|
150
|
+
if (!message.destination || ownerId !== message.destination) {
|
|
151
|
+
return { schemaVersion: TASK_ROUTING_SCHEMA, status: 'blocked', code: 'ENTROROVE_MESSAGE_OWNER' };
|
|
152
|
+
}
|
|
153
|
+
if (message.status === 'accepted') {
|
|
154
|
+
if (!validReceipt(message.receipt, message, authority.state.repositoryDigest)) {
|
|
155
|
+
return { schemaVersion: TASK_ROUTING_SCHEMA, status: 'blocked', code: 'ENTROROVE_MESSAGE_RECEIPT_MISSING' };
|
|
156
|
+
}
|
|
157
|
+
return { schemaVersion: TASK_ROUTING_SCHEMA, status: 'reported', receipt: message.receipt,
|
|
158
|
+
message, duplicate: true };
|
|
159
|
+
}
|
|
160
|
+
const task = taskRecord(authority.state, taskId);
|
|
161
|
+
if (!['routed', 'accepted', 'active'].includes(task.status)) {
|
|
162
|
+
return { schemaVersion: TASK_ROUTING_SCHEMA, status: 'blocked', code: 'ENTROROVE_TASK_NOT_ROUTABLE' };
|
|
163
|
+
}
|
|
164
|
+
const accepted = { ...message, status: 'accepted', acceptedAt: new Date().toISOString(), acceptedBy: ownerId };
|
|
165
|
+
accepted.receipt = { schemaVersion: 'entrorove.message-receipt/1.0', source: 'local-communication',
|
|
166
|
+
receiptId: `receipt-${randomUUID()}`, taskId, messageId, repositoryDigest: authority.state.repositoryDigest,
|
|
167
|
+
messageDigest: digest(canonical(originalMessage(message))), acceptedBy: ownerId, acceptedAt: accepted.acceptedAt };
|
|
168
|
+
const state = { ...authority.state,
|
|
169
|
+
messages: authority.state.messages.map(item => item.messageId === messageId ? accepted : item),
|
|
170
|
+
tasks: authority.state.tasks.map(item => item.taskId === taskId && item.status === 'routed'
|
|
171
|
+
? { ...item, status: 'accepted' } : item) };
|
|
172
|
+
await record(authority, 'message-accept', { taskId, messageId, acceptedBy: accepted.acceptedBy,
|
|
173
|
+
receiptId: accepted.receipt.receiptId }, state);
|
|
174
|
+
return { schemaVersion: TASK_ROUTING_SCHEMA, status: 'reported', receipt: accepted.receipt, message: accepted };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function validReceipt(receipt, message, repositoryDigest) {
|
|
178
|
+
return receipt && receipt.schemaVersion === 'entrorove.message-receipt/1.0'
|
|
179
|
+
&& receipt.source === 'local-communication' && typeof receipt.receiptId === 'string' && ID.test(receipt.receiptId)
|
|
180
|
+
&& receipt.taskId === message.taskId && receipt.messageId === message.messageId
|
|
181
|
+
&& receipt.repositoryDigest === repositoryDigest && receipt.acceptedBy === message.acceptedBy
|
|
182
|
+
&& receipt.acceptedAt === message.acceptedAt && receipt.acceptedBy === message.destination
|
|
183
|
+
&& receipt.messageDigest === digest(canonical(originalMessage(message)));
|
|
184
|
+
}
|
|
185
|
+
function resumePackage(task, state) {
|
|
186
|
+
const checkpoint = task.checkpoint ?? null;
|
|
187
|
+
const requirements = task.requirements ?? checkpoint?.requirements ?? checkpoint?.originalRequirements ?? [];
|
|
188
|
+
const completed = new Set(checkpoint?.completedRequirementIds ?? []);
|
|
189
|
+
const remainingRequirements = task.remainingRequirements ?? checkpoint?.remainingRequirements
|
|
190
|
+
?? requirements.filter(requirement => !completed.has(requirement.id));
|
|
191
|
+
const pendingMessages = state.messages.filter(message => message.taskId === task.taskId
|
|
192
|
+
&& !['completed', 'failed', 'cancelled'].includes(message.status));
|
|
193
|
+
return { task, originalGoal: task.goal, checkpoint, requirements, remainingRequirements, pendingMessages,
|
|
194
|
+
inbox: task.inbox ?? checkpoint?.inbox ?? pendingMessages,
|
|
195
|
+
outbox: task.outbox ?? task.pendingOutbox ?? checkpoint?.outbox ?? checkpoint?.pendingOutbox ?? [],
|
|
196
|
+
continuationNotifications: task.continuationNotifications ?? checkpoint?.continuationNotifications ?? [],
|
|
197
|
+
nextAction: checkpoint?.nextAction ?? task.nextAction ?? null,
|
|
198
|
+
canContinue: ['accepted', 'active'].includes(task.status), engineeringAuthorization: 'cloud-required' };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function resumeUnlocked(input) {
|
|
202
|
+
const taskId = requireId(input.taskId, 'taskId');
|
|
203
|
+
const authority = await load(input, false);
|
|
204
|
+
const task = taskRecord(authority.state, taskId);
|
|
205
|
+
const recovered = resumePackage(task, authority.state);
|
|
206
|
+
if (!recovered.canContinue) {
|
|
207
|
+
return { schemaVersion: TASK_ROUTING_SCHEMA, status: 'blocked', code: 'ENTROROVE_TASK_NOT_ACCEPTED', taskId, ...recovered };
|
|
208
|
+
}
|
|
209
|
+
return { schemaVersion: TASK_ROUTING_SCHEMA, status: 'reported', ...recovered };
|
|
210
|
+
}
|
|
26
211
|
export async function taskDescribe(input) { return routingLock(input, () => describeUnlocked(input)); }
|
|
27
212
|
export async function messageRoute(input) { return routingLock(input, () => routeUnlocked(input)); }
|
|
28
213
|
export async function messageAccept(input) { return routingLock(input, () => acceptUnlocked(input)); }
|
|
29
|
-
export async function taskResume(input) { return
|
|
214
|
+
export async function taskResume(input) { return resumeUnlocked(input); }
|
|
30
215
|
export const LOCAL_TASK_ROUTING_OPERATIONS = Object.freeze({ 'task-describe': taskDescribe, 'message-route': messageRoute, 'message-accept': messageAccept, 'task-resume': taskResume });
|
|
31
|
-
export const LOCAL_TASK_ROUTING_SCHEMAS =
|
|
32
|
-
'task-describe': { type: 'object', additionalProperties: false, required: ['repositoryRoot', 'goal'] },
|
|
33
|
-
'message-route': { type: 'object', additionalProperties: false, required: ['repositoryRoot', 'taskId', 'messageId'] },
|
|
34
|
-
'message-accept': { type: 'object', additionalProperties: false, required: ['repositoryRoot', 'taskId', 'messageId'] },
|
|
35
|
-
'task-resume': { type: 'object', additionalProperties: false, required: ['repositoryRoot', 'taskId'] },
|
|
36
|
-
});
|
|
216
|
+
export const LOCAL_TASK_ROUTING_SCHEMAS = operationSchemasFor(LOCAL_TASK_ROUTING_OPERATIONS);
|
package/manifest.json
CHANGED