@decionis/agent-safe-pipeline 0.3.7 → 0.4.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/README.md +31 -0
- package/dist/Index.d.ts +12 -0
- package/dist/Index.d.ts.map +1 -1
- package/dist/Index.js +12 -0
- package/dist/Index.js.map +1 -1
- package/dist/decision/DecionisGate.d.ts +6 -1
- package/dist/decision/DecionisGate.d.ts.map +1 -1
- package/dist/decision/DecionisGate.js +61 -11
- package/dist/decision/DecionisGate.js.map +1 -1
- package/dist/decision/DecisionAuthority.d.ts +16 -0
- package/dist/decision/DecisionAuthority.d.ts.map +1 -1
- package/dist/decision/DecisionAuthority.js.map +1 -1
- package/dist/execution/AuthorizationVerifier.d.ts +33 -4
- package/dist/execution/AuthorizationVerifier.d.ts.map +1 -1
- package/dist/execution/AuthorizationVerifier.js +74 -10
- package/dist/execution/AuthorizationVerifier.js.map +1 -1
- package/dist/execution/EffectAdapter.d.ts +117 -0
- package/dist/execution/EffectAdapter.d.ts.map +1 -0
- package/dist/execution/EffectAdapter.js +22 -0
- package/dist/execution/EffectAdapter.js.map +1 -0
- package/dist/execution/ExecutionDeadline.d.ts +35 -0
- package/dist/execution/ExecutionDeadline.d.ts.map +1 -0
- package/dist/execution/ExecutionDeadline.js +52 -0
- package/dist/execution/ExecutionDeadline.js.map +1 -0
- package/dist/execution/ExecutionJournal.d.ts +148 -0
- package/dist/execution/ExecutionJournal.d.ts.map +1 -0
- package/dist/execution/ExecutionJournal.js +157 -0
- package/dist/execution/ExecutionJournal.js.map +1 -0
- package/dist/execution/ExecutionOutcome.d.ts +186 -0
- package/dist/execution/ExecutionOutcome.d.ts.map +1 -0
- package/dist/execution/ExecutionOutcome.js +2 -0
- package/dist/execution/ExecutionOutcome.js.map +1 -0
- package/dist/execution/ExecutionPosture.d.ts +50 -0
- package/dist/execution/ExecutionPosture.d.ts.map +1 -0
- package/dist/execution/ExecutionPosture.js +20 -0
- package/dist/execution/ExecutionPosture.js.map +1 -0
- package/dist/execution/ExecutorEvidence.d.ts +64 -0
- package/dist/execution/ExecutorEvidence.d.ts.map +1 -0
- package/dist/execution/ExecutorEvidence.js +14 -0
- package/dist/execution/ExecutorEvidence.js.map +1 -0
- package/dist/execution/GovernedExecutionRunner.d.ts +309 -0
- package/dist/execution/GovernedExecutionRunner.d.ts.map +1 -0
- package/dist/execution/GovernedExecutionRunner.js +1341 -0
- package/dist/execution/GovernedExecutionRunner.js.map +1 -0
- package/dist/execution/NativeExecutor.d.ts +220 -0
- package/dist/execution/NativeExecutor.d.ts.map +1 -0
- package/dist/execution/NativeExecutor.js +22 -0
- package/dist/execution/NativeExecutor.js.map +1 -0
- package/dist/execution/NativeExecutorSchema.d.ts +387 -0
- package/dist/execution/NativeExecutorSchema.d.ts.map +1 -0
- package/dist/execution/NativeExecutorSchema.js +261 -0
- package/dist/execution/NativeExecutorSchema.js.map +1 -0
- package/dist/execution/PostgresExecutionJournal.d.ts +54 -0
- package/dist/execution/PostgresExecutionJournal.d.ts.map +1 -0
- package/dist/execution/PostgresExecutionJournal.js +131 -0
- package/dist/execution/PostgresExecutionJournal.js.map +1 -0
- package/dist/execution/RegistryExecutor.d.ts +35 -0
- package/dist/execution/RegistryExecutor.d.ts.map +1 -0
- package/dist/execution/RegistryExecutor.js +84 -0
- package/dist/execution/RegistryExecutor.js.map +1 -0
- package/dist/execution/SafeExecutor.d.ts +22 -193
- package/dist/execution/SafeExecutor.d.ts.map +1 -1
- package/dist/execution/SafeExecutor.js +49 -521
- package/dist/execution/SafeExecutor.js.map +1 -1
- package/dist/http/AuthorityPaths.d.ts +51 -0
- package/dist/http/AuthorityPaths.d.ts.map +1 -0
- package/dist/http/AuthorityPaths.js +84 -0
- package/dist/http/AuthorityPaths.js.map +1 -0
- package/dist/shadow/ShadowPipeline.d.ts +3 -1
- package/dist/shadow/ShadowPipeline.d.ts.map +1 -1
- package/dist/shadow/ShadowPipeline.js +3 -3
- package/dist/shadow/ShadowPipeline.js.map +1 -1
- package/dist/testing/Index.d.ts +2 -1
- package/dist/testing/Index.d.ts.map +1 -1
- package/dist/testing/Index.js +2 -1
- package/dist/testing/Index.js.map +1 -1
- package/dist/testing/LocalAuthority.d.ts +35 -1
- package/dist/testing/LocalAuthority.d.ts.map +1 -1
- package/dist/testing/LocalAuthority.js +122 -2
- package/dist/testing/LocalAuthority.js.map +1 -1
- package/dist/testing/SignedSignals.d.ts +150 -0
- package/dist/testing/SignedSignals.d.ts.map +1 -0
- package/dist/testing/SignedSignals.js +291 -0
- package/dist/testing/SignedSignals.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type DispatchedAuthorization, type ExecutionJournal, type JournalRecord, type OpenAttempt } from "./ExecutionJournal.js";
|
|
2
|
+
import type { SqlClient } from "./PostgresReplayStore.js";
|
|
3
|
+
export interface PostgresExecutionJournalOptions {
|
|
4
|
+
readonly client: SqlClient;
|
|
5
|
+
/**
|
|
6
|
+
* Whose attempts these are: the organisation the executor runs for. Two
|
|
7
|
+
* deployments that share a database never read each other's records, and
|
|
8
|
+
* an idempotency key is single-use within one namespace.
|
|
9
|
+
*/
|
|
10
|
+
readonly namespace: string;
|
|
11
|
+
/** The journal table, optionally schema-qualified; `agentsafe_execution_journal` by default. */
|
|
12
|
+
readonly table?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare const DEFAULT_JOURNAL_TABLE = "agentsafe_execution_journal";
|
|
15
|
+
/**
|
|
16
|
+
* The execution journal every replica shares: one row per record, appended
|
|
17
|
+
* and never updated, read back in insertion order and folded with the same
|
|
18
|
+
* functions the file journal uses, so the two cannot disagree about which
|
|
19
|
+
* attempts are open or what an intent was dispatched under.
|
|
20
|
+
*
|
|
21
|
+
* It is the durable form of what `journaledActionHandler` does in the
|
|
22
|
+
* runtime: the `GRANT_CLAIMED` record is written after the grant is consumed
|
|
23
|
+
* and before the provider is touched, and a write that fails throws, so the
|
|
24
|
+
* runner reports a failure before dispatch rather than an effect nobody
|
|
25
|
+
* journaled. Single use of an idempotency key is the database's decision: a
|
|
26
|
+
* partial unique index on `(namespace, idempotency_key)` over claim records
|
|
27
|
+
* refuses the second claim, so two replicas racing on one key dispatch once,
|
|
28
|
+
* and the loser is told `IDEMPOTENCY_KEY_DISPATCHED`.
|
|
29
|
+
*
|
|
30
|
+
* The operator creates the table (`PostgresExecutionJournal.schema()`); the
|
|
31
|
+
* executor's role needs SELECT and INSERT on it and nothing else. Rows hold
|
|
32
|
+
* the captured intent, parameters included, so the table is as sensitive as
|
|
33
|
+
* the requests themselves.
|
|
34
|
+
*/
|
|
35
|
+
export declare class PostgresExecutionJournal implements ExecutionJournal {
|
|
36
|
+
private readonly client;
|
|
37
|
+
private readonly namespace;
|
|
38
|
+
private readonly table;
|
|
39
|
+
constructor(options: PostgresExecutionJournalOptions);
|
|
40
|
+
/** The DDL an operator runs once, before the first replica starts. */
|
|
41
|
+
static schema(table?: string): string;
|
|
42
|
+
append(record: JournalRecord): Promise<void>;
|
|
43
|
+
openAttempts(): Promise<readonly OpenAttempt[]>;
|
|
44
|
+
authorizationsOf(intentId: string): Promise<readonly DispatchedAuthorization[]>;
|
|
45
|
+
dispatchedUnder(idempotencyKey: string): Promise<readonly DispatchedAuthorization[]>;
|
|
46
|
+
/** Throws unless the table exists with the columns this journal uses. */
|
|
47
|
+
probe(): Promise<void>;
|
|
48
|
+
/** The client belongs to the caller, who opened it and closes it. */
|
|
49
|
+
close(): void;
|
|
50
|
+
/** The rows' records that parse, in order; a row that does not is skipped, as a bad file line is. */
|
|
51
|
+
private read;
|
|
52
|
+
private static json;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=PostgresExecutionJournal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PostgresExecutionJournal.d.ts","sourceRoot":"","sources":["../../src/execution/PostgresExecutionJournal.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,uBAAuB,EAC5B,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,WAAW,EACjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAE1D,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,gGAAgG;IAChG,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,eAAO,MAAM,qBAAqB,gCAAgC,CAAC;AAOnE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,wBAAyB,YAAW,gBAAgB;IAC/D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAY;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;gBAEZ,OAAO,EAAE,+BAA+B;IAW3D,sEAAsE;WACxD,MAAM,CAAC,KAAK,SAAwB,GAAG,MAAM;IAmB9C,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAoB5C,YAAY,IAAI,OAAO,CAAC,SAAS,WAAW,EAAE,CAAC;IAc/C,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,uBAAuB,EAAE,CAAC;IAQ/E,eAAe,CAC1B,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,SAAS,uBAAuB,EAAE,CAAC;IAU9C,yEAAyE;IAC5D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAOnC,qEAAqE;IAC9D,KAAK,IAAI,IAAI;IAEpB,qGAAqG;YACvF,IAAI;IAelB,OAAO,CAAC,MAAM,CAAC,IAAI;CAQpB"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { authorizationsFrom, dispatchedUnderFrom, IDEMPOTENCY_KEY_DISPATCHED, JournalError, JournalRecordSchema, openAttemptsFrom, } from "./ExecutionJournal.js";
|
|
2
|
+
export const DEFAULT_JOURNAL_TABLE = "agentsafe_execution_journal";
|
|
3
|
+
/** A lowercase SQL identifier, or `schema.table`: interpolated into SQL, so nothing else is. */
|
|
4
|
+
const TABLE = /^[a-z_][a-z0-9_]{0,62}(?:\.[a-z_][a-z0-9_]{0,62})?$/;
|
|
5
|
+
/** The SQLSTATE a unique index answers a second insert with. */
|
|
6
|
+
const UNIQUE_VIOLATION = "23505";
|
|
7
|
+
/**
|
|
8
|
+
* The execution journal every replica shares: one row per record, appended
|
|
9
|
+
* and never updated, read back in insertion order and folded with the same
|
|
10
|
+
* functions the file journal uses, so the two cannot disagree about which
|
|
11
|
+
* attempts are open or what an intent was dispatched under.
|
|
12
|
+
*
|
|
13
|
+
* It is the durable form of what `journaledActionHandler` does in the
|
|
14
|
+
* runtime: the `GRANT_CLAIMED` record is written after the grant is consumed
|
|
15
|
+
* and before the provider is touched, and a write that fails throws, so the
|
|
16
|
+
* runner reports a failure before dispatch rather than an effect nobody
|
|
17
|
+
* journaled. Single use of an idempotency key is the database's decision: a
|
|
18
|
+
* partial unique index on `(namespace, idempotency_key)` over claim records
|
|
19
|
+
* refuses the second claim, so two replicas racing on one key dispatch once,
|
|
20
|
+
* and the loser is told `IDEMPOTENCY_KEY_DISPATCHED`.
|
|
21
|
+
*
|
|
22
|
+
* The operator creates the table (`PostgresExecutionJournal.schema()`); the
|
|
23
|
+
* executor's role needs SELECT and INSERT on it and nothing else. Rows hold
|
|
24
|
+
* the captured intent, parameters included, so the table is as sensitive as
|
|
25
|
+
* the requests themselves.
|
|
26
|
+
*/
|
|
27
|
+
export class PostgresExecutionJournal {
|
|
28
|
+
client;
|
|
29
|
+
namespace;
|
|
30
|
+
table;
|
|
31
|
+
constructor(options) {
|
|
32
|
+
const table = options.table ?? DEFAULT_JOURNAL_TABLE;
|
|
33
|
+
if (!TABLE.test(table))
|
|
34
|
+
throw new Error("JOURNAL_TABLE_INVALID");
|
|
35
|
+
if (options.namespace.length === 0 || options.namespace.length > 200) {
|
|
36
|
+
throw new Error("JOURNAL_NAMESPACE_INVALID");
|
|
37
|
+
}
|
|
38
|
+
this.client = options.client;
|
|
39
|
+
this.namespace = options.namespace;
|
|
40
|
+
this.table = table;
|
|
41
|
+
}
|
|
42
|
+
/** The DDL an operator runs once, before the first replica starts. */
|
|
43
|
+
static schema(table = DEFAULT_JOURNAL_TABLE) {
|
|
44
|
+
if (!TABLE.test(table))
|
|
45
|
+
throw new Error("JOURNAL_TABLE_INVALID");
|
|
46
|
+
const index = table.replace(".", "_");
|
|
47
|
+
return [
|
|
48
|
+
`CREATE TABLE IF NOT EXISTS ${table} (`,
|
|
49
|
+
" seq bigserial PRIMARY KEY,",
|
|
50
|
+
" namespace text NOT NULL,",
|
|
51
|
+
" intent_id text NOT NULL,",
|
|
52
|
+
" record_kind text NOT NULL,",
|
|
53
|
+
" idempotency_key text,",
|
|
54
|
+
" record jsonb NOT NULL,",
|
|
55
|
+
" recorded_at timestamptz NOT NULL DEFAULT now()",
|
|
56
|
+
");",
|
|
57
|
+
`CREATE INDEX IF NOT EXISTS ${index}_intent ON ${table} (namespace, intent_id, seq);`,
|
|
58
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS ${index}_claim_once ON ${table} (namespace, idempotency_key) WHERE record_kind = 'GRANT_CLAIMED';`,
|
|
59
|
+
"",
|
|
60
|
+
].join("\n");
|
|
61
|
+
}
|
|
62
|
+
async append(record) {
|
|
63
|
+
const parsed = JournalRecordSchema.safeParse(record);
|
|
64
|
+
if (!parsed.success)
|
|
65
|
+
throw new JournalError("JOURNAL_RECORD_INVALID");
|
|
66
|
+
const value = parsed.data;
|
|
67
|
+
// Only a claim carries the key into its indexed column: the unique index
|
|
68
|
+
// is over claims, and an opening record with the same key is not a use.
|
|
69
|
+
const key = value.record === "GRANT_CLAIMED" ? value.idempotency_key : null;
|
|
70
|
+
try {
|
|
71
|
+
await this.client.query(`INSERT INTO ${this.table} (namespace, intent_id, record_kind, idempotency_key, record) VALUES ($1, $2, $3, $4, $5)`, [this.namespace, value.intent_id, value.record, key, JSON.stringify(value)]);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
if (error?.code === UNIQUE_VIOLATION) {
|
|
75
|
+
throw new JournalError(IDEMPOTENCY_KEY_DISPATCHED);
|
|
76
|
+
}
|
|
77
|
+
throw new JournalError("JOURNAL_WRITE_FAILED");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async openAttempts() {
|
|
81
|
+
// An attempt is open until its intent has a closing record. Intent ids
|
|
82
|
+
// are fresh per capture and opened once, so excluding every intent that
|
|
83
|
+
// has one is exactly the fold's answer, without reading closed history.
|
|
84
|
+
const rows = await this.read(`SELECT record FROM ${this.table} WHERE namespace = $1 AND intent_id IN (` +
|
|
85
|
+
`SELECT intent_id FROM ${this.table} WHERE namespace = $1 AND record_kind = 'ATTEMPT_OPENED' ` +
|
|
86
|
+
`EXCEPT SELECT intent_id FROM ${this.table} WHERE namespace = $1 AND record_kind IN ('ATTEMPT_CLOSED', 'RECONCILED')` +
|
|
87
|
+
`) ORDER BY seq`, [this.namespace]);
|
|
88
|
+
return openAttemptsFrom(rows);
|
|
89
|
+
}
|
|
90
|
+
async authorizationsOf(intentId) {
|
|
91
|
+
const rows = await this.read(`SELECT record FROM ${this.table} WHERE namespace = $1 AND intent_id = $2 ORDER BY seq`, [this.namespace, intentId]);
|
|
92
|
+
return authorizationsFrom(rows, intentId);
|
|
93
|
+
}
|
|
94
|
+
async dispatchedUnder(idempotencyKey) {
|
|
95
|
+
const rows = await this.read(`SELECT record FROM ${this.table} WHERE namespace = $1 AND intent_id IN (` +
|
|
96
|
+
`SELECT intent_id FROM ${this.table} WHERE namespace = $1 AND record_kind = 'GRANT_CLAIMED' AND idempotency_key = $2` +
|
|
97
|
+
`) ORDER BY seq`, [this.namespace, idempotencyKey]);
|
|
98
|
+
return dispatchedUnderFrom(rows, idempotencyKey);
|
|
99
|
+
}
|
|
100
|
+
/** Throws unless the table exists with the columns this journal uses. */
|
|
101
|
+
async probe() {
|
|
102
|
+
await this.client.query(`SELECT seq, namespace, intent_id, record_kind, idempotency_key, record FROM ${this.table} LIMIT 0`, []);
|
|
103
|
+
}
|
|
104
|
+
/** The client belongs to the caller, who opened it and closes it. */
|
|
105
|
+
close() { }
|
|
106
|
+
/** The rows' records that parse, in order; a row that does not is skipped, as a bad file line is. */
|
|
107
|
+
async read(text, values) {
|
|
108
|
+
const result = await this.client.query(text, values);
|
|
109
|
+
// Stryker disable next-line ArrayDeclaration: a seed element is not a record, and every fold ignores what is not one.
|
|
110
|
+
const records = [];
|
|
111
|
+
// Stryker disable next-line ArrayDeclaration: an element of the fallback is not a row with a record, and is skipped.
|
|
112
|
+
for (const row of result.rows ?? []) {
|
|
113
|
+
const raw = row.record;
|
|
114
|
+
const value = typeof raw === "string" ? PostgresExecutionJournal.json(raw) : raw;
|
|
115
|
+
const parsed = JournalRecordSchema.safeParse(value);
|
|
116
|
+
if (parsed.success)
|
|
117
|
+
records.push(parsed.data);
|
|
118
|
+
}
|
|
119
|
+
return records;
|
|
120
|
+
}
|
|
121
|
+
// Stryker disable all: an unparsable row is skipped either way, read as null or as undefined.
|
|
122
|
+
static json(text) {
|
|
123
|
+
try {
|
|
124
|
+
return JSON.parse(text);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=PostgresExecutionJournal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PostgresExecutionJournal.js","sourceRoot":"","sources":["../../src/execution/PostgresExecutionJournal.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,0BAA0B,EAC1B,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,GAKjB,MAAM,uBAAuB,CAAC;AAe/B,MAAM,CAAC,MAAM,qBAAqB,GAAG,6BAA6B,CAAC;AAEnE,gGAAgG;AAChG,MAAM,KAAK,GAAG,qDAAqD,CAAC;AACpE,gEAAgE;AAChE,MAAM,gBAAgB,GAAG,OAAO,CAAC;AAEjC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,OAAO,wBAAwB;IAClB,MAAM,CAAY;IAClB,SAAS,CAAS;IAClB,KAAK,CAAS;IAE/B,YAAmB,OAAwC;QACzD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,qBAAqB,CAAC;QACrD,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;QACjE,IAAI,OAAO,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,CAAC,SAAS,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;YACrE,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;QAC/C,CAAC;QACD,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC7B,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;QACnC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;IAED,sEAAsE;IAC/D,MAAM,CAAC,MAAM,CAAC,KAAK,GAAG,qBAAqB;QAChD,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;QACjE,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACtC,OAAO;YACL,8BAA8B,KAAK,IAAI;YACvC,8BAA8B;YAC9B,4BAA4B;YAC5B,4BAA4B;YAC5B,8BAA8B;YAC9B,yBAAyB;YACzB,0BAA0B;YAC1B,kDAAkD;YAClD,IAAI;YACJ,8BAA8B,KAAK,cAAc,KAAK,+BAA+B;YACrF,qCAAqC,KAAK,kBAAkB,KAAK,oEAAoE;YACrI,EAAE;SACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACf,CAAC;IAEM,KAAK,CAAC,MAAM,CAAC,MAAqB;QACvC,MAAM,MAAM,GAAG,mBAAmB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QACrD,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,MAAM,IAAI,YAAY,CAAC,wBAAwB,CAAC,CAAC;QACtE,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC;QAC1B,yEAAyE;QACzE,wEAAwE;QACxE,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,KAAK,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC;QAC5E,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CACrB,eAAe,IAAI,CAAC,KAAK,2FAA2F,EACpH,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAC5E,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAK,KAA4C,EAAE,IAAI,KAAK,gBAAgB,EAAE,CAAC;gBAC7E,MAAM,IAAI,YAAY,CAAC,0BAA0B,CAAC,CAAC;YACrD,CAAC;YACD,MAAM,IAAI,YAAY,CAAC,sBAAsB,CAAC,CAAC;QACjD,CAAC;IACH,CAAC;IAEM,KAAK,CAAC,YAAY;QACvB,uEAAuE;QACvE,wEAAwE;QACxE,wEAAwE;QACxE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAC1B,sBAAsB,IAAI,CAAC,KAAK,0CAA0C;YACxE,yBAAyB,IAAI,CAAC,KAAK,2DAA2D;YAC9F,gCAAgC,IAAI,CAAC,KAAK,2EAA2E;YACrH,gBAAgB,EAClB,CAAC,IAAI,CAAC,SAAS,CAAC,CACjB,CAAC;QACF,OAAO,gBAAgB,CAAC,IAAI,CAAC,CAAC;IAChC,CAAC;IAEM,KAAK,CAAC,gBAAgB,CAAC,QAAgB;QAC5C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAC1B,sBAAsB,IAAI,CAAC,KAAK,uDAAuD,EACvF,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAC3B,CAAC;QACF,OAAO,kBAAkB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC5C,CAAC;IAEM,KAAK,CAAC,eAAe,CAC1B,cAAsB;QAEtB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAC1B,sBAAsB,IAAI,CAAC,KAAK,0CAA0C;YACxE,yBAAyB,IAAI,CAAC,KAAK,kFAAkF;YACrH,gBAAgB,EAClB,CAAC,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,CACjC,CAAC;QACF,OAAO,mBAAmB,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;IACnD,CAAC;IAED,yEAAyE;IAClE,KAAK,CAAC,KAAK;QAChB,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CACrB,+EAA+E,IAAI,CAAC,KAAK,UAAU,EACnG,EAAE,CACH,CAAC;IACJ,CAAC;IAED,qEAAqE;IAC9D,KAAK,KAAU,CAAC;IAEvB,qGAAqG;IAC7F,KAAK,CAAC,IAAI,CAAC,IAAY,EAAE,MAA0B;QACzD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACrD,sHAAsH;QACtH,MAAM,OAAO,GAAoB,EAAE,CAAC;QACpC,qHAAqH;QACrH,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;YACpC,MAAM,GAAG,GAAI,GAAqC,CAAC,MAAM,CAAC;YAC1D,MAAM,KAAK,GAAY,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,wBAAwB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;YAC1F,MAAM,MAAM,GAAG,mBAAmB,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YACpD,IAAI,MAAM,CAAC,OAAO;gBAAE,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAChD,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,8FAA8F;IACtF,MAAM,CAAC,IAAI,CAAC,IAAY;QAC9B,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAY,CAAC;QACrC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;CAEF"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { CapturedIntent } from "../intent/ExecutionIntent.js";
|
|
2
|
+
import type { ActionExecutionAttempt, ActionRegistry } from "./ActionRegistry.js";
|
|
3
|
+
import type { ClaimedExecution, EffectObservation, ExecutionCapability, ExecutionEffect, NativeExecutor, PreparedAction, ReconcileContext, ReconcileResult } from "./NativeExecutor.js";
|
|
4
|
+
/**
|
|
5
|
+
* The dispatch-style executor an `ActionRegistry` already is: every
|
|
6
|
+
* registered handler, run by intent, behind the contract. It is how
|
|
7
|
+
* `SafeExecutor` and the runtime's registry reach `GovernedExecutionRunner`
|
|
8
|
+
* without a change to a single handler: the registry keeps its dispatch
|
|
9
|
+
* tracking, its refusals and its single-flight reconciliation, and this
|
|
10
|
+
* class only reads what it reports into the contract's terms.
|
|
11
|
+
*
|
|
12
|
+
* The action it executes is the captured intent itself, so what it prepares
|
|
13
|
+
* is exactly what was decided: there is no second description of the action
|
|
14
|
+
* that could drift from the one the authority bound.
|
|
15
|
+
*/
|
|
16
|
+
export declare class RegistryExecutor implements NativeExecutor<CapturedIntent, ActionExecutionAttempt, unknown> {
|
|
17
|
+
private readonly registry;
|
|
18
|
+
readonly platform: string;
|
|
19
|
+
readonly version = "registry/1";
|
|
20
|
+
readonly style: "dispatch";
|
|
21
|
+
readonly actionTypes: readonly string[];
|
|
22
|
+
constructor(registry: ActionRegistry, platform?: string);
|
|
23
|
+
/** The registry knows its handlers, not a connection; it states no capability to check. */
|
|
24
|
+
capabilities(): Promise<readonly ExecutionCapability[]>;
|
|
25
|
+
/**
|
|
26
|
+
* Refuses an unregistered action or parameters its handler's schema does
|
|
27
|
+
* not accept, by throwing exactly as the registry's own validation does.
|
|
28
|
+
*/
|
|
29
|
+
prepare(captured: CapturedIntent): Promise<PreparedAction>;
|
|
30
|
+
/** The registry tracks its own dispatch boundary; what it reports is carried through verbatim. */
|
|
31
|
+
execute(run: ClaimedExecution<CapturedIntent>): Promise<ExecutionEffect<ActionExecutionAttempt>>;
|
|
32
|
+
observe(effect: ExecutionEffect<ActionExecutionAttempt>): Promise<EffectObservation<unknown>>;
|
|
33
|
+
reconcile(context: ReconcileContext): Promise<ReconcileResult<unknown>>;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=RegistryExecutor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RegistryExecutor.d.ts","sourceRoot":"","sources":["../../src/execution/RegistryExecutor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAEnE,OAAO,KAAK,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAClF,OAAO,KAAK,EACV,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,EACf,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,eAAe,EAChB,MAAM,qBAAqB,CAAC;AAG7B;;;;;;;;;;;GAWG;AACH,qBAAa,gBAAiB,YAAW,cAAc,CACrD,cAAc,EACd,sBAAsB,EACtB,OAAO,CACR;IAOG,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAN3B,SAAgB,QAAQ,EAAE,MAAM,CAAC;IACjC,SAAgB,OAAO,gBAAgB;IACvC,SAAgB,KAAK,EAAG,UAAU,CAAU;IAC5C,SAAgB,WAAW,EAAE,SAAS,MAAM,EAAE,CAAM;gBAGjC,QAAQ,EAAE,cAAc,EACzC,QAAQ,SAAoB;IAK9B,2FAA2F;IAC9E,YAAY,IAAI,OAAO,CAAC,SAAS,mBAAmB,EAAE,CAAC;IAIpE;;;OAGG;IACU,OAAO,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC;IAkBvE,kGAAkG;IACrF,OAAO,CAClB,GAAG,EAAE,gBAAgB,CAAC,cAAc,CAAC,GACpC,OAAO,CAAC,eAAe,CAAC,sBAAsB,CAAC,CAAC;IAQtC,OAAO,CAClB,MAAM,EAAE,eAAe,CAAC,sBAAsB,CAAC,GAC9C,OAAO,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAwBzB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;CAGrF"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { CanonicalIntentHasher } from "../intent/CanonicalIntentHasher.js";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
/**
|
|
4
|
+
* The dispatch-style executor an `ActionRegistry` already is: every
|
|
5
|
+
* registered handler, run by intent, behind the contract. It is how
|
|
6
|
+
* `SafeExecutor` and the runtime's registry reach `GovernedExecutionRunner`
|
|
7
|
+
* without a change to a single handler: the registry keeps its dispatch
|
|
8
|
+
* tracking, its refusals and its single-flight reconciliation, and this
|
|
9
|
+
* class only reads what it reports into the contract's terms.
|
|
10
|
+
*
|
|
11
|
+
* The action it executes is the captured intent itself, so what it prepares
|
|
12
|
+
* is exactly what was decided: there is no second description of the action
|
|
13
|
+
* that could drift from the one the authority bound.
|
|
14
|
+
*/
|
|
15
|
+
export class RegistryExecutor {
|
|
16
|
+
registry;
|
|
17
|
+
platform;
|
|
18
|
+
version = "registry/1";
|
|
19
|
+
style = "dispatch";
|
|
20
|
+
actionTypes = [];
|
|
21
|
+
constructor(registry, platform = "action-registry") {
|
|
22
|
+
this.registry = registry;
|
|
23
|
+
this.platform = platform;
|
|
24
|
+
}
|
|
25
|
+
/** The registry knows its handlers, not a connection; it states no capability to check. */
|
|
26
|
+
async capabilities() {
|
|
27
|
+
return await Promise.resolve([]);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Refuses an unregistered action or parameters its handler's schema does
|
|
31
|
+
* not accept, by throwing exactly as the registry's own validation does.
|
|
32
|
+
*/
|
|
33
|
+
async prepare(captured) {
|
|
34
|
+
this.registry.validate(captured);
|
|
35
|
+
const intent = captured.intent;
|
|
36
|
+
const canonical = CanonicalIntentHasher.stringify(intent.parameters);
|
|
37
|
+
return await Promise.resolve({
|
|
38
|
+
actionType: intent.action,
|
|
39
|
+
proposal: { action: intent.action, target: intent.target, parameters: intent.parameters },
|
|
40
|
+
downstreamTarget: intent.downstreamTarget,
|
|
41
|
+
idempotencyKey: intent.idempotencyKey,
|
|
42
|
+
expectedEffectDigest: intent.expectedEffectDigest === undefined
|
|
43
|
+
? null
|
|
44
|
+
: intent.expectedEffectDigest,
|
|
45
|
+
requestDigest: `sha256:${createHash("sha256").update(canonical).digest("hex")}`,
|
|
46
|
+
facts: {},
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/** The registry tracks its own dispatch boundary; what it reports is carried through verbatim. */
|
|
50
|
+
async execute(run) {
|
|
51
|
+
const attempt = await this.registry.executeTracked(run.action, run.authorization);
|
|
52
|
+
if (attempt.status === "FAILED_BEFORE_DISPATCH") {
|
|
53
|
+
return { kind: "NOT_DISPATCHED", code: attempt.code };
|
|
54
|
+
}
|
|
55
|
+
return { kind: "DISPATCHED", receipt: attempt, signedReceipt: attempt.receipt };
|
|
56
|
+
}
|
|
57
|
+
async observe(effect) {
|
|
58
|
+
const attempt = effect.kind === "DISPATCHED" ? effect.receipt : null;
|
|
59
|
+
if (attempt === null || attempt.status === "FAILED_BEFORE_DISPATCH") {
|
|
60
|
+
return await Promise.resolve({ status: "INDETERMINATE", reason: null, result: null });
|
|
61
|
+
}
|
|
62
|
+
if (attempt.status === "COMPLETED") {
|
|
63
|
+
return {
|
|
64
|
+
status: "COMMITTED",
|
|
65
|
+
reason: null,
|
|
66
|
+
result: attempt.result,
|
|
67
|
+
signedReceipt: attempt.receipt,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
if (attempt.status === "REFUSED_AFTER_DISPATCH") {
|
|
71
|
+
return {
|
|
72
|
+
status: "FAILED",
|
|
73
|
+
reason: attempt.reason,
|
|
74
|
+
result: null,
|
|
75
|
+
signedReceipt: attempt.receipt,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return { status: "INDETERMINATE", reason: null, result: null, signedReceipt: attempt.receipt };
|
|
79
|
+
}
|
|
80
|
+
async reconcile(context) {
|
|
81
|
+
return await this.registry.reconcile(context.intent, context.idempotencyKey);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=RegistryExecutor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RegistryExecutor.js","sourceRoot":"","sources":["../../src/execution/RegistryExecutor.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,7 +4,8 @@ import type { CapturedIntent } from "../intent/ExecutionIntent.js";
|
|
|
4
4
|
import type { JsonObject } from "../intent/JsonValue.js";
|
|
5
5
|
import type { ActionRegistry } from "./ActionRegistry.js";
|
|
6
6
|
import type { AuthorizationVerifier, ExecutionCommitOutcome, ExecutionFinalization, VerifiedAuthorization } from "./AuthorizationVerifier.js";
|
|
7
|
-
|
|
7
|
+
import type { DispatchedAttempts, ExecutionBlockedResult, ExecutionHeldResult, ExecutionReconciliationResult, ExecutionRecoveryReference, HeldClaimResult, HeldExecution, SafeExecutionResult } from "./ExecutionOutcome.js";
|
|
8
|
+
export type { DispatchedAttempts, ExecutionBlockedResult, ExecutionBlockReason, ExecutionHeldResult, ExecutionPreDispatchFailureReason, ExecutionReconciliationResult, ExecutionRecoveryReference, HeldClaimResult, HeldExecution, ProviderOutcomeUnknownResult, ProviderRefusedResult, SafeExecutionResult, } from "./ExecutionOutcome.js";
|
|
8
9
|
/**
|
|
9
10
|
* What this executor is, beyond the registry and the verifier it was given.
|
|
10
11
|
*
|
|
@@ -35,155 +36,24 @@ export interface SafeExecutorOptions {
|
|
|
35
36
|
*/
|
|
36
37
|
readonly attempts?: DispatchedAttempts;
|
|
37
38
|
}
|
|
38
|
-
/** Where an executor reads back the authorizations it dispatched an intent under. */
|
|
39
|
-
export interface DispatchedAttempts {
|
|
40
|
-
/** Every authorization journaled for this intent, oldest first; none when it was never dispatched here. */
|
|
41
|
-
authorizationsOf(captured: CapturedIntent): Promise<readonly VerifiedAuthorization[]>;
|
|
42
|
-
}
|
|
43
|
-
export type ExecutionPreDispatchFailureReason = "HANDLER_FAILED_BEFORE_DISPATCH" | "AUDIT_UNAVAILABLE";
|
|
44
39
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
|
|
50
|
-
export interface ExecutionRecoveryReference {
|
|
51
|
-
readonly version: "agent-safe.recovery/1";
|
|
52
|
-
readonly decisionId: string;
|
|
53
|
-
readonly dossierId: string;
|
|
54
|
-
readonly grantId: string;
|
|
55
|
-
readonly intentHash: string;
|
|
56
|
-
readonly expiresAt: string;
|
|
57
|
-
readonly idempotencyKey: string;
|
|
58
|
-
}
|
|
59
|
-
export interface ExecutionBlockedResult {
|
|
60
|
-
readonly outcome: "BLOCKED";
|
|
61
|
-
readonly executed: false;
|
|
62
|
-
readonly reason: ExecutionBlockReason;
|
|
63
|
-
readonly result: null;
|
|
64
|
-
readonly authorization: null;
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* The provider refused, definitively, after the dispatch boundary. The grant
|
|
68
|
-
* was consumed and the request was sent, and nothing was effected: the
|
|
69
|
-
* outcome is a fact, not an unknown, so there is nothing to reconcile and no
|
|
70
|
-
* recovery reference to present. A caller that wants the action after all
|
|
71
|
-
* needs a fresh decision and a fresh grant, because the refusal was about
|
|
72
|
-
* this attempt.
|
|
73
|
-
*/
|
|
74
|
-
export interface ProviderRefusedResult {
|
|
75
|
-
readonly outcome: "DEFINITELY_NOT_EXECUTED";
|
|
76
|
-
readonly executed: false;
|
|
77
|
-
readonly recovered: false;
|
|
78
|
-
/** The provider's own reason, as the handler reported it. */
|
|
79
|
-
readonly reason: string;
|
|
80
|
-
readonly result: null;
|
|
81
|
-
readonly authorization: VerifiedAuthorization;
|
|
82
|
-
}
|
|
83
|
-
export interface ProviderOutcomeUnknownResult {
|
|
84
|
-
readonly outcome: "UNKNOWN_AFTER_DISPATCH";
|
|
85
|
-
readonly executed: null;
|
|
86
|
-
readonly reason: "PROVIDER_OUTCOME_UNKNOWN";
|
|
87
|
-
readonly result: null;
|
|
88
|
-
readonly authorization: VerifiedAuthorization;
|
|
89
|
-
readonly recovery: ExecutionRecoveryReference;
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Every outcome that consumed a grant reports whether the attempt's commit
|
|
93
|
-
* evidence reached the authority. Finalization is evidence, not authority;
|
|
94
|
-
* it never changes `outcome` or `executed`.
|
|
95
|
-
*/
|
|
96
|
-
export type SafeExecutionResult<TResult = unknown> = {
|
|
97
|
-
readonly outcome: "COMPLETED";
|
|
98
|
-
readonly executed: true;
|
|
99
|
-
readonly recovered: false;
|
|
100
|
-
readonly result: TResult;
|
|
101
|
-
readonly authorization: VerifiedAuthorization;
|
|
102
|
-
readonly finalization: ExecutionFinalization;
|
|
103
|
-
} | ExecutionBlockedResult | {
|
|
104
|
-
readonly outcome: "FAILED_BEFORE_DISPATCH";
|
|
105
|
-
readonly executed: false;
|
|
106
|
-
readonly reason: ExecutionPreDispatchFailureReason;
|
|
107
|
-
/** Why the handler refused, when it threw a `PreDispatchRefusal`. */
|
|
108
|
-
readonly code?: string;
|
|
109
|
-
readonly result: null;
|
|
110
|
-
readonly authorization: VerifiedAuthorization;
|
|
111
|
-
readonly finalization: ExecutionFinalization;
|
|
112
|
-
} | (ProviderRefusedResult & {
|
|
113
|
-
readonly finalization: ExecutionFinalization;
|
|
114
|
-
}) | (ProviderOutcomeUnknownResult & {
|
|
115
|
-
readonly finalization: ExecutionFinalization;
|
|
116
|
-
});
|
|
117
|
-
export type ExecutionReconciliationResult<TResult = unknown> = {
|
|
118
|
-
readonly outcome: "COMPLETED";
|
|
119
|
-
readonly executed: true;
|
|
120
|
-
readonly recovered: true;
|
|
121
|
-
readonly result: TResult;
|
|
122
|
-
readonly authorization: VerifiedAuthorization;
|
|
123
|
-
} | {
|
|
124
|
-
readonly outcome: "DEFINITELY_NOT_EXECUTED";
|
|
125
|
-
readonly executed: false;
|
|
126
|
-
readonly recovered: true;
|
|
127
|
-
readonly reason: "PROVIDER_CONFIRMED_NOT_EXECUTED";
|
|
128
|
-
readonly result: null;
|
|
129
|
-
readonly authorization: VerifiedAuthorization;
|
|
130
|
-
} | ExecutionBlockedResult | ProviderOutcomeUnknownResult;
|
|
131
|
-
/**
|
|
132
|
-
* A grant admitted for a later claim. Every check `run` makes before it
|
|
133
|
-
* consumes a grant has passed, and nothing has been consumed: the grant is
|
|
134
|
-
* still the authority's to hand out once. Only `hold` issues one, only the
|
|
135
|
-
* executor that issued it can claim it, and it is claimed at most once.
|
|
40
|
+
* The registry-shaped face of `GovernedExecutionRunner`, kept so that every
|
|
41
|
+
* caller written against it keeps its signatures and its outcomes exactly.
|
|
42
|
+
* Each method is the runner's, with the registry as the executor: admission,
|
|
43
|
+
* the claim, finalization, holds and reconciliation all happen in the
|
|
44
|
+
* runner, and nothing here decides anything.
|
|
136
45
|
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
46
|
+
* Two runner behaviours stay off behind this face, because they did not
|
|
47
|
+
* exist when it was written: the runner does not refuse a dispatch whose
|
|
48
|
+
* window closed (the registry's handlers bound their own dispatch), and it
|
|
49
|
+
* does not report reconciliations to the authority. Code that wants either
|
|
50
|
+
* uses the runner, with a `NativeExecutor`, directly.
|
|
141
51
|
*/
|
|
142
|
-
export interface HeldExecution {
|
|
143
|
-
readonly captured: CapturedIntent;
|
|
144
|
-
readonly decision: DecisionGate;
|
|
145
|
-
/** The grant's own expiry: after it there is nothing left to claim. */
|
|
146
|
-
readonly expiresAt: string;
|
|
147
|
-
}
|
|
148
|
-
export interface ExecutionHeldResult {
|
|
149
|
-
readonly outcome: "HELD";
|
|
150
|
-
readonly executed: false;
|
|
151
|
-
readonly result: null;
|
|
152
|
-
readonly authorization: null;
|
|
153
|
-
readonly held: HeldExecution;
|
|
154
|
-
}
|
|
155
|
-
export type HeldClaimResult = {
|
|
156
|
-
readonly outcome: "CLAIMED";
|
|
157
|
-
readonly authorization: VerifiedAuthorization;
|
|
158
|
-
} | ExecutionBlockedResult | Extract<SafeExecutionResult<never>, {
|
|
159
|
-
outcome: "FAILED_BEFORE_DISPATCH";
|
|
160
|
-
}>;
|
|
161
52
|
export declare class SafeExecutor {
|
|
162
|
-
private readonly
|
|
163
|
-
private readonly
|
|
164
|
-
|
|
165
|
-
private readonly hasher;
|
|
166
|
-
private readonly boundaryId;
|
|
167
|
-
private readonly workloadDigest;
|
|
168
|
-
/** The holds this executor issued and nobody has claimed, keyed by the frozen handle. */
|
|
169
|
-
private readonly holds;
|
|
170
|
-
/** The holds claimed and not yet settled, with the authorization each one consumed. */
|
|
171
|
-
private readonly claims;
|
|
172
|
-
private readonly attempts;
|
|
173
|
-
constructor(registry: ActionRegistry, verifier: AuthorizationVerifier, audit?: AuditRecorder | undefined, options?: SafeExecutorOptions);
|
|
53
|
+
private readonly runner;
|
|
54
|
+
private readonly executor;
|
|
55
|
+
constructor(registry: ActionRegistry, verifier: AuthorizationVerifier, audit?: AuditRecorder, options?: SafeExecutorOptions);
|
|
174
56
|
run<TResult = unknown>(captured: CapturedIntent, decision: DecisionGate): Promise<SafeExecutionResult<TResult>>;
|
|
175
|
-
/**
|
|
176
|
-
* Every check made before a grant may be consumed, with the evidence that
|
|
177
|
-
* the intent was captured and decided. A refusal is recorded and returned;
|
|
178
|
-
* null means the decision passed, and so carries a grant.
|
|
179
|
-
*/
|
|
180
|
-
private admit;
|
|
181
|
-
/**
|
|
182
|
-
* Consumes the grant through the verifier, checks what came back against
|
|
183
|
-
* the decision and the intent, and records the consumption and the start
|
|
184
|
-
* of execution. Nothing here dispatches.
|
|
185
|
-
*/
|
|
186
|
-
private consume;
|
|
187
57
|
/**
|
|
188
58
|
* Admits a decision exactly as `run` would and holds its grant instead of
|
|
189
59
|
* consuming it. The intent, the decision and the hold are recorded; the
|
|
@@ -195,18 +65,14 @@ export declare class SafeExecutor {
|
|
|
195
65
|
* Admits a hold this executor did not make: one another replica made, or
|
|
196
66
|
* this process made before a restart, read back from a store the replicas
|
|
197
67
|
* share. Every check `hold` makes is made again on the way in, and the
|
|
198
|
-
* record says the hold was adopted.
|
|
199
|
-
* is still the authority's to hand out once, so a hold claimed elsewhere
|
|
200
|
-
* is refused at the claim by the authority or the shared replay store,
|
|
201
|
-
* never admitted twice by this executor's own memory.
|
|
68
|
+
* record says the hold was adopted.
|
|
202
69
|
*/
|
|
203
70
|
adoptHold(captured: CapturedIntent, decision: DecisionGate): Promise<ExecutionHeldResult | ExecutionBlockedResult>;
|
|
204
71
|
/**
|
|
205
72
|
* Registers a claim this executor did not make, so that `settleHeld` can
|
|
206
|
-
* finalize it here
|
|
207
|
-
* the
|
|
208
|
-
*
|
|
209
|
-
* is, as always, the authority's to accept or refuse.
|
|
73
|
+
* finalize it here. Nothing is consumed and nothing is checked against the
|
|
74
|
+
* authority; the finalization is, as always, the authority's to accept or
|
|
75
|
+
* refuse.
|
|
210
76
|
*/
|
|
211
77
|
adoptClaim(captured: CapturedIntent, decision: DecisionGate, authorization: VerifiedAuthorization, material?: JsonObject | null): HeldExecution;
|
|
212
78
|
/**
|
|
@@ -215,13 +81,10 @@ export declare class SafeExecutor {
|
|
|
215
81
|
* verifier keeps its claims to itself or did not make this one.
|
|
216
82
|
*/
|
|
217
83
|
claimMaterial(authorization: VerifiedAuthorization): JsonObject | null;
|
|
218
|
-
private holdWith;
|
|
219
84
|
/**
|
|
220
|
-
* Claims a held grant, once.
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
* not issue is refused the same way. What the claim returns is checked
|
|
224
|
-
* exactly as `run` checks it.
|
|
85
|
+
* Claims a held grant, once. A second claim of the same hold, concurrent or
|
|
86
|
+
* later, is refused without reaching the authority; a handle this executor
|
|
87
|
+
* did not issue is refused the same way.
|
|
225
88
|
*/
|
|
226
89
|
claimHeld(held: HeldExecution): Promise<HeldClaimResult>;
|
|
227
90
|
/**
|
|
@@ -230,40 +93,6 @@ export declare class SafeExecutor {
|
|
|
230
93
|
* nobody can say. The terminal record names the outcome, as `run`'s do.
|
|
231
94
|
*/
|
|
232
95
|
settleHeld(held: HeldExecution, outcome: ExecutionCommitOutcome, receipt?: string | null): Promise<ExecutionFinalization>;
|
|
233
|
-
private static settledEvent;
|
|
234
|
-
/**
|
|
235
|
-
* Reports the attempt outcome to the verifier's authority, with the
|
|
236
|
-
* provider's effect receipt when the attempt brought one back. A missing,
|
|
237
|
-
* failing, or malformed finalization can never alter the execution result.
|
|
238
|
-
*/
|
|
239
|
-
private finalize;
|
|
240
|
-
private static finalizationCode;
|
|
241
96
|
reconcile<TResult = unknown>(captured: CapturedIntent, recovery: ExecutionRecoveryReference): Promise<ExecutionReconciliationResult<TResult>>;
|
|
242
|
-
/**
|
|
243
|
-
* Observational artifacts carry an explicit non-authoritative marker. They
|
|
244
|
-
* must never be interpreted as a `DecisionGate`, even after an unsafe cast
|
|
245
|
-
* or a JSON round trip.
|
|
246
|
-
*/
|
|
247
|
-
private static isAuthoritativeDecision;
|
|
248
|
-
/**
|
|
249
|
-
* Whether this boundary is the one the intent was captured through. The
|
|
250
|
-
* boundary is read back out of the hashed context, so an intent whose
|
|
251
|
-
* boundary was edited after capture fails conformance before it reaches
|
|
252
|
-
* here, and one that never named a boundary cannot acquire one now.
|
|
253
|
-
*/
|
|
254
|
-
private boundaryMatches;
|
|
255
|
-
/**
|
|
256
|
-
* Whether the workload that proposed the intent is the one running it. Like
|
|
257
|
-
* the boundary, the digest is read back out of the hashed context, so an
|
|
258
|
-
* intent whose workload was edited after capture fails conformance first.
|
|
259
|
-
*/
|
|
260
|
-
private workloadMatches;
|
|
261
|
-
private intentConforms;
|
|
262
|
-
private block;
|
|
263
|
-
private failedBeforeDispatch;
|
|
264
|
-
private record;
|
|
265
|
-
private static recoveryReference;
|
|
266
|
-
private static sameBinding;
|
|
267
|
-
private static authorizationFrom;
|
|
268
97
|
}
|
|
269
98
|
//# sourceMappingURL=SafeExecutor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SafeExecutor.d.ts","sourceRoot":"","sources":["../../src/execution/SafeExecutor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"SafeExecutor.d.ts","sourceRoot":"","sources":["../../src/execution/SafeExecutor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EACV,qBAAqB,EACrB,sBAAsB,EACtB,qBAAqB,EACrB,qBAAqB,EACtB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EACV,kBAAkB,EAClB,sBAAsB,EACtB,mBAAmB,EACnB,6BAA6B,EAC7B,0BAA0B,EAC1B,eAAe,EACf,aAAa,EACb,mBAAmB,EACpB,MAAM,uBAAuB,CAAC;AAI/B,YAAY,EACV,kBAAkB,EAClB,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,EACnB,iCAAiC,EACjC,6BAA6B,EAC7B,0BAA0B,EAC1B,eAAe,EACf,aAAa,EACb,4BAA4B,EAC5B,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,uBAAuB,CAAC;AAE/B;;;;;;;;;GASG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;;;OAMG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC;;;;;;;OAOG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CACxC;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0B;IACjD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmB;gBAG1C,QAAQ,EAAE,cAAc,EACxB,QAAQ,EAAE,qBAAqB,EAC/B,KAAK,CAAC,EAAE,aAAa,EACrB,OAAO,CAAC,EAAE,mBAAmB;IAclB,GAAG,CAAC,OAAO,GAAG,OAAO,EAChC,QAAQ,EAAE,cAAc,EACxB,QAAQ,EAAE,YAAY,GACrB,OAAO,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAQxC;;;;;OAKG;IACU,IAAI,CACf,QAAQ,EAAE,cAAc,EACxB,QAAQ,EAAE,YAAY,GACrB,OAAO,CAAC,mBAAmB,GAAG,sBAAsB,CAAC;IAIxD;;;;;OAKG;IACU,SAAS,CACpB,QAAQ,EAAE,cAAc,EACxB,QAAQ,EAAE,YAAY,GACrB,OAAO,CAAC,mBAAmB,GAAG,sBAAsB,CAAC;IAIxD;;;;;OAKG;IACI,UAAU,CACf,QAAQ,EAAE,cAAc,EACxB,QAAQ,EAAE,YAAY,EACtB,aAAa,EAAE,qBAAqB,EACpC,QAAQ,GAAE,UAAU,GAAG,IAAW,GACjC,aAAa;IAShB;;;;OAIG;IACI,aAAa,CAAC,aAAa,EAAE,qBAAqB,GAAG,UAAU,GAAG,IAAI;IAI7E;;;;OAIG;IACU,SAAS,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,eAAe,CAAC;IAIrE;;;;OAIG;IACU,UAAU,CACrB,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,sBAAsB,EAC/B,OAAO,GAAE,MAAM,GAAG,IAAW,GAC5B,OAAO,CAAC,qBAAqB,CAAC;IAMpB,SAAS,CAAC,OAAO,GAAG,OAAO,EACtC,QAAQ,EAAE,cAAc,EACxB,QAAQ,EAAE,0BAA0B,GACnC,OAAO,CAAC,6BAA6B,CAAC,OAAO,CAAC,CAAC;CAOnD"}
|