@totemsdk/industrial-action 0.1.0
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/LICENSE +21 -0
- package/README.md +114 -0
- package/dist/canonical.d.ts +3 -0
- package/dist/canonical.js +27 -0
- package/dist/commitment.d.ts +8 -0
- package/dist/commitment.js +23 -0
- package/dist/condition.d.ts +5 -0
- package/dist/condition.js +72 -0
- package/dist/definition.d.ts +14 -0
- package/dist/definition.js +82 -0
- package/dist/errors.d.ts +23 -0
- package/dist/errors.js +54 -0
- package/dist/executor.d.ts +2 -0
- package/dist/executor.js +56 -0
- package/dist/governance-bridge.d.ts +3 -0
- package/dist/governance-bridge.js +27 -0
- package/dist/ids.d.ts +13 -0
- package/dist/ids.js +33 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +49 -0
- package/dist/proposal.d.ts +6 -0
- package/dist/proposal.js +65 -0
- package/dist/receipt.d.ts +17 -0
- package/dist/receipt.js +24 -0
- package/dist/registry.d.ts +14 -0
- package/dist/registry.js +50 -0
- package/dist/types.d.ts +121 -0
- package/dist/types.js +2 -0
- package/package.json +72 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Totem SDK Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# @totemsdk/industrial-action
|
|
2
|
+
|
|
3
|
+
Deterministic industrial action lifecycle for Totem Edge — converts governed intent into context-aware, bounded, verifiably executed operations on field devices and protocols.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @totemsdk/industrial-action
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Design
|
|
12
|
+
|
|
13
|
+
This package sits between governance/authority (intent + mandate) and edge protocol transports (OPC-UA, Modbus, BACnet, etc.). It provides:
|
|
14
|
+
|
|
15
|
+
- **Action definition & schema** — declare parameter and context schemas per action kind
|
|
16
|
+
- **Proposal lifecycle** — propose, validate commitment, check expiry, verify executability
|
|
17
|
+
- **Commitment hashing** — domain-prefixed SHA3-256 canonical binding prevents parameter tampering
|
|
18
|
+
- **Guardrails / conditions** — evaluate pre-execution constraints (parameter range, context match, time window, custom)
|
|
19
|
+
- **Executor abstraction** — plugin executors per action kind, confirmed/failed/unknown status mapping
|
|
20
|
+
- **Governance bridge** — reserve-before-execute, commit-on-confirmed, abort-on-failure flow
|
|
21
|
+
- **Action receipts** — verifiable proof records with integrity checks
|
|
22
|
+
- **Registry** — register definitions and executors, lookup by kind
|
|
23
|
+
|
|
24
|
+
## Quick start
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { createProposal, createActionDefinition, executeAction, ActionRegistry, evaluateConditions } from '@totemsdk/industrial-action';
|
|
28
|
+
import type { ActionSchema, Condition } from '@totemsdk/industrial-action';
|
|
29
|
+
|
|
30
|
+
// 1. Define schema
|
|
31
|
+
const setTempSchema: ActionSchema = {
|
|
32
|
+
parameters: [
|
|
33
|
+
{ name: 'setpoint', type: 'number', required: true },
|
|
34
|
+
{ name: 'rampRate', type: 'number', required: false },
|
|
35
|
+
],
|
|
36
|
+
context: [
|
|
37
|
+
{ name: 'zoneId', type: 'string', required: true },
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// 2. Create definition
|
|
42
|
+
const setTempDef = createActionDefinition('temp.set', 'Set temperature setpoint', setTempSchema, {
|
|
43
|
+
async execute(params, context) {
|
|
44
|
+
// send to PLC / BACnet / OPC-UA
|
|
45
|
+
return { ok: true, data: { applied: true } };
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// 3. Register
|
|
50
|
+
const registry = new ActionRegistry();
|
|
51
|
+
registry.registerDefinition(setTempDef);
|
|
52
|
+
|
|
53
|
+
// 4. Create proposal
|
|
54
|
+
const proposal = createProposal({
|
|
55
|
+
kind: 'temp.set',
|
|
56
|
+
parameters: { setpoint: 22.5, rampRate: 1.0 },
|
|
57
|
+
context: { zoneId: 'HVAC-03' },
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// 5. Check conditions
|
|
61
|
+
const guards: Condition[] = [
|
|
62
|
+
{ type: 'parameter_range', field: 'setpoint', operator: 'gte', value: 10 },
|
|
63
|
+
{ type: 'parameter_range', field: 'setpoint', operator: 'lte', value: 35 },
|
|
64
|
+
];
|
|
65
|
+
const { passed, failed } = evaluateConditions(guards, proposal.parameters, proposal.context);
|
|
66
|
+
|
|
67
|
+
// 6. Execute
|
|
68
|
+
if (passed) {
|
|
69
|
+
const { execution, receipt } = await executeAction(proposal, { kind: 'temp.set', execute: setTempDef.handler.execute }, proposal.context);
|
|
70
|
+
console.log(execution.status); // 'confirmed' | 'failed' | 'unknown'
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## API
|
|
75
|
+
|
|
76
|
+
### Core types
|
|
77
|
+
- `ActionProposal` — proposed action with commitment hash, expiry, optional authority decision
|
|
78
|
+
- `ActionExecution` — execution record with status, result, error, receipt
|
|
79
|
+
- `ActionReceipt` — verifiable proof of action outcome
|
|
80
|
+
- `IndustrialActionDefinition` — action kind + schema + handler
|
|
81
|
+
- `ActionSchema` — parameter and context field schemas
|
|
82
|
+
- `Condition` — guardrail with field, operator, value, custom evaluator
|
|
83
|
+
|
|
84
|
+
### Key functions
|
|
85
|
+
| Function | Purpose |
|
|
86
|
+
|----------|---------|
|
|
87
|
+
| `createProposal` | Create action proposal with computed ID and commitment hash |
|
|
88
|
+
| `verifyCommitment` | Check proposal integrity against tampering |
|
|
89
|
+
| `createActionDefinition` | Define an action kind with schema |
|
|
90
|
+
| `validateParameters` / `validateContext` | Validate inputs against schema |
|
|
91
|
+
| `evaluateConditions` | Evaluate guardrails before execution |
|
|
92
|
+
| `executeAction` | Run executor, map result to confirmed/failed/unknown |
|
|
93
|
+
| `createReceipt` / `verifyReceiptIntegrity` | Create and verify action receipts |
|
|
94
|
+
| `createGovernanceBridge` | Wrap reserve/commit/abort for governance integration |
|
|
95
|
+
|
|
96
|
+
### Errors
|
|
97
|
+
- `IndustrialActionError` (base)
|
|
98
|
+
- `ActionValidationError` — parameter/context validation failures
|
|
99
|
+
- `ActionCommitmentError` — commitment hash mismatch
|
|
100
|
+
- `ActionConditionError` — guardrail violations
|
|
101
|
+
- `ActionExecutionError` — executor failures
|
|
102
|
+
- `ActionGovernanceError` — governance bridge failures
|
|
103
|
+
- `ActionDefinitionError` — registry/definition issues
|
|
104
|
+
|
|
105
|
+
## Dependencies
|
|
106
|
+
|
|
107
|
+
- `@totemsdk/core` — SHA3-256 hashing
|
|
108
|
+
- `@totemsdk/proof` — proof types
|
|
109
|
+
- `@totemsdk/authority` — mandate types
|
|
110
|
+
- `@totemsdk/edge` — `EdgeOperationResult`
|
|
111
|
+
|
|
112
|
+
## License
|
|
113
|
+
|
|
114
|
+
MIT
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toHex = toHex;
|
|
4
|
+
exports.canonicalJson = canonicalJson;
|
|
5
|
+
exports.hashCanonical = hashCanonical;
|
|
6
|
+
const core_1 = require("@totemsdk/core");
|
|
7
|
+
function toHex(bytes) {
|
|
8
|
+
return Array.from(bytes)
|
|
9
|
+
.map((b) => b.toString(16).padStart(2, '0'))
|
|
10
|
+
.join('');
|
|
11
|
+
}
|
|
12
|
+
function canonicalJson(value) {
|
|
13
|
+
if (value === null || typeof value !== 'object') {
|
|
14
|
+
return JSON.stringify(value);
|
|
15
|
+
}
|
|
16
|
+
if (Array.isArray(value)) {
|
|
17
|
+
return '[' + value.map(canonicalJson).join(',') + ']';
|
|
18
|
+
}
|
|
19
|
+
const obj = value;
|
|
20
|
+
const keys = Object.keys(obj).sort();
|
|
21
|
+
const pairs = keys.map((k) => `${JSON.stringify(k)}:${canonicalJson(obj[k])}`);
|
|
22
|
+
return '{' + pairs.join(',') + '}';
|
|
23
|
+
}
|
|
24
|
+
function hashCanonical(domain, value) {
|
|
25
|
+
const input = domain + canonicalJson(value);
|
|
26
|
+
return toHex((0, core_1.sha3_256)(new TextEncoder().encode(input)));
|
|
27
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ActionProposal } from './types.js';
|
|
2
|
+
export declare function createCommitment(proposal: {
|
|
3
|
+
kind: string;
|
|
4
|
+
parameters: Record<string, unknown>;
|
|
5
|
+
context: Record<string, unknown>;
|
|
6
|
+
}): string;
|
|
7
|
+
export declare function verifyCommitmentBinding(proposal: ActionProposal): boolean;
|
|
8
|
+
export declare function serializeCommitmentPayload(proposal: ActionProposal): string;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createCommitment = createCommitment;
|
|
4
|
+
exports.verifyCommitmentBinding = verifyCommitmentBinding;
|
|
5
|
+
exports.serializeCommitmentPayload = serializeCommitmentPayload;
|
|
6
|
+
const ids_js_1 = require("./ids.js");
|
|
7
|
+
const canonical_js_1 = require("./canonical.js");
|
|
8
|
+
function createCommitment(proposal) {
|
|
9
|
+
return (0, ids_js_1.computeCommitmentHash)(proposal);
|
|
10
|
+
}
|
|
11
|
+
function verifyCommitmentBinding(proposal) {
|
|
12
|
+
const expected = createCommitment(proposal);
|
|
13
|
+
return proposal.commitmentHash === expected;
|
|
14
|
+
}
|
|
15
|
+
function serializeCommitmentPayload(proposal) {
|
|
16
|
+
return (0, canonical_js_1.canonicalJson)({
|
|
17
|
+
id: proposal.id,
|
|
18
|
+
kind: proposal.kind,
|
|
19
|
+
commitmentHash: proposal.commitmentHash,
|
|
20
|
+
parameters: proposal.parameters,
|
|
21
|
+
context: proposal.context,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Condition, ConditionResult } from './types.js';
|
|
2
|
+
export declare function evaluateConditions(conditions: Condition[], params: Record<string, unknown>, context: Record<string, unknown>): ConditionResult;
|
|
3
|
+
export declare function createCondition(params: Pick<Condition, 'type' | 'field' | 'operator' | 'value'> & {
|
|
4
|
+
evaluate?: Condition['evaluate'];
|
|
5
|
+
}): Condition;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.evaluateConditions = evaluateConditions;
|
|
4
|
+
exports.createCondition = createCondition;
|
|
5
|
+
function evaluateConditions(conditions, params, context) {
|
|
6
|
+
const failed = [];
|
|
7
|
+
for (const condition of conditions) {
|
|
8
|
+
const reason = evaluateCondition(condition, params, context);
|
|
9
|
+
if (reason !== null) {
|
|
10
|
+
failed.push({ condition, reason });
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return { passed: failed.length === 0, failed };
|
|
14
|
+
}
|
|
15
|
+
function evaluateCondition(condition, params, context) {
|
|
16
|
+
if (condition.evaluate) {
|
|
17
|
+
return condition.evaluate(params, context);
|
|
18
|
+
}
|
|
19
|
+
const value = resolveField(condition.field, params, context);
|
|
20
|
+
if (value === undefined && condition.operator !== 'neq') {
|
|
21
|
+
return `field '${condition.field}' not found`;
|
|
22
|
+
}
|
|
23
|
+
return applyOperator(condition.operator ?? 'eq', value, condition.value, condition.field ?? '');
|
|
24
|
+
}
|
|
25
|
+
function resolveField(field, params, context) {
|
|
26
|
+
if (field === undefined)
|
|
27
|
+
return undefined;
|
|
28
|
+
if (field.startsWith('context.')) {
|
|
29
|
+
return context[field.slice(8)];
|
|
30
|
+
}
|
|
31
|
+
if (field.startsWith('params.')) {
|
|
32
|
+
return params[field.slice(7)];
|
|
33
|
+
}
|
|
34
|
+
return params[field] ?? context[field];
|
|
35
|
+
}
|
|
36
|
+
function applyOperator(operator, actual, expected, field) {
|
|
37
|
+
switch (operator) {
|
|
38
|
+
case 'eq':
|
|
39
|
+
return actual === expected ? null : `'${field}': expected ${String(expected)}, got ${String(actual)}`;
|
|
40
|
+
case 'neq':
|
|
41
|
+
return actual !== expected ? null : `'${field}': expected not equal to ${String(expected)}`;
|
|
42
|
+
case 'gt':
|
|
43
|
+
return typeof actual === 'number' && typeof expected === 'number' && actual > expected
|
|
44
|
+
? null
|
|
45
|
+
: `'${field}': expected > ${String(expected)}, got ${String(actual)}`;
|
|
46
|
+
case 'gte':
|
|
47
|
+
return typeof actual === 'number' && typeof expected === 'number' && actual >= expected
|
|
48
|
+
? null
|
|
49
|
+
: `'${field}': expected >= ${String(expected)}, got ${String(actual)}`;
|
|
50
|
+
case 'lt':
|
|
51
|
+
return typeof actual === 'number' && typeof expected === 'number' && actual < expected
|
|
52
|
+
? null
|
|
53
|
+
: `'${field}': expected < ${String(expected)}, got ${String(actual)}`;
|
|
54
|
+
case 'lte':
|
|
55
|
+
return typeof actual === 'number' && typeof expected === 'number' && actual <= expected
|
|
56
|
+
? null
|
|
57
|
+
: `'${field}': expected <= ${String(expected)}, got ${String(actual)}`;
|
|
58
|
+
case 'in':
|
|
59
|
+
return Array.isArray(expected) && expected.includes(actual)
|
|
60
|
+
? null
|
|
61
|
+
: `'${field}': ${String(actual)} not in [${String(expected)}]`;
|
|
62
|
+
case 'not_in':
|
|
63
|
+
return Array.isArray(expected) && !expected.includes(actual)
|
|
64
|
+
? null
|
|
65
|
+
: `'${field}': ${String(actual)} is in [${String(expected)}]`;
|
|
66
|
+
default:
|
|
67
|
+
return `unknown operator '${operator}'`;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function createCondition(params) {
|
|
71
|
+
return { ...params };
|
|
72
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { IndustrialActionDefinition, ActionSchema, ParameterType } from './types.js';
|
|
2
|
+
export declare function createActionDefinition<TParameters = unknown, TResult = unknown>(kind: string, description: string, schema: ActionSchema, handler: {
|
|
3
|
+
execute(params: TParameters, context: Record<string, unknown>): Promise<{
|
|
4
|
+
ok: boolean;
|
|
5
|
+
data?: TResult;
|
|
6
|
+
error?: string;
|
|
7
|
+
errorCode?: string;
|
|
8
|
+
}>;
|
|
9
|
+
}): IndustrialActionDefinition<TParameters, TResult>;
|
|
10
|
+
export declare function validateParameters(schema: ActionSchema, parameters: Record<string, unknown>): string[];
|
|
11
|
+
export declare function validateContext(schema: ActionSchema, context: Record<string, unknown>, now: number): string[];
|
|
12
|
+
export declare function checkType(expected: ParameterType, value: unknown): string | null;
|
|
13
|
+
export declare function assertValidParameters(schema: ActionSchema, parameters: Record<string, unknown>): void;
|
|
14
|
+
export declare function assertValidContext(schema: ActionSchema, context: Record<string, unknown>, now: number): void;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createActionDefinition = createActionDefinition;
|
|
4
|
+
exports.validateParameters = validateParameters;
|
|
5
|
+
exports.validateContext = validateContext;
|
|
6
|
+
exports.checkType = checkType;
|
|
7
|
+
exports.assertValidParameters = assertValidParameters;
|
|
8
|
+
exports.assertValidContext = assertValidContext;
|
|
9
|
+
const errors_js_1 = require("./errors.js");
|
|
10
|
+
function createActionDefinition(kind, description, schema, handler) {
|
|
11
|
+
return { kind, description, schema, handler };
|
|
12
|
+
}
|
|
13
|
+
function validateParameters(schema, parameters) {
|
|
14
|
+
const errors = [];
|
|
15
|
+
for (const field of schema.parameters) {
|
|
16
|
+
const value = parameters[field.name];
|
|
17
|
+
if (value === undefined || value === null) {
|
|
18
|
+
if (field.required) {
|
|
19
|
+
errors.push(`parameter '${field.name}' is required`);
|
|
20
|
+
}
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const typeErr = checkType(field.type, value);
|
|
24
|
+
if (typeErr) {
|
|
25
|
+
errors.push(`parameter '${field.name}': ${typeErr}`);
|
|
26
|
+
}
|
|
27
|
+
if (field.validation) {
|
|
28
|
+
const validationErr = field.validation(value);
|
|
29
|
+
if (validationErr) {
|
|
30
|
+
errors.push(`parameter '${field.name}': ${validationErr}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return errors;
|
|
35
|
+
}
|
|
36
|
+
function validateContext(schema, context, now) {
|
|
37
|
+
const errors = [];
|
|
38
|
+
for (const field of schema.context) {
|
|
39
|
+
const value = context[field.name];
|
|
40
|
+
if (value === undefined || value === null) {
|
|
41
|
+
if (field.required) {
|
|
42
|
+
errors.push(`context '${field.name}' is required`);
|
|
43
|
+
}
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const typeErr = checkType(field.type, value);
|
|
47
|
+
if (typeErr) {
|
|
48
|
+
errors.push(`context '${field.name}': ${typeErr}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return errors;
|
|
52
|
+
}
|
|
53
|
+
function checkType(expected, value) {
|
|
54
|
+
switch (expected) {
|
|
55
|
+
case 'string':
|
|
56
|
+
return typeof value === 'string' ? null : `expected string, got ${typeof value}`;
|
|
57
|
+
case 'number':
|
|
58
|
+
return typeof value === 'number' ? null : `expected number, got ${typeof value}`;
|
|
59
|
+
case 'boolean':
|
|
60
|
+
return typeof value === 'boolean' ? null : `expected boolean, got ${typeof value}`;
|
|
61
|
+
case 'object':
|
|
62
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
63
|
+
? null
|
|
64
|
+
: `expected object, got ${typeof value}`;
|
|
65
|
+
case 'array':
|
|
66
|
+
return Array.isArray(value) ? null : `expected array, got ${typeof value}`;
|
|
67
|
+
default:
|
|
68
|
+
return `unknown type '${expected}'`;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function assertValidParameters(schema, parameters) {
|
|
72
|
+
const errors = validateParameters(schema, parameters);
|
|
73
|
+
if (errors.length > 0) {
|
|
74
|
+
throw new errors_js_1.ActionValidationError('parameter validation failed', { errors });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function assertValidContext(schema, context, now) {
|
|
78
|
+
const errors = validateContext(schema, context, now);
|
|
79
|
+
if (errors.length > 0) {
|
|
80
|
+
throw new errors_js_1.ActionValidationError('context validation failed', { errors });
|
|
81
|
+
}
|
|
82
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare class IndustrialActionError extends Error {
|
|
2
|
+
readonly code: string;
|
|
3
|
+
constructor(code: string, message: string);
|
|
4
|
+
}
|
|
5
|
+
export declare class ActionDefinitionError extends IndustrialActionError {
|
|
6
|
+
constructor(message: string);
|
|
7
|
+
}
|
|
8
|
+
export declare class ActionValidationError extends IndustrialActionError {
|
|
9
|
+
readonly details: Record<string, unknown>;
|
|
10
|
+
constructor(message: string, details?: Record<string, unknown>);
|
|
11
|
+
}
|
|
12
|
+
export declare class ActionExecutionError extends IndustrialActionError {
|
|
13
|
+
constructor(message: string);
|
|
14
|
+
}
|
|
15
|
+
export declare class ActionConditionError extends IndustrialActionError {
|
|
16
|
+
constructor(message: string);
|
|
17
|
+
}
|
|
18
|
+
export declare class ActionGovernanceError extends IndustrialActionError {
|
|
19
|
+
constructor(message: string);
|
|
20
|
+
}
|
|
21
|
+
export declare class ActionCommitmentError extends IndustrialActionError {
|
|
22
|
+
constructor(message: string);
|
|
23
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ActionCommitmentError = exports.ActionGovernanceError = exports.ActionConditionError = exports.ActionExecutionError = exports.ActionValidationError = exports.ActionDefinitionError = exports.IndustrialActionError = void 0;
|
|
4
|
+
class IndustrialActionError extends Error {
|
|
5
|
+
constructor(code, message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = 'IndustrialActionError';
|
|
8
|
+
this.code = code;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
exports.IndustrialActionError = IndustrialActionError;
|
|
12
|
+
class ActionDefinitionError extends IndustrialActionError {
|
|
13
|
+
constructor(message) {
|
|
14
|
+
super('ACTION_DEFINITION_ERROR', message);
|
|
15
|
+
this.name = 'ActionDefinitionError';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
exports.ActionDefinitionError = ActionDefinitionError;
|
|
19
|
+
class ActionValidationError extends IndustrialActionError {
|
|
20
|
+
constructor(message, details) {
|
|
21
|
+
super('ACTION_VALIDATION_ERROR', message);
|
|
22
|
+
this.name = 'ActionValidationError';
|
|
23
|
+
this.details = details ?? {};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
exports.ActionValidationError = ActionValidationError;
|
|
27
|
+
class ActionExecutionError extends IndustrialActionError {
|
|
28
|
+
constructor(message) {
|
|
29
|
+
super('ACTION_EXECUTION_ERROR', message);
|
|
30
|
+
this.name = 'ActionExecutionError';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.ActionExecutionError = ActionExecutionError;
|
|
34
|
+
class ActionConditionError extends IndustrialActionError {
|
|
35
|
+
constructor(message) {
|
|
36
|
+
super('ACTION_CONDITION_ERROR', message);
|
|
37
|
+
this.name = 'ActionConditionError';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.ActionConditionError = ActionConditionError;
|
|
41
|
+
class ActionGovernanceError extends IndustrialActionError {
|
|
42
|
+
constructor(message) {
|
|
43
|
+
super('ACTION_GOVERNANCE_ERROR', message);
|
|
44
|
+
this.name = 'ActionGovernanceError';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
exports.ActionGovernanceError = ActionGovernanceError;
|
|
48
|
+
class ActionCommitmentError extends IndustrialActionError {
|
|
49
|
+
constructor(message) {
|
|
50
|
+
super('ACTION_COMMITMENT_ERROR', message);
|
|
51
|
+
this.name = 'ActionCommitmentError';
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
exports.ActionCommitmentError = ActionCommitmentError;
|
package/dist/executor.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.executeAction = executeAction;
|
|
4
|
+
const errors_js_1 = require("./errors.js");
|
|
5
|
+
const ids_js_1 = require("./ids.js");
|
|
6
|
+
const proposal_js_1 = require("./proposal.js");
|
|
7
|
+
const definition_js_1 = require("./definition.js");
|
|
8
|
+
async function executeAction(proposal, executor, context, now) {
|
|
9
|
+
const effectiveNow = now ?? Date.now();
|
|
10
|
+
(0, proposal_js_1.assertValidProposal)(proposal);
|
|
11
|
+
if (!(0, proposal_js_1.isProposalExecutable)(proposal, effectiveNow)) {
|
|
12
|
+
throw new errors_js_1.ActionValidationError('proposal is not executable');
|
|
13
|
+
}
|
|
14
|
+
(0, definition_js_1.assertValidParameters)(executor.kind === proposal.kind ? { parameters: [], context: [] } : { parameters: [], context: [] }, proposal.parameters);
|
|
15
|
+
(0, definition_js_1.assertValidContext)(executor.kind === proposal.kind ? { parameters: [], context: [] } : { parameters: [], context: [] }, context, effectiveNow);
|
|
16
|
+
const executionId = (0, ids_js_1.computeActionExecutionId)(proposal.id);
|
|
17
|
+
const execution = {
|
|
18
|
+
id: executionId,
|
|
19
|
+
proposalId: proposal.id,
|
|
20
|
+
status: 'executing',
|
|
21
|
+
startedAt: effectiveNow,
|
|
22
|
+
};
|
|
23
|
+
try {
|
|
24
|
+
const result = await executor.execute(proposal, proposal.parameters, context);
|
|
25
|
+
execution.status = result.ok ? 'confirmed' : 'failed';
|
|
26
|
+
execution.completedAt = Date.now();
|
|
27
|
+
if (result.ok) {
|
|
28
|
+
execution.result = result.data;
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
execution.error = { code: result.errorCode ?? 'EXECUTION_FAILED', message: result.error ?? 'execution returned failure' };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
execution.status = 'unknown';
|
|
36
|
+
execution.completedAt = Date.now();
|
|
37
|
+
execution.error = {
|
|
38
|
+
code: 'EXECUTION_ERROR',
|
|
39
|
+
message: err instanceof Error ? err.message : 'unknown execution error',
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const receipt = {
|
|
43
|
+
receiptId: (0, ids_js_1.computeReceiptId)(execution.id, proposal.id),
|
|
44
|
+
actionId: execution.id,
|
|
45
|
+
proposalId: proposal.id,
|
|
46
|
+
kind: proposal.kind,
|
|
47
|
+
status: execution.status === 'confirmed' ? 'confirmed' : execution.status === 'failed' ? 'failed' : 'unknown',
|
|
48
|
+
commitmentHash: proposal.commitmentHash,
|
|
49
|
+
parameters: proposal.parameters,
|
|
50
|
+
result: execution.result,
|
|
51
|
+
error: execution.error,
|
|
52
|
+
issuedAt: execution.completedAt ?? Date.now(),
|
|
53
|
+
};
|
|
54
|
+
execution.receipt = receipt;
|
|
55
|
+
return { execution, receipt };
|
|
56
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { GovernanceBridge, ActionProposal } from './types.js';
|
|
2
|
+
export declare function createGovernanceBridge(reserveFn: GovernanceBridge['reserve']): GovernanceBridge;
|
|
3
|
+
export declare function checkGovernanceConstraints(proposal: ActionProposal, now: number): string[];
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createGovernanceBridge = createGovernanceBridge;
|
|
4
|
+
exports.checkGovernanceConstraints = checkGovernanceConstraints;
|
|
5
|
+
function createGovernanceBridge(reserveFn) {
|
|
6
|
+
return {
|
|
7
|
+
async reserve(proposal, mandateProofId) {
|
|
8
|
+
return reserveFn(proposal, mandateProofId);
|
|
9
|
+
},
|
|
10
|
+
async commit(reservationId, execution) {
|
|
11
|
+
return { ok: true };
|
|
12
|
+
},
|
|
13
|
+
async abort(reservationId, error) {
|
|
14
|
+
return { ok: true };
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function checkGovernanceConstraints(proposal, now) {
|
|
19
|
+
const errors = [];
|
|
20
|
+
if (proposal.authorityDecision && !proposal.authorityDecision.allowed) {
|
|
21
|
+
errors.push(`authority decision denied: ${proposal.authorityDecision.reason ?? 'no reason'}`);
|
|
22
|
+
}
|
|
23
|
+
if (proposal.expiresAt !== undefined && now > proposal.expiresAt) {
|
|
24
|
+
errors.push('proposal has expired');
|
|
25
|
+
}
|
|
26
|
+
return errors;
|
|
27
|
+
}
|
package/dist/ids.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare function computeActionProposalId(params: {
|
|
2
|
+
kind: string;
|
|
3
|
+
parameters: Record<string, unknown>;
|
|
4
|
+
context: Record<string, unknown>;
|
|
5
|
+
proposedAt: number;
|
|
6
|
+
}): string;
|
|
7
|
+
export declare function computeActionExecutionId(proposalId: string): string;
|
|
8
|
+
export declare function computeCommitmentHash(proposal: {
|
|
9
|
+
kind: string;
|
|
10
|
+
parameters: Record<string, unknown>;
|
|
11
|
+
context: Record<string, unknown>;
|
|
12
|
+
}): string;
|
|
13
|
+
export declare function computeReceiptId(executionId: string, proposalId: string): string;
|
package/dist/ids.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.computeActionProposalId = computeActionProposalId;
|
|
4
|
+
exports.computeActionExecutionId = computeActionExecutionId;
|
|
5
|
+
exports.computeCommitmentHash = computeCommitmentHash;
|
|
6
|
+
exports.computeReceiptId = computeReceiptId;
|
|
7
|
+
const canonical_js_1 = require("./canonical.js");
|
|
8
|
+
function computeActionProposalId(params) {
|
|
9
|
+
return 'totem:ia:proposal:' + (0, canonical_js_1.hashCanonical)('TOTEM_INDUSTRIAL_ACTION_PROPOSAL_V1', {
|
|
10
|
+
kind: params.kind,
|
|
11
|
+
parameters: params.parameters,
|
|
12
|
+
context: params.context,
|
|
13
|
+
proposedAt: params.proposedAt,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
function computeActionExecutionId(proposalId) {
|
|
17
|
+
return 'totem:ia:exec:' + (0, canonical_js_1.hashCanonical)('TOTEM_INDUSTRIAL_ACTION_EXECUTION_V1', {
|
|
18
|
+
proposalId,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
function computeCommitmentHash(proposal) {
|
|
22
|
+
return (0, canonical_js_1.hashCanonical)('TOTEM_INDUSTRIAL_ACTION_COMMITMENT_V1', {
|
|
23
|
+
kind: proposal.kind,
|
|
24
|
+
parameters: proposal.parameters,
|
|
25
|
+
context: proposal.context,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function computeReceiptId(executionId, proposalId) {
|
|
29
|
+
return 'totem:ia:receipt:' + (0, canonical_js_1.hashCanonical)('TOTEM_INDUSTRIAL_ACTION_RECEIPT_V1', {
|
|
30
|
+
executionId,
|
|
31
|
+
proposalId,
|
|
32
|
+
});
|
|
33
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type { ActionStatus, ExecutionStatus, ParameterType, ParameterSchema, ContextField, ContextSchema, ActionSchema, ActionError, ActionProposal, ActionExecution, ActionReceipt, ActionHandler, IndustrialActionDefinition, Condition, ConditionResult, ActionExecutor, GovernanceBridge, ActionStorage, CreateProposalParams, ExecuteActionResult, } from './types.js';
|
|
2
|
+
export { toHex, canonicalJson, hashCanonical } from './canonical.js';
|
|
3
|
+
export { computeActionProposalId, computeActionExecutionId, computeCommitmentHash, computeReceiptId, } from './ids.js';
|
|
4
|
+
export { IndustrialActionError, ActionDefinitionError, ActionValidationError, ActionExecutionError, ActionConditionError, ActionGovernanceError, ActionCommitmentError, } from './errors.js';
|
|
5
|
+
export { createActionDefinition, validateParameters, validateContext, assertValidParameters, assertValidContext, } from './definition.js';
|
|
6
|
+
export { createProposal, verifyCommitment, assertValidProposal, isProposalExpired, isProposalExecutable, } from './proposal.js';
|
|
7
|
+
export { createCommitment, verifyCommitmentBinding, serializeCommitmentPayload, } from './commitment.js';
|
|
8
|
+
export { evaluateConditions, createCondition, } from './condition.js';
|
|
9
|
+
export { executeAction, } from './executor.js';
|
|
10
|
+
export { ActionRegistry } from './registry.js';
|
|
11
|
+
export { createGovernanceBridge, checkGovernanceConstraints, } from './governance-bridge.js';
|
|
12
|
+
export { createReceipt, verifyReceiptIntegrity, } from './receipt.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.verifyReceiptIntegrity = exports.createReceipt = exports.checkGovernanceConstraints = exports.createGovernanceBridge = exports.ActionRegistry = exports.executeAction = exports.createCondition = exports.evaluateConditions = exports.serializeCommitmentPayload = exports.verifyCommitmentBinding = exports.createCommitment = exports.isProposalExecutable = exports.isProposalExpired = exports.assertValidProposal = exports.verifyCommitment = exports.createProposal = exports.assertValidContext = exports.assertValidParameters = exports.validateContext = exports.validateParameters = exports.createActionDefinition = exports.ActionCommitmentError = exports.ActionGovernanceError = exports.ActionConditionError = exports.ActionExecutionError = exports.ActionValidationError = exports.ActionDefinitionError = exports.IndustrialActionError = exports.computeReceiptId = exports.computeCommitmentHash = exports.computeActionExecutionId = exports.computeActionProposalId = exports.hashCanonical = exports.canonicalJson = exports.toHex = void 0;
|
|
4
|
+
var canonical_js_1 = require("./canonical.js");
|
|
5
|
+
Object.defineProperty(exports, "toHex", { enumerable: true, get: function () { return canonical_js_1.toHex; } });
|
|
6
|
+
Object.defineProperty(exports, "canonicalJson", { enumerable: true, get: function () { return canonical_js_1.canonicalJson; } });
|
|
7
|
+
Object.defineProperty(exports, "hashCanonical", { enumerable: true, get: function () { return canonical_js_1.hashCanonical; } });
|
|
8
|
+
var ids_js_1 = require("./ids.js");
|
|
9
|
+
Object.defineProperty(exports, "computeActionProposalId", { enumerable: true, get: function () { return ids_js_1.computeActionProposalId; } });
|
|
10
|
+
Object.defineProperty(exports, "computeActionExecutionId", { enumerable: true, get: function () { return ids_js_1.computeActionExecutionId; } });
|
|
11
|
+
Object.defineProperty(exports, "computeCommitmentHash", { enumerable: true, get: function () { return ids_js_1.computeCommitmentHash; } });
|
|
12
|
+
Object.defineProperty(exports, "computeReceiptId", { enumerable: true, get: function () { return ids_js_1.computeReceiptId; } });
|
|
13
|
+
var errors_js_1 = require("./errors.js");
|
|
14
|
+
Object.defineProperty(exports, "IndustrialActionError", { enumerable: true, get: function () { return errors_js_1.IndustrialActionError; } });
|
|
15
|
+
Object.defineProperty(exports, "ActionDefinitionError", { enumerable: true, get: function () { return errors_js_1.ActionDefinitionError; } });
|
|
16
|
+
Object.defineProperty(exports, "ActionValidationError", { enumerable: true, get: function () { return errors_js_1.ActionValidationError; } });
|
|
17
|
+
Object.defineProperty(exports, "ActionExecutionError", { enumerable: true, get: function () { return errors_js_1.ActionExecutionError; } });
|
|
18
|
+
Object.defineProperty(exports, "ActionConditionError", { enumerable: true, get: function () { return errors_js_1.ActionConditionError; } });
|
|
19
|
+
Object.defineProperty(exports, "ActionGovernanceError", { enumerable: true, get: function () { return errors_js_1.ActionGovernanceError; } });
|
|
20
|
+
Object.defineProperty(exports, "ActionCommitmentError", { enumerable: true, get: function () { return errors_js_1.ActionCommitmentError; } });
|
|
21
|
+
var definition_js_1 = require("./definition.js");
|
|
22
|
+
Object.defineProperty(exports, "createActionDefinition", { enumerable: true, get: function () { return definition_js_1.createActionDefinition; } });
|
|
23
|
+
Object.defineProperty(exports, "validateParameters", { enumerable: true, get: function () { return definition_js_1.validateParameters; } });
|
|
24
|
+
Object.defineProperty(exports, "validateContext", { enumerable: true, get: function () { return definition_js_1.validateContext; } });
|
|
25
|
+
Object.defineProperty(exports, "assertValidParameters", { enumerable: true, get: function () { return definition_js_1.assertValidParameters; } });
|
|
26
|
+
Object.defineProperty(exports, "assertValidContext", { enumerable: true, get: function () { return definition_js_1.assertValidContext; } });
|
|
27
|
+
var proposal_js_1 = require("./proposal.js");
|
|
28
|
+
Object.defineProperty(exports, "createProposal", { enumerable: true, get: function () { return proposal_js_1.createProposal; } });
|
|
29
|
+
Object.defineProperty(exports, "verifyCommitment", { enumerable: true, get: function () { return proposal_js_1.verifyCommitment; } });
|
|
30
|
+
Object.defineProperty(exports, "assertValidProposal", { enumerable: true, get: function () { return proposal_js_1.assertValidProposal; } });
|
|
31
|
+
Object.defineProperty(exports, "isProposalExpired", { enumerable: true, get: function () { return proposal_js_1.isProposalExpired; } });
|
|
32
|
+
Object.defineProperty(exports, "isProposalExecutable", { enumerable: true, get: function () { return proposal_js_1.isProposalExecutable; } });
|
|
33
|
+
var commitment_js_1 = require("./commitment.js");
|
|
34
|
+
Object.defineProperty(exports, "createCommitment", { enumerable: true, get: function () { return commitment_js_1.createCommitment; } });
|
|
35
|
+
Object.defineProperty(exports, "verifyCommitmentBinding", { enumerable: true, get: function () { return commitment_js_1.verifyCommitmentBinding; } });
|
|
36
|
+
Object.defineProperty(exports, "serializeCommitmentPayload", { enumerable: true, get: function () { return commitment_js_1.serializeCommitmentPayload; } });
|
|
37
|
+
var condition_js_1 = require("./condition.js");
|
|
38
|
+
Object.defineProperty(exports, "evaluateConditions", { enumerable: true, get: function () { return condition_js_1.evaluateConditions; } });
|
|
39
|
+
Object.defineProperty(exports, "createCondition", { enumerable: true, get: function () { return condition_js_1.createCondition; } });
|
|
40
|
+
var executor_js_1 = require("./executor.js");
|
|
41
|
+
Object.defineProperty(exports, "executeAction", { enumerable: true, get: function () { return executor_js_1.executeAction; } });
|
|
42
|
+
var registry_js_1 = require("./registry.js");
|
|
43
|
+
Object.defineProperty(exports, "ActionRegistry", { enumerable: true, get: function () { return registry_js_1.ActionRegistry; } });
|
|
44
|
+
var governance_bridge_js_1 = require("./governance-bridge.js");
|
|
45
|
+
Object.defineProperty(exports, "createGovernanceBridge", { enumerable: true, get: function () { return governance_bridge_js_1.createGovernanceBridge; } });
|
|
46
|
+
Object.defineProperty(exports, "checkGovernanceConstraints", { enumerable: true, get: function () { return governance_bridge_js_1.checkGovernanceConstraints; } });
|
|
47
|
+
var receipt_js_1 = require("./receipt.js");
|
|
48
|
+
Object.defineProperty(exports, "createReceipt", { enumerable: true, get: function () { return receipt_js_1.createReceipt; } });
|
|
49
|
+
Object.defineProperty(exports, "verifyReceiptIntegrity", { enumerable: true, get: function () { return receipt_js_1.verifyReceiptIntegrity; } });
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { CreateProposalParams, ActionProposal } from './types.js';
|
|
2
|
+
export declare function createProposal(params: CreateProposalParams): ActionProposal;
|
|
3
|
+
export declare function verifyCommitment(proposal: ActionProposal): boolean;
|
|
4
|
+
export declare function assertValidProposal(proposal: ActionProposal): void;
|
|
5
|
+
export declare function isProposalExpired(proposal: ActionProposal, now: number): boolean;
|
|
6
|
+
export declare function isProposalExecutable(proposal: ActionProposal, now: number): boolean;
|
package/dist/proposal.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createProposal = createProposal;
|
|
4
|
+
exports.verifyCommitment = verifyCommitment;
|
|
5
|
+
exports.assertValidProposal = assertValidProposal;
|
|
6
|
+
exports.isProposalExpired = isProposalExpired;
|
|
7
|
+
exports.isProposalExecutable = isProposalExecutable;
|
|
8
|
+
const errors_js_1 = require("./errors.js");
|
|
9
|
+
const ids_js_1 = require("./ids.js");
|
|
10
|
+
function createProposal(params) {
|
|
11
|
+
const now = params.proposedAt ?? Date.now();
|
|
12
|
+
const commitmentHash = (0, ids_js_1.computeCommitmentHash)({
|
|
13
|
+
kind: params.kind,
|
|
14
|
+
parameters: params.parameters,
|
|
15
|
+
context: params.context,
|
|
16
|
+
});
|
|
17
|
+
const id = (0, ids_js_1.computeActionProposalId)({
|
|
18
|
+
kind: params.kind,
|
|
19
|
+
parameters: params.parameters,
|
|
20
|
+
context: params.context,
|
|
21
|
+
proposedAt: now,
|
|
22
|
+
});
|
|
23
|
+
const proposal = {
|
|
24
|
+
id,
|
|
25
|
+
kind: params.kind,
|
|
26
|
+
parameters: params.parameters,
|
|
27
|
+
context: params.context,
|
|
28
|
+
proposedAt: now,
|
|
29
|
+
commitmentHash,
|
|
30
|
+
};
|
|
31
|
+
if (params.expiresAt !== undefined)
|
|
32
|
+
proposal.expiresAt = params.expiresAt;
|
|
33
|
+
if (params.mandateProofId !== undefined)
|
|
34
|
+
proposal.mandateProofId = params.mandateProofId;
|
|
35
|
+
return proposal;
|
|
36
|
+
}
|
|
37
|
+
function verifyCommitment(proposal) {
|
|
38
|
+
const expected = (0, ids_js_1.computeCommitmentHash)({
|
|
39
|
+
kind: proposal.kind,
|
|
40
|
+
parameters: proposal.parameters,
|
|
41
|
+
context: proposal.context,
|
|
42
|
+
});
|
|
43
|
+
return proposal.commitmentHash === expected;
|
|
44
|
+
}
|
|
45
|
+
function assertValidProposal(proposal) {
|
|
46
|
+
if (!proposal.id)
|
|
47
|
+
throw new errors_js_1.ActionValidationError('proposal id is required');
|
|
48
|
+
if (!proposal.kind)
|
|
49
|
+
throw new errors_js_1.ActionValidationError('proposal kind is required');
|
|
50
|
+
if (!verifyCommitment(proposal)) {
|
|
51
|
+
throw new errors_js_1.ActionCommitmentError('commitment hash mismatch');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function isProposalExpired(proposal, now) {
|
|
55
|
+
if (proposal.expiresAt === undefined)
|
|
56
|
+
return false;
|
|
57
|
+
return now > proposal.expiresAt;
|
|
58
|
+
}
|
|
59
|
+
function isProposalExecutable(proposal, now) {
|
|
60
|
+
if (isProposalExpired(proposal, now))
|
|
61
|
+
return false;
|
|
62
|
+
if (proposal.authorityDecision && !proposal.authorityDecision.allowed)
|
|
63
|
+
return false;
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ActionReceipt } from './types.js';
|
|
2
|
+
export declare function createReceipt(params: {
|
|
3
|
+
actionId: string;
|
|
4
|
+
proposalId: string;
|
|
5
|
+
kind: string;
|
|
6
|
+
status: ActionReceipt['status'];
|
|
7
|
+
commitmentHash: string;
|
|
8
|
+
parameters: Record<string, unknown>;
|
|
9
|
+
result?: unknown;
|
|
10
|
+
error?: {
|
|
11
|
+
code: string;
|
|
12
|
+
message: string;
|
|
13
|
+
details?: Record<string, unknown>;
|
|
14
|
+
};
|
|
15
|
+
issuedAt?: number;
|
|
16
|
+
}): ActionReceipt;
|
|
17
|
+
export declare function verifyReceiptIntegrity(receipt: ActionReceipt): boolean;
|
package/dist/receipt.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createReceipt = createReceipt;
|
|
4
|
+
exports.verifyReceiptIntegrity = verifyReceiptIntegrity;
|
|
5
|
+
const ids_js_1 = require("./ids.js");
|
|
6
|
+
function createReceipt(params) {
|
|
7
|
+
const receiptId = (0, ids_js_1.computeReceiptId)(params.actionId, params.proposalId);
|
|
8
|
+
return {
|
|
9
|
+
receiptId,
|
|
10
|
+
actionId: params.actionId,
|
|
11
|
+
proposalId: params.proposalId,
|
|
12
|
+
kind: params.kind,
|
|
13
|
+
status: params.status,
|
|
14
|
+
commitmentHash: params.commitmentHash,
|
|
15
|
+
parameters: params.parameters,
|
|
16
|
+
result: params.result,
|
|
17
|
+
error: params.error,
|
|
18
|
+
issuedAt: params.issuedAt ?? Date.now(),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function verifyReceiptIntegrity(receipt) {
|
|
22
|
+
const expectedId = (0, ids_js_1.computeReceiptId)(receipt.actionId, receipt.proposalId);
|
|
23
|
+
return receipt.receiptId === expectedId;
|
|
24
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { IndustrialActionDefinition, ActionExecutor } from './types.js';
|
|
2
|
+
export declare class ActionRegistry {
|
|
3
|
+
private definitions;
|
|
4
|
+
private executors;
|
|
5
|
+
registerDefinition(definition: IndustrialActionDefinition): void;
|
|
6
|
+
registerExecutor(executor: ActionExecutor): void;
|
|
7
|
+
getDefinition(kind: string): IndustrialActionDefinition | undefined;
|
|
8
|
+
getExecutor(kind: string): ActionExecutor | undefined;
|
|
9
|
+
getDefinitionOrThrow(kind: string): IndustrialActionDefinition;
|
|
10
|
+
getExecutorOrThrow(kind: string): ActionExecutor;
|
|
11
|
+
hasDefinition(kind: string): boolean;
|
|
12
|
+
hasExecutor(kind: string): boolean;
|
|
13
|
+
listKinds(): string[];
|
|
14
|
+
}
|
package/dist/registry.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ActionRegistry = void 0;
|
|
4
|
+
const errors_js_1 = require("./errors.js");
|
|
5
|
+
class ActionRegistry {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.definitions = new Map();
|
|
8
|
+
this.executors = new Map();
|
|
9
|
+
}
|
|
10
|
+
registerDefinition(definition) {
|
|
11
|
+
if (this.definitions.has(definition.kind)) {
|
|
12
|
+
throw new errors_js_1.ActionDefinitionError(`definition '${definition.kind}' is already registered`);
|
|
13
|
+
}
|
|
14
|
+
this.definitions.set(definition.kind, definition);
|
|
15
|
+
}
|
|
16
|
+
registerExecutor(executor) {
|
|
17
|
+
if (this.executors.has(executor.kind)) {
|
|
18
|
+
throw new errors_js_1.ActionDefinitionError(`executor '${executor.kind}' is already registered`);
|
|
19
|
+
}
|
|
20
|
+
this.executors.set(executor.kind, executor);
|
|
21
|
+
}
|
|
22
|
+
getDefinition(kind) {
|
|
23
|
+
return this.definitions.get(kind);
|
|
24
|
+
}
|
|
25
|
+
getExecutor(kind) {
|
|
26
|
+
return this.executors.get(kind);
|
|
27
|
+
}
|
|
28
|
+
getDefinitionOrThrow(kind) {
|
|
29
|
+
const def = this.definitions.get(kind);
|
|
30
|
+
if (!def)
|
|
31
|
+
throw new errors_js_1.ActionDefinitionError(`no definition registered for '${kind}'`);
|
|
32
|
+
return def;
|
|
33
|
+
}
|
|
34
|
+
getExecutorOrThrow(kind) {
|
|
35
|
+
const exec = this.executors.get(kind);
|
|
36
|
+
if (!exec)
|
|
37
|
+
throw new errors_js_1.ActionDefinitionError(`no executor registered for '${kind}'`);
|
|
38
|
+
return exec;
|
|
39
|
+
}
|
|
40
|
+
hasDefinition(kind) {
|
|
41
|
+
return this.definitions.has(kind);
|
|
42
|
+
}
|
|
43
|
+
hasExecutor(kind) {
|
|
44
|
+
return this.executors.has(kind);
|
|
45
|
+
}
|
|
46
|
+
listKinds() {
|
|
47
|
+
return Array.from(this.definitions.keys());
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
exports.ActionRegistry = ActionRegistry;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { AuthorityDecision } from '@totemsdk/authority';
|
|
2
|
+
import type { EdgeOperationResult } from '@totemsdk/edge';
|
|
3
|
+
export type ActionStatus = 'proposed' | 'approved' | 'reserved' | 'executing' | 'confirmed' | 'failed' | 'unknown' | 'cancelled';
|
|
4
|
+
export type ExecutionStatus = 'pending' | 'executing' | 'confirmed' | 'failed' | 'unknown';
|
|
5
|
+
export type ParameterType = 'string' | 'number' | 'boolean' | 'object' | 'array';
|
|
6
|
+
export interface ParameterSchema {
|
|
7
|
+
name: string;
|
|
8
|
+
type: ParameterType;
|
|
9
|
+
required: boolean;
|
|
10
|
+
description?: string;
|
|
11
|
+
defaultValue?: unknown;
|
|
12
|
+
validation?: (value: unknown) => string | null;
|
|
13
|
+
}
|
|
14
|
+
export interface ContextField {
|
|
15
|
+
name: string;
|
|
16
|
+
value: unknown;
|
|
17
|
+
timestamp: number;
|
|
18
|
+
source?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface ContextSchema {
|
|
21
|
+
name: string;
|
|
22
|
+
type: 'string' | 'number' | 'boolean' | 'object';
|
|
23
|
+
required: boolean;
|
|
24
|
+
description?: string;
|
|
25
|
+
maxAgeMs?: number;
|
|
26
|
+
}
|
|
27
|
+
export interface ActionSchema {
|
|
28
|
+
parameters: ParameterSchema[];
|
|
29
|
+
context: ContextSchema[];
|
|
30
|
+
}
|
|
31
|
+
export interface ActionError {
|
|
32
|
+
code: string;
|
|
33
|
+
message: string;
|
|
34
|
+
details?: Record<string, unknown>;
|
|
35
|
+
}
|
|
36
|
+
export interface ActionProposal {
|
|
37
|
+
id: string;
|
|
38
|
+
kind: string;
|
|
39
|
+
parameters: Record<string, unknown>;
|
|
40
|
+
context: Record<string, unknown>;
|
|
41
|
+
proposedAt: number;
|
|
42
|
+
expiresAt?: number;
|
|
43
|
+
commitmentHash: string;
|
|
44
|
+
mandateProofId?: string;
|
|
45
|
+
authorityDecision?: AuthorityDecision;
|
|
46
|
+
}
|
|
47
|
+
export interface ActionExecution {
|
|
48
|
+
id: string;
|
|
49
|
+
proposalId: string;
|
|
50
|
+
status: ExecutionStatus;
|
|
51
|
+
result?: unknown;
|
|
52
|
+
error?: ActionError;
|
|
53
|
+
startedAt: number;
|
|
54
|
+
completedAt?: number;
|
|
55
|
+
receipt?: ActionReceipt;
|
|
56
|
+
}
|
|
57
|
+
export interface ActionReceipt {
|
|
58
|
+
receiptId: string;
|
|
59
|
+
actionId: string;
|
|
60
|
+
proposalId: string;
|
|
61
|
+
kind: string;
|
|
62
|
+
status: ActionStatus;
|
|
63
|
+
commitmentHash: string;
|
|
64
|
+
parameters: Record<string, unknown>;
|
|
65
|
+
result?: unknown;
|
|
66
|
+
error?: ActionError;
|
|
67
|
+
issuedAt: number;
|
|
68
|
+
}
|
|
69
|
+
export interface ActionHandler<TParameters = unknown, TResult = unknown> {
|
|
70
|
+
execute(params: TParameters, context: Record<string, unknown>): Promise<EdgeOperationResult<TResult>>;
|
|
71
|
+
}
|
|
72
|
+
export interface IndustrialActionDefinition<TParameters = unknown, TResult = unknown> {
|
|
73
|
+
kind: string;
|
|
74
|
+
description: string;
|
|
75
|
+
schema: ActionSchema;
|
|
76
|
+
handler: ActionHandler<TParameters, TResult>;
|
|
77
|
+
}
|
|
78
|
+
export interface Condition {
|
|
79
|
+
type: 'parameter_range' | 'context_match' | 'time_window' | 'custom';
|
|
80
|
+
field?: string;
|
|
81
|
+
operator?: 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'not_in';
|
|
82
|
+
value?: unknown;
|
|
83
|
+
evaluate?: (params: Record<string, unknown>, context: Record<string, unknown>) => string | null;
|
|
84
|
+
}
|
|
85
|
+
export interface ConditionResult {
|
|
86
|
+
passed: boolean;
|
|
87
|
+
failed: Array<{
|
|
88
|
+
condition: Condition;
|
|
89
|
+
reason: string;
|
|
90
|
+
}>;
|
|
91
|
+
}
|
|
92
|
+
export interface ActionExecutor<TParameters = unknown, TResult = unknown> {
|
|
93
|
+
kind: string;
|
|
94
|
+
execute(proposal: ActionProposal, params: TParameters, context: Record<string, unknown>): Promise<EdgeOperationResult<TResult>>;
|
|
95
|
+
}
|
|
96
|
+
export interface GovernanceBridge {
|
|
97
|
+
reserve(proposal: ActionProposal, mandateProofId: string): Promise<EdgeOperationResult<{
|
|
98
|
+
reservationId: string;
|
|
99
|
+
}>>;
|
|
100
|
+
commit(reservationId: string, execution: ActionExecution): Promise<EdgeOperationResult<void>>;
|
|
101
|
+
abort(reservationId: string, error: ActionError): Promise<EdgeOperationResult<void>>;
|
|
102
|
+
}
|
|
103
|
+
export interface ActionStorage {
|
|
104
|
+
saveProposal(proposal: ActionProposal): Promise<EdgeOperationResult<void>>;
|
|
105
|
+
getProposal(id: string): Promise<EdgeOperationResult<ActionProposal>>;
|
|
106
|
+
saveExecution(execution: ActionExecution): Promise<EdgeOperationResult<void>>;
|
|
107
|
+
getExecution(id: string): Promise<EdgeOperationResult<ActionExecution>>;
|
|
108
|
+
saveReceipt(receipt: ActionReceipt): Promise<EdgeOperationResult<void>>;
|
|
109
|
+
}
|
|
110
|
+
export interface CreateProposalParams {
|
|
111
|
+
kind: string;
|
|
112
|
+
parameters: Record<string, unknown>;
|
|
113
|
+
context: Record<string, unknown>;
|
|
114
|
+
proposedAt?: number;
|
|
115
|
+
expiresAt?: number;
|
|
116
|
+
mandateProofId?: string;
|
|
117
|
+
}
|
|
118
|
+
export interface ExecuteActionResult<TResult = unknown> {
|
|
119
|
+
execution: ActionExecution;
|
|
120
|
+
receipt?: ActionReceipt;
|
|
121
|
+
}
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@totemsdk/industrial-action",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Deterministic industrial action lifecycle for Totem Edge — converts governed intent into context-aware, bounded, verifiably executed operations on field devices and protocols",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"require": "./dist/index.js",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"prepare": "",
|
|
17
|
+
"clean": "rm -rf dist",
|
|
18
|
+
"test": "jest --passWithNoTests"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"README.md",
|
|
23
|
+
"LICENSE"
|
|
24
|
+
],
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@totemsdk/core": "^1.1.0",
|
|
27
|
+
"@totemsdk/proof": "^0.1.2",
|
|
28
|
+
"@totemsdk/authority": "^0.1.2",
|
|
29
|
+
"@totemsdk/edge": "^0.1.3"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/jest": "^30.0.0",
|
|
33
|
+
"@types/node": "^20.0.0",
|
|
34
|
+
"jest": "^30.4.2",
|
|
35
|
+
"jest-environment-node": "^30.0.0",
|
|
36
|
+
"jest-runtime": "^30.0.0",
|
|
37
|
+
"@swc/core": "^1.15.46",
|
|
38
|
+
"@swc/jest": "^0.2.39",
|
|
39
|
+
"ts-jest": "^29.0.0",
|
|
40
|
+
"typescript": "^7.0.2"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=18.0.0"
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"license": "MIT",
|
|
49
|
+
"author": "Totem SDK",
|
|
50
|
+
"homepage": "https://totem.ing",
|
|
51
|
+
"bugs": {
|
|
52
|
+
"url": "https://github.com/totem-sdk/totem-sdk/issues"
|
|
53
|
+
},
|
|
54
|
+
"repository": {
|
|
55
|
+
"type": "git",
|
|
56
|
+
"url": "git+https://github.com/totem-sdk/totem-sdk.git",
|
|
57
|
+
"directory": "packages/totem-sdk/packages/industrial-action"
|
|
58
|
+
},
|
|
59
|
+
"keywords": [
|
|
60
|
+
"totem",
|
|
61
|
+
"totemsdk",
|
|
62
|
+
"industrial",
|
|
63
|
+
"iot",
|
|
64
|
+
"edge",
|
|
65
|
+
"action",
|
|
66
|
+
"operational",
|
|
67
|
+
"opcua",
|
|
68
|
+
"modbus",
|
|
69
|
+
"bacnet",
|
|
70
|
+
"guardrails"
|
|
71
|
+
]
|
|
72
|
+
}
|