@emilia-protocol/gate 0.9.4 → 0.10.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/CHANGELOG.md +70 -0
- package/LICENSE +190 -0
- package/README.md +75 -16
- package/action-control-manifest.js +26 -0
- package/adapters/_kit.js +47 -5
- package/adapters/aws.js +15 -4
- package/adapters/github-demo.mjs +25 -22
- package/adapters/github.js +1 -1
- package/adapters/jira.js +1 -0
- package/adapters/linear.js +1 -0
- package/adapters/salesforce.js +1 -0
- package/adapters/stripe.js +1 -1
- package/adapters/supabase.js +26 -4
- package/adapters/vercel.js +33 -4
- package/aec-execution.js +16 -4
- package/breakglass.js +285 -51
- package/control-plane.js +341 -0
- package/coverage.js +722 -0
- package/custody-demo.mjs +13 -3
- package/demo.mjs +9 -3
- package/deploy/helm/README.md +16 -8
- package/deploy/helm/emilia-gate/Chart.yaml +3 -1
- package/deploy/helm/emilia-gate/templates/NOTES.txt +3 -2
- package/deploy/helm/emilia-gate/templates/deployment.yaml +55 -1
- package/deploy/helm/emilia-gate/values.yaml +18 -2
- package/deploy/helm/emilia-gate-service/Chart.yaml +11 -0
- package/deploy/helm/emilia-gate-service/README.md +81 -0
- package/deploy/helm/emilia-gate-service/templates/NOTES.txt +12 -0
- package/deploy/helm/emilia-gate-service/templates/_helpers.tpl +83 -0
- package/deploy/helm/emilia-gate-service/templates/deployment.yaml +153 -0
- package/deploy/helm/emilia-gate-service/templates/migration-job.yaml +73 -0
- package/deploy/helm/emilia-gate-service/templates/networkpolicy.yaml +113 -0
- package/deploy/helm/emilia-gate-service/templates/pdb.yaml +14 -0
- package/deploy/helm/emilia-gate-service/templates/service.yaml +20 -0
- package/deploy/helm/emilia-gate-service/templates/serviceaccount.yaml +8 -0
- package/deploy/helm/emilia-gate-service/tests/fixtures/001_gate.sql +26 -0
- package/deploy/helm/emilia-gate-service/tests/fixtures/002_runtime_access.sql +32 -0
- package/deploy/helm/emilia-gate-service/tests/fixtures/gate.config.mjs +29 -0
- package/deploy/helm/emilia-gate-service/tests/render-check.sh +145 -0
- package/deploy/helm/emilia-gate-service/values.schema.json +145 -0
- package/deploy/helm/emilia-gate-service/values.yaml +166 -0
- package/deploy/sql/001-runtime.sql +707 -0
- package/deploy/terraform/README.md +24 -14
- package/deploy/terraform/main.tf +59 -0
- package/deploy/terraform/service/README.md +81 -0
- package/deploy/terraform/service/main.tf +718 -0
- package/deploy/terraform/service/outputs.tf +19 -0
- package/deploy/terraform/service/tests/validate.sh +24 -0
- package/deploy/terraform/service/tests/validation.tftest.hcl +168 -0
- package/deploy/terraform/service/variables.tf +459 -0
- package/deploy/terraform/service/versions.tf +10 -0
- package/deploy/terraform/variables.tf +95 -2
- package/deployment-attestation.js +248 -0
- package/eg1-conformance.js +46 -12
- package/enterprise.js +6 -2
- package/ep-assure.mjs +3 -2
- package/evidence-postgres.js +357 -0
- package/evidence.js +10 -3
- package/execution-binding.js +141 -26
- package/index.js +556 -115
- package/key-registry.js +51 -19
- package/mcp.js +4 -2
- package/network-witness.js +500 -0
- package/package.json +25 -5
- package/reliance-kernel.js +5 -6
- package/reliance-packet.js +43 -10
- package/reports/assurance-package.js +45 -19
- package/reports/reperform.js +78 -18
- package/reports/underwriter.js +1 -1
- package/settlement.js +300 -0
- package/store-postgres.js +14 -0
- package/store.js +26 -5
- package/strict-json.js +86 -0
- package/witness-postgres.js +97 -0
package/adapters/github-demo.mjs
CHANGED
|
@@ -8,38 +8,41 @@
|
|
|
8
8
|
* with no credentials; swap in `new Octokit({ auth })` for the real thing.
|
|
9
9
|
* @license Apache-2.0
|
|
10
10
|
*/
|
|
11
|
-
import
|
|
12
|
-
import { createGate } from '../index.js';
|
|
11
|
+
import { createGate, createEg1Harness } from '../index.js';
|
|
13
12
|
import { createGithubManifest, guardGithubMutation } from './github.js';
|
|
14
13
|
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
: typeof v === 'object' ? `{${Object.keys(v).sort().map((k) => JSON.stringify(k) + ':' + canon(v[k])).join(',')}}`
|
|
18
|
-
: JSON.stringify(v));
|
|
19
|
-
const { publicKey, privateKey } = crypto.generateKeyPairSync('ed25519');
|
|
20
|
-
const ISSUER = publicKey.export({ type: 'spki', format: 'der' }).toString('base64url');
|
|
21
|
-
let n = 0;
|
|
22
|
-
const mint = (extra = {}) => {
|
|
23
|
-
const payload = {
|
|
24
|
-
receipt_id: `gh_${++n}`, subject: 'agent:repo-bot', issuer: 'ep:org:demo', created_at: new Date().toISOString(),
|
|
25
|
-
claim: { action_type: 'github.repo.delete', owner: 'acme', repo: 'prod', outcome: 'allow_with_signoff', approver: 'ep:approver:cto', ...extra },
|
|
26
|
-
};
|
|
27
|
-
return { '@version': 'EP-RECEIPT-v1', payload, signature: { algorithm: 'Ed25519', value: crypto.sign(null, Buffer.from(canon(payload), 'utf8'), privateKey).toString('base64url') } };
|
|
28
|
-
};
|
|
14
|
+
const action = { action_type: 'github.repo.delete', owner: 'acme', repo: 'prod' };
|
|
15
|
+
const harness = createEg1Harness({ action, idPrefix: 'gh' });
|
|
29
16
|
|
|
17
|
+
const executed = [];
|
|
30
18
|
const octokit = {
|
|
31
|
-
repos: {
|
|
19
|
+
repos: {
|
|
20
|
+
delete: async (p) => {
|
|
21
|
+
executed.push(`${p.owner}/${p.repo}`);
|
|
22
|
+
return { status: 204 };
|
|
23
|
+
},
|
|
24
|
+
},
|
|
32
25
|
};
|
|
33
|
-
const gate = createGate({
|
|
26
|
+
const gate = createGate({
|
|
27
|
+
manifest: createGithubManifest(),
|
|
28
|
+
trustedKeys: [harness.publicKey],
|
|
29
|
+
approverKeys: harness.approverKeys,
|
|
30
|
+
rpId: harness.rpId,
|
|
31
|
+
allowedOrigins: harness.allowedOrigins,
|
|
32
|
+
allowEphemeralStore: true,
|
|
33
|
+
});
|
|
34
34
|
const G = (s) => `\x1b[32m${s}\x1b[0m`; const R = (s) => `\x1b[31m${s}\x1b[0m`;
|
|
35
35
|
const line = (s) => console.log(s);
|
|
36
36
|
|
|
37
37
|
async function attempt(label, params, receipt) {
|
|
38
|
+
const before = executed.length;
|
|
38
39
|
try {
|
|
39
40
|
const { reliance } = await guardGithubMutation(gate, octokit, { op: 'repo.delete', params, receipt });
|
|
40
|
-
|
|
41
|
+
const mutation = executed.length === before + 1 ? ` GitHub API=${executed.at(-1)}` : ' GitHub API=NOT CALLED';
|
|
42
|
+
line(` ${label} -> ${G('ALLOWED')} reliance=${reliance.verdict.toUpperCase()}${mutation}`);
|
|
41
43
|
} catch (e) {
|
|
42
|
-
|
|
44
|
+
const mutation = executed.length === before ? ' GitHub API=NOT CALLED' : ' GitHub API=CALLED';
|
|
45
|
+
line(` ${label} -> ${R(`REFUSED ${e.status || ''}`)} (${e.gate?.reason || e.message})${mutation}`);
|
|
43
46
|
}
|
|
44
47
|
}
|
|
45
48
|
|
|
@@ -47,10 +50,10 @@ line('='.repeat(66));
|
|
|
47
50
|
line(' EMILIA Gate × GitHub — agent tries to delete the production repo');
|
|
48
51
|
line('='.repeat(66));
|
|
49
52
|
await attempt('1. delete acme/prod, no receipt ', { owner: 'acme', repo: 'prod' }, null);
|
|
50
|
-
const approval = mint();
|
|
53
|
+
const approval = harness.mint({ outcome: 'allow_with_signoff' });
|
|
51
54
|
await attempt('2. delete acme/prod, valid human signoff ', { owner: 'acme', repo: 'prod' }, approval);
|
|
52
55
|
await attempt('3. same receipt replayed ', { owner: 'acme', repo: 'prod' }, approval);
|
|
53
|
-
const reAppro = mint();
|
|
56
|
+
const reAppro = harness.mint({ outcome: 'allow_with_signoff' });
|
|
54
57
|
await attempt('4. approved acme/prod, but targets staging ', { owner: 'acme', repo: 'staging' }, reAppro);
|
|
55
58
|
line(' ' + '-'.repeat(62));
|
|
56
59
|
line(' No receipt, no mutation. The GitHub API is only reached on an allowed line.');
|
package/adapters/github.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* import { createGate } from '@emilia-protocol/gate';
|
|
13
13
|
* import { createGithubManifest, guardGithubMutation } from '@emilia-protocol/gate/adapters/github';
|
|
14
14
|
*
|
|
15
|
-
* const gate = createGate({ manifest: createGithubManifest(), trustedKeys: [ISSUER] });
|
|
15
|
+
* const gate = createGate({ manifest: createGithubManifest(), trustedKeys: [ISSUER], store: sharedConsumptionStore });
|
|
16
16
|
* const octokit = new Octokit({ auth });
|
|
17
17
|
* await guardGithubMutation(gate, octokit, {
|
|
18
18
|
* op: 'repo.delete', params: { owner, repo }, receipt, // throws if no valid receipt
|
package/adapters/jira.js
CHANGED
|
@@ -34,6 +34,7 @@ const OPS = {
|
|
|
34
34
|
'issue.bulk_delete': {
|
|
35
35
|
selector: { protocol: 'jira', tool: 'bulk_delete_issues' },
|
|
36
36
|
observed: (p) => ({ action_type: 'jira.issue.bulk_delete', project: p.project, jql_hash: hashCanonical(String(p.jql || '').trim()) }),
|
|
37
|
+
actuator: (p, observed) => ({ ...observed, jql: p.jql }),
|
|
37
38
|
perform: (c, p) => c.bulkDeleteIssues({ project: p.project, jql: p.jql }),
|
|
38
39
|
},
|
|
39
40
|
'project.delete': {
|
package/adapters/linear.js
CHANGED
|
@@ -39,6 +39,7 @@ const OPS = {
|
|
|
39
39
|
'issue.bulk_delete': {
|
|
40
40
|
selector: { protocol: 'linear', tool: 'bulk_delete_issues' },
|
|
41
41
|
observed: (p) => ({ action_type: 'linear.issue.bulk_delete', team: p.team, query_hash: hashCanonical(String(p.query || '').trim()) }),
|
|
42
|
+
actuator: (p, observed) => ({ ...observed, query: p.query }),
|
|
42
43
|
perform: (c, p) => c.bulkDeleteIssues({ team: p.team, query: p.query }),
|
|
43
44
|
},
|
|
44
45
|
'team.delete': {
|
package/adapters/salesforce.js
CHANGED
|
@@ -34,6 +34,7 @@ const OPS = {
|
|
|
34
34
|
'records.bulk_delete': {
|
|
35
35
|
selector: { protocol: 'salesforce', tool: 'bulk_delete' },
|
|
36
36
|
observed: (p) => ({ action_type: 'salesforce.records.bulk_delete', object: p.object, soql_hash: hashCanonical(String(p.soql || '').trim()) }),
|
|
37
|
+
actuator: (p, observed) => ({ ...observed, soql: p.soql }),
|
|
37
38
|
perform: (c, p) => c.bulkDelete({ object: p.object, soql: p.soql }),
|
|
38
39
|
},
|
|
39
40
|
'data.export': {
|
package/adapters/stripe.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* import { createGate } from '@emilia-protocol/gate';
|
|
13
13
|
* import { createStripeManifest, guardStripeMutation } from '@emilia-protocol/gate/adapters/stripe';
|
|
14
14
|
*
|
|
15
|
-
* const gate = createGate({ manifest: createStripeManifest(), trustedKeys: [ISSUER] });
|
|
15
|
+
* const gate = createGate({ manifest: createStripeManifest(), trustedKeys: [ISSUER], store: sharedConsumptionStore });
|
|
16
16
|
* await guardStripeMutation(gate, new Stripe(key), {
|
|
17
17
|
* op: 'payout.create', params: { amount: 40000, currency: 'usd', destination: 'acct_x' }, receipt,
|
|
18
18
|
* });
|
package/adapters/supabase.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* import { createGate } from '@emilia-protocol/gate';
|
|
13
13
|
* import { createSupabaseManifest, guardSupabaseMutation, isDestructiveSql } from '@emilia-protocol/gate/adapters/supabase';
|
|
14
14
|
*
|
|
15
|
-
* const gate = createGate({ manifest: createSupabaseManifest(), trustedKeys: [ISSUER] });
|
|
15
|
+
* const gate = createGate({ manifest: createSupabaseManifest(), trustedKeys: [ISSUER], store: sharedConsumptionStore });
|
|
16
16
|
* // client: anything with .query(sql) (node-postgres / a Supabase RPC wrapper).
|
|
17
17
|
* await guardSupabaseMutation(gate, client, {
|
|
18
18
|
* op: 'sql.destructive', params: { sql: 'DELETE FROM payments WHERE id=1', table: 'payments' }, receipt,
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import { createAdapter, manifestFromPack, hashCanonical } from './_kit.js';
|
|
22
22
|
|
|
23
|
+
export const RLS_DEFINITION_BINDING_VERSION = 'EP-SUPABASE-RLS-DEFINITION-v1';
|
|
24
|
+
|
|
23
25
|
const DESTRUCTIVE = /\b(delete|drop|truncate|alter\s+table)\b/i;
|
|
24
26
|
const UPDATE_NO_WHERE = /\bupdate\b(?:(?!\bwhere\b).)*$/is;
|
|
25
27
|
|
|
@@ -34,6 +36,14 @@ export function statementHash(sql) {
|
|
|
34
36
|
return hashCanonical(String(sql || '').replace(/\s+/g, ' ').trim().toLowerCase());
|
|
35
37
|
}
|
|
36
38
|
|
|
39
|
+
/** Digest the exact canonical RLS definition without placing it in evidence. */
|
|
40
|
+
export function rlsDefinitionDigest(definition) {
|
|
41
|
+
return hashCanonical({
|
|
42
|
+
version: RLS_DEFINITION_BINDING_VERSION,
|
|
43
|
+
definition,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
37
47
|
export const SUPABASE_ACTION_PACK = Object.freeze([
|
|
38
48
|
Object.freeze({
|
|
39
49
|
id: 'supabase.sql.destructive', label: 'Destructive SQL', action_type: 'supabase.sql.destructive',
|
|
@@ -54,7 +64,11 @@ export const SUPABASE_ACTION_PACK = Object.freeze([
|
|
|
54
64
|
risk: 'critical', receipt_required: true, assurance_class: 'quorum',
|
|
55
65
|
match: { protocol: 'supabase', tool: 'alter_policy' },
|
|
56
66
|
why: 'Changes who can read/write rows. Row-Level-Security changes deserve the two-person rule.',
|
|
57
|
-
execution_binding: {
|
|
67
|
+
execution_binding: {
|
|
68
|
+
required_fields: [
|
|
69
|
+
'action_type', 'table', 'policy', 'rls_definition_digest', 'rls_definition_version',
|
|
70
|
+
],
|
|
71
|
+
},
|
|
58
72
|
}),
|
|
59
73
|
]);
|
|
60
74
|
|
|
@@ -62,6 +76,7 @@ const OPS = {
|
|
|
62
76
|
'sql.destructive': {
|
|
63
77
|
selector: { protocol: 'supabase', tool: 'execute_sql' },
|
|
64
78
|
observed: (p) => ({ action_type: 'supabase.sql.destructive', statement_hash: statementHash(p.sql) }),
|
|
79
|
+
actuator: (p, observed) => ({ ...observed, sql: p.sql }),
|
|
65
80
|
perform: (client, p) => client.query(p.sql),
|
|
66
81
|
},
|
|
67
82
|
'data.export': {
|
|
@@ -71,7 +86,14 @@ const OPS = {
|
|
|
71
86
|
},
|
|
72
87
|
'rls.change': {
|
|
73
88
|
selector: { protocol: 'supabase', tool: 'alter_policy' },
|
|
74
|
-
observed: (p) => ({
|
|
89
|
+
observed: (p) => ({
|
|
90
|
+
action_type: 'supabase.rls.change',
|
|
91
|
+
table: p.table,
|
|
92
|
+
policy: p.policy,
|
|
93
|
+
rls_definition_digest: rlsDefinitionDigest(p.definition),
|
|
94
|
+
rls_definition_version: RLS_DEFINITION_BINDING_VERSION,
|
|
95
|
+
}),
|
|
96
|
+
actuator: (p, observed) => ({ ...observed, definition: p.definition }),
|
|
75
97
|
perform: (client, p) => client.alterPolicy(p.table, p.policy, p.definition),
|
|
76
98
|
},
|
|
77
99
|
};
|
|
@@ -96,5 +118,5 @@ export function guardSupabaseMutation(gate, client, args) {
|
|
|
96
118
|
|
|
97
119
|
export default {
|
|
98
120
|
SUPABASE_ACTION_PACK, SUPABASE_OPS, createSupabaseManifest, guardSupabaseMutation,
|
|
99
|
-
isDestructiveSql, statementHash,
|
|
121
|
+
isDestructiveSql, statementHash, rlsDefinitionDigest, RLS_DEFINITION_BINDING_VERSION,
|
|
100
122
|
};
|
package/adapters/vercel.js
CHANGED
|
@@ -5,7 +5,17 @@
|
|
|
5
5
|
* never reach Vercel without a receipt bound to THIS project. Client injected
|
|
6
6
|
* (the Vercel REST client or a thin wrapper).
|
|
7
7
|
*/
|
|
8
|
-
import { createAdapter, manifestFromPack } from './_kit.js';
|
|
8
|
+
import { createAdapter, manifestFromPack, hashCanonical } from './_kit.js';
|
|
9
|
+
|
|
10
|
+
export const SECRET_VALUE_BINDING_VERSION = 'EP-VERCEL-SECRET-VALUE-v1';
|
|
11
|
+
|
|
12
|
+
/** Digest an exact secret value for receipt binding; callers must never log it. */
|
|
13
|
+
export function secretValueDigest(value) {
|
|
14
|
+
return hashCanonical({
|
|
15
|
+
version: SECRET_VALUE_BINDING_VERSION,
|
|
16
|
+
value,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
9
19
|
|
|
10
20
|
export const VERCEL_ACTION_PACK = Object.freeze([
|
|
11
21
|
Object.freeze({
|
|
@@ -27,7 +37,11 @@ export const VERCEL_ACTION_PACK = Object.freeze([
|
|
|
27
37
|
risk: 'high', receipt_required: true, assurance_class: 'class_a',
|
|
28
38
|
match: { protocol: 'vercel', tool: 'upsert_env' },
|
|
29
39
|
why: 'Changes production secrets/config. Bind project+key+target.',
|
|
30
|
-
execution_binding: {
|
|
40
|
+
execution_binding: {
|
|
41
|
+
required_fields: [
|
|
42
|
+
'action_type', 'project', 'key', 'target', 'secret_value_digest', 'secret_value_version',
|
|
43
|
+
],
|
|
44
|
+
},
|
|
31
45
|
}),
|
|
32
46
|
]);
|
|
33
47
|
|
|
@@ -44,7 +58,15 @@ const OPS = {
|
|
|
44
58
|
},
|
|
45
59
|
'env.set': {
|
|
46
60
|
selector: { protocol: 'vercel', tool: 'upsert_env' },
|
|
47
|
-
observed: (p) => ({
|
|
61
|
+
observed: (p) => ({
|
|
62
|
+
action_type: 'vercel.env.set',
|
|
63
|
+
project: p.project,
|
|
64
|
+
key: p.key,
|
|
65
|
+
target: p.target,
|
|
66
|
+
secret_value_digest: secretValueDigest(p.value),
|
|
67
|
+
secret_value_version: SECRET_VALUE_BINDING_VERSION,
|
|
68
|
+
}),
|
|
69
|
+
actuator: (p, observed) => ({ ...observed, value: p.value }),
|
|
48
70
|
perform: (c, p) => c.upsertEnv({ project: p.project, key: p.key, value: p.value, target: p.target }),
|
|
49
71
|
},
|
|
50
72
|
};
|
|
@@ -53,4 +75,11 @@ const adapter = createAdapter({ system: 'vercel', ops: OPS });
|
|
|
53
75
|
export const VERCEL_OPS = adapter.OPS;
|
|
54
76
|
export function createVercelManifest(extra = []) { return manifestFromPack(VERCEL_ACTION_PACK, extra); }
|
|
55
77
|
export function guardVercelMutation(gate, client, args) { return adapter.guard(gate, client, args); }
|
|
56
|
-
export default {
|
|
78
|
+
export default {
|
|
79
|
+
VERCEL_ACTION_PACK,
|
|
80
|
+
VERCEL_OPS,
|
|
81
|
+
SECRET_VALUE_BINDING_VERSION,
|
|
82
|
+
secretValueDigest,
|
|
83
|
+
createVercelManifest,
|
|
84
|
+
guardVercelMutation,
|
|
85
|
+
};
|
package/aec-execution.js
CHANGED
|
@@ -9,9 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { createEvidenceLog, verifyEvidenceRecord } from './evidence.js';
|
|
11
11
|
import { MemoryConsumptionStore } from './store.js';
|
|
12
|
-
|
|
13
|
-
const { verifyAuthorizationChain } = await import('@emilia-protocol/verify/evidence-chain')
|
|
14
|
-
.catch(() => import('../verify/evidence-chain.js'));
|
|
12
|
+
import { verifyAuthorizationChain } from '@emilia-protocol/verify/evidence-chain';
|
|
15
13
|
|
|
16
14
|
const HUMAN_FLOORS = new Set(['class_a', 'quorum', 'class_a_or_quorum']);
|
|
17
15
|
const HEX_256 = /^[0-9a-f]{64}$/;
|
|
@@ -50,6 +48,19 @@ function humanFloorSatisfied(result, floor) {
|
|
|
50
48
|
return classA || quorum;
|
|
51
49
|
}
|
|
52
50
|
|
|
51
|
+
function evidenceSatisfied(result) {
|
|
52
|
+
try {
|
|
53
|
+
if (!result || typeof result !== 'object') return false;
|
|
54
|
+
// New verifiers expose the evidence-layer term. Older supported 3.x
|
|
55
|
+
// verifiers expose the equivalent `allow` result only. If `satisfied` is
|
|
56
|
+
// present, it is authoritative so a conflicting compatibility alias can
|
|
57
|
+
// never upgrade a refusal.
|
|
58
|
+
return own(result, 'satisfied') ? result.satisfied === true : result.allow === true;
|
|
59
|
+
} catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
53
64
|
function consumptionKey(result) {
|
|
54
65
|
// Consume the executor-owned action instance, not a presenter-selected
|
|
55
66
|
// component identifier. Otherwise an invalid decoy component or an alternate
|
|
@@ -216,7 +227,7 @@ export function createAECExecutionGate({
|
|
|
216
227
|
expectedAction: actionSnapshot,
|
|
217
228
|
verificationTime,
|
|
218
229
|
});
|
|
219
|
-
if (!result
|
|
230
|
+
if (!evidenceSatisfied(result)) return deny('aec_refused', result, { reasons: result.reasons });
|
|
220
231
|
if (!humanFloorSatisfied(result, humanFloor)) return deny('human_floor_unsatisfied', result);
|
|
221
232
|
|
|
222
233
|
const key = consumptionKey(result);
|
|
@@ -294,6 +305,7 @@ export const __aecExecutionSecurityInternals = Object.freeze({
|
|
|
294
305
|
validLogRecord,
|
|
295
306
|
validComponent,
|
|
296
307
|
humanFloorSatisfied,
|
|
308
|
+
evidenceSatisfied,
|
|
297
309
|
consumptionKey,
|
|
298
310
|
instant,
|
|
299
311
|
});
|